diff --git a/amd/main.js b/amd/main.js index bde57ee..ae3cd81 100644 --- a/amd/main.js +++ b/amd/main.js @@ -1,7 +1,7 @@ require.config({ paths: { 'jquery': '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min', - 'circuit-sdk': '../circuit' + 'circuit-sdk': '//unpkg.com/circuit-sdk/circuit' } }); diff --git a/circuit.js b/circuit.js deleted file mode 100644 index b98ff67..0000000 --- a/circuit.js +++ /dev/null @@ -1,47634 +0,0 @@ -(function (root, factory) { - if (root === undefined && window !== undefined) root = window; - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module unless amdModuleId is set - define([], function () { - return (root['Circuit'] = factory()); - }); - } else if (typeof module === 'object' && module.exports) { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - root['Circuit'] = factory(); - } -}(this, function () { - -/** - * Copyright 2018 Unify Software and Solutions GmbH & Co.KG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * @version: 1.2.2800 - */ - -var Circuit = {}; Object.defineProperty(Circuit, 'version', { value: '1.2.2800'}); - -// Define external globals for JSHint -/*global Buffer, clearInterval, clearTimeout, process, require, setInterval, setTimeout*/ - -var global = Function('return this')(); -var window = global.window || global; -var navigator = window.navigator || {}; -var document = window.document; -var WebSocket = global.WebSocket; -var XMLHttpRequest = global.XMLHttpRequest; -var Promise = global.Promise; - -var Circuit = (function (circuit) { - 'use strict'; - - circuit.isSDK = true; - - // Browser: - // runtime = { - // name: 'browser' - // } - // - // NodeJS example: - // runtime = { - // name: 'node', - // version: '0.12.7', - // hostname: 'circuitsandbox.net', - // platform: 'linux' - // } - var _runtime = { - name: 'unknown' - }; - - if (typeof process !== 'undefined') { - if (process.versions && process.versions.node) { - _runtime = { - name: 'node', - version: process.versions.node, - hostname: require('os').hostname() - }; - } else if (process.argv && process.argv[0]) { - // Unknown runtime, use process name - _runtime = { - name: process.argv[0] - }; - } - } else { - _runtime = { - name: 'browser' - }; - } - - // Stub window object with required APIs if not defined - window.setTimeout = window.setTimeout || setTimeout; - window.clearTimeout = window.clearTimeout || clearTimeout; - window.setInterval = window.setInterval || setInterval; - window.clearInterval = window.clearInterval || clearInterval; - if (!window.location) { - window.location = { - host: _runtime.hostname - }; - } - if (!navigator.platform) { - navigator.platform = _runtime.name; - } - - if (!navigator.userAgent) { - navigator.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2125.122 Safari/537.36'; - } - - if (!window.navigator) { - window.navigator = navigator; - } - - if (!window.btoa) { - window.btoa = function (value) { - return new Buffer(value).toString('base64'); - }; - } - - // Exports - circuit._runtime = _runtime; - circuit.DefaultAvatars = {}; - - return circuit; -})(Circuit || {}); - -// Define global variables for JSHint -/*global console*/ - -var Circuit = (function (circuit) { - 'use strict'; - - /** - * Enum for Log level of API logging - * @class LogLevel - * @memberOf Circuit - * @static - * @final - * @property Debug - Debug Log Level - * @property Info - Info Log Level - * @property Warning - Warning Log Level - * @property Error - Error Log Level - */ - var LogLevel = Object.freeze({ - /** - * Value: 1 - * @property Debug - * @type {Number} - * @static - */ - Debug: 1, - /** - * Value: 2 - * @property Info - * @type {Number} - * @static - */ - Info: 2, - /** - * Value: 3 - * @property Warning - * @type {Number} - * @static - */ - Warning: 3, - /** - * Value: 4 - * @property Error - * @type {Number} - * @static - */ - Error: 4, - /** - * Value: 5 - * @property Off - * @type {Number} - * @static - */ - Off: 5 - }); - - var _logLevel = LogLevel.Warning; - - /** - * API logger instance. - * @static - * @class logger - */ - var logger = { - /** - * Set the log level for API logging. - * @method setLevel - * @param {LogLevel} level Log level - * @returns {void} - * @memberof Circuit.logger - * @example - * Circuit.logger.setLevel(Circuit.Enums.LogLevel.Debug); - */ - setLevel: function (level) { - _logLevel = level; - }, - - /** - * Get the log level for API logging. - * @method getLevel - * @returns {LogLevel} Log level - * @memberof Circuit.logger - * @example - * var level = Circuit.logger.getLevel(); - */ - getLevel: function () { - return _logLevel; - }, - - debug: function () { - if (_logLevel <= LogLevel.Debug) { - console.log.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - info: function () { - if (_logLevel <= LogLevel.Info) { - console.info.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - warning: function () { - if (_logLevel <= LogLevel.Warning) { - console.warn.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - warn: function () { - if (_logLevel <= LogLevel.Warning) { - console.warn.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - error: function (error, obj) { - if (_logLevel <= LogLevel.Error) { - error = (error && error.stack) || error; - obj = (obj && obj.stack) || obj; - if (obj) { - console.error(error, obj); - } else { - console.error(error); - } - } - }, - msgSend: function () { - if (_logLevel <= LogLevel.Debug) { - console.log.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - msgRcvd: function () { - if (_logLevel <= LogLevel.Debug) { - console.log.apply(console, Array.prototype.slice.apply(arguments)); - } - }, - - logMsg: function (minLevel, txt, msg) { - switch (minLevel.name) { - case 'DEBUG': - this.debug(txt, msg); - break; - case 'INFO': - this.info(txt, msg); - break; - case 'WARN': - case 'WARNING': - this.info(txt, msg); - break; - case 'ERROR': - this.info(txt, msg); - break; - default: - this.debug(txt, msg); - break; - } - }, - - setExtensionVersion: function (version) { - console.log('ExtensionVersion = ' + version); - } - }; - - circuit.logger = logger; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.LogLevel = LogLevel; - - return circuit; - -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - /** - * A Circuit error object derived from the JavaScript error object. - * - * @class Error - * @constructor - * @param {String} code Error code - * @param {String|Object} err The error message, or a detailed error object. - */ - circuit.Error = function (code, err) { - this.code = code || 'UNEXPECTED_ERROR'; - - if (typeof err === 'string') { - this.message = err; - } else if (typeof err === 'object') { - this.errObj = err; - err.info && !err.message && (err.message = err.info); - } - this.stack = Error().stack; - }; - circuit.Error.prototype = Object.create(Error.prototype); - circuit.Error.prototype.name = 'Error'; - - /** - * Error message - * - * @property message - * @type String - */ - - /** - * Error code as defined in Circuit.Constants.ReturnCode or Circuit.Constants.ErrorCode. - * E.g. `UNEXPECTED_ERROR` - * - * @property code - * @type String - */ - - /** - * Detailed error object containing detailed information that can be used when debugging. The object - * is different depending on the type of error. - * - * @property errObj - * @type Object - */ - return circuit; - -})(Circuit || {}); - - -// Define global variables for JSHint -/*global window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Define the object - var storage = null; - - try { - storage = window.localStorage || storage; - } catch (e) { - // If access to the localStorage is denied use sessionStorage - // This fixes problem with IE on Windows 8 - storage = window.sessionStorage || storage; - } - - function invokeMethod(method, args) { - try { - return storage ? storage[method].apply(storage, args) : null; - } catch (e) { - return null; - } - } - - var storeManager = { - getItem: function (key) { - return invokeMethod('getItem', [key]); - }, - setItem: function (key, value) { - return invokeMethod('setItem', [key, value]); - }, - removeItem: function (key) { - return invokeMethod('removeItem', [key]); - } - }; - - // Exports - circuit.storeManager = storeManager; - - return circuit; - -})(Circuit || {}); - -// Define global variables for JSHint -/*global module*/ - -var Circuit = (function (circuit) { - 'use strict'; - - var ChromeExtension = {}; - - ChromeExtension.SIGNATURE = 'ansibleChromeExtensionSignature'; - - ChromeExtension.DOMEvent = Object.freeze({ - TO_EXTENSION: 'toExtension', - FROM_EXTENSION: 'fromExtension' - }); - - // Used to define if we have a callback or not. - ChromeExtension.BgMsgType = Object.freeze({ - REQUEST: 'request', - RESPONSE: 'response', - EVENT: 'event' - }); - - // Features (Targets). As a general rule, we have message types for each feature (Target) - ChromeExtension.BgTarget = Object.freeze({ - INTERNAL: 'internal', - LOG: 'log', - SCREEN_SHARE: 'screenShare', - EXCHANGE_CONNECTOR: 'exchange_connector', - HEADSET_APP: 'headsetAppManager' - }); - - // Error reasons - ChromeExtension.ResponseError = Object.freeze({ - UNREGISTERED: 'unregistered', // The content script failed to communicate with the extension and unregistered itself. - TIMEOUT: 'timeout', // Timeout waiting for response from extension - UNSUPPORTED_REQUEST: 'unsupportedRequest', // Extension doesn't support this request - INTERNAL_ERROR: 'internalError' // There was an internal error processing the request - }); - - // Log events - ChromeExtension.BgLogMsgType = Object.freeze({ - LOG: 'log' - }); - - // Internal Targets - ChromeExtension.BgInternalMsgType = Object.freeze({ - INIT_MSG: 'initMsg', - INIT_MSG_ACK: 'initMsgAck', - INJECT_SIGNATURE: 'injectChromeAppSignature', - IS_ALIVE: 'isAlive', - GET_WINDOW_SIZE: 'getWindowSize', - EXTENSION_DISCONNECTED: 'extensionDisconnected', - BRING_TO_FRONT: 'bringToFront' - }); - - ChromeExtension.BgScreenShareMsgType = Object.freeze({ - CHOOSE_DESKTOP_MEDIA: 'cdm', - CHOOSE_DESKTOP_MEDIA_DONE: 'cdmDone', - CHOOSE_DESKTOP_MEDIA_CANCELLED: 'cdmCancelled' - }); - - ChromeExtension.BgExchangeMsgType = Object.freeze({ - CONNECT: 'connect', - DISCONNECT: 'disconnect', - SEARCH_CONTACTS: 'searchContacts', - GET_CONNECTION_STATUS: 'getConnectionStatus', - GET_CAPABILITIES: 'getCapabilities', - GET_CONTACT: 'getContact', - STORE_EXCH_CREDENTIALS: 'storeCredentials', - CONNECTION_STATUS: 'connectionStatus', - GET_ALL_PERSONAL_CONTACTS: 'getAllPersonalContacts', - CONTACT_FOLDERS_SYNC_STATE: 'contactFoldersSyncState', - SYNC_ALL_PERSONAL_CONTACTS: 'syncAllPersonalContacts', - GET_APPOINTMENTS: 'getAppointments', - GET_STORED_CREDENTIALS: 'getStoredCredentials', - GET_RENEWED_TOKEN: 'getRenewedToken', - ON_RENEWED_TOKEN: 'onRenewedToken' - }); - - // Internal Targets - ChromeExtension.BgHeadsetAppMsgType = Object.freeze({ - GET_HEADSET_APP_STATUS: 'getHeadsetAppStatus', - LAUNCH_HEADSET_APP: 'launchHeadsetApp', - HEADSET_APP_LAUNCHED: 'headsetAppLaunched', - HEADSET_APP_UNINSTALLED: 'headsetAppUninstalled' - }); - - // Possible response values sent by Exchange Connector - ChromeExtension.ExchangeConnResponse = Object.freeze({ - OK: 'ok', - NO_RESULT: 'noResult', - FAILED: 'failed', - COULD_NOT_CONNECT: 'couldNotConnect', - UNAUTHORIZED: 'unauthorized', - UNSUPPORTED_METHOD: 'unsupportedMethod', - TIMED_OUT: 'timedOut', - MAILBOX_NOT_FOUND: 'mailboxNotFound', - NTLM_NOT_SUPPORTED: 'ntlmNotSupported' - }); - - // Headset Integration App statuses - ChromeExtension.HeadsetAppStatus = Object.freeze({ - NO_MANAGEMENT_PERMISSION: 'noManagementPermission', - NOT_INSTALLED: 'notInstalled', - INSTALLED: 'installed', - DISABLED: 'disabled', - UNKNOWN: 'unknown' - }); - - ChromeExtension.Exchange = Object.freeze({ - CONFIGURED_PASSWORD_MASKED: '$$$$' - }); - - ChromeExtension.PortMsg = Object.freeze({ - CONNECT: 'connect', - DISCONNECT: 'disconnect', - MSG: 'msg' - }); - - // Exports - circuit.ChromeExtension = ChromeExtension; - - if (typeof module !== 'undefined' && module.exports) { - module.exports = ChromeExtension; - } - - return circuit; - -})(Circuit || {}); - -/*global document, escape, navigator, PhoneNumberUtil, process, require, unescape, window, XMLHttpRequest, he, Skype*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Variables used for electron app and for cross domain registration. - circuit.__server = ''; - circuit.__domain = ''; - circuit.isElectron = !!(!circuit.isSDK && typeof process !== 'undefined' && process.versions && process.versions.electron); - - // Imports - var logger = circuit.logger; - var Emoticons = circuit.Emoticons; - var Emoji = circuit.Emoji; - var remote; - if (circuit.isElectron) { - remote = require('electron').remote; - circuit.__server = remote.getCurrentWindow()._domain; - } - - if (typeof Skype !== 'undefined') { - circuit.SkypeSDK = Skype; // Skype sdk library exposes this to window object, need to isolate it - Skype = null; - } - - var EMOTICON_REGEX = /<(img[^>|<]*(?:shortcut|abbr)="(\S*)"[^\/?>]*\/?)>/gi; - - // SSL proxy to overcome mixed-content warning due to loading unsecure preview images - var SSL_IMAGE_PROXY = 'https://embed-proxy.circuitsandbox.net/?target='; - - /** - * Static utility functions - * @class Utils - * @static - */ - var Utils = circuit.Utils || {}; - - // Converts any decimal htmlEntity to the required symbol - used for mobile emoji support - function decimalToSymbol(htmlEntity) { - var dec = htmlEntity.replace(/[&#;]/g, ''); - if (!(/^[0-9]+$/).test(dec) || (dec >= 0xD800 && dec <= 0xDFFF) || dec > 0x10FFFF) { // Invalid input - return htmlEntity; - } - - var res = ''; - if (dec > 0xFFFF) { - dec -= 0x10000; - res += String.fromCharCode(dec >>> 10 & 0x3FF | 0xD800); //eslint-disable-line no-bitwise - dec = 0xDC00 | dec & 0x3FF; //eslint-disable-line no-bitwise - } - res += String.fromCharCode(dec); - return res; - } - - // Conversation fields which need to be trimmed from the conversation objects - // sent to the mobile clients. - var TRIM_FOR_MOBILE_GET_LISTS = ['items', 'parents', 'peerUsers', 'previousMembers']; - - Utils.trimCallForMobile = function (call) { - var mobileCall; - if (call) { - mobileCall = Utils.flattenObj(call); - // TODO: Need to enhance iOS and Android apps before we can trim the fields. - // TRIM_FOR_MOBILE_GET_LISTS.forEach(function (field) { - // delete mobileCall[field]; - // }); - } - return mobileCall; - }; - - Utils.trimConvForMobile = function (conv) { - var mobileConv; - if (conv) { - mobileConv = Utils.flattenObj(conv); - TRIM_FOR_MOBILE_GET_LISTS.forEach(function (field) { - delete mobileConv[field]; - }); - mobileConv.call = Utils.trimCallForMobile(conv.call); - } - return mobileConv; - }; - - Utils.isPromise = function (obj) { - return !!(obj && typeof obj.then === 'function'); - }; - - /** - * Converts the given version string into an version number. - * - * @param {String} version The client version in a 'x.xx.xxxx[-SNAPSHOT]' format. - */ - Utils.convertVersionToNumber = function (version) { - if (typeof version !== 'string') { - return 0; - } - var splitVersion = version.split('.'); - var release = parseInt(splitVersion[0], 10) || 0; - var major = parseInt(splitVersion[1], 10) || 0; - major = Math.min(major, 99); - var minor = parseInt(splitVersion[2], 10) || 0; - minor = Math.min(minor, 9999); - - return minor + major * 1e4 + release * 1e6; - }; - - /** - * Trims a text, given a selected length, from beginning, adding ellipsis. - */ - Utils.trimTextWithEmoticonsBeforeMatch = function (text, textToMatch, maxCharactersBeforeMatch) { - var PREFIX = '...'; - var matchedIndex = text.indexOf(textToMatch); - // contains the matched term and the text on its right - var afterMatchingPart = text.slice(matchedIndex); - // text before matchmatchedIndex - var beforeMatchingPart = text.slice(0, matchedIndex); - - var LT = String.fromCharCode(17); // Temporary mapping using non-printable ascii code x11 (DEC 17) - var GT = String.fromCharCode(18); // Temporary mapping using non-printable ascii code x12 (DEC 18) - var content = beforeMatchingPart.replace(EMOTICON_REGEX, function (a, b) { - return LT + b + GT; - }); - - // We run the beforeMatchingText in inverse order so we can locate - // the non printable ending character - var isCounting = true; - var trimingIndex = content.length - 1; - var loopIndex = content.length - 1; - var splitIndex = loopIndex; - - // We iterate the String from end. We skip characters that are between non-printable characters - // and update non-printable chars on the fly, in order to generate a valid html with emoticons. - while (((beforeMatchingPart.length - 1) - trimingIndex) <= maxCharactersBeforeMatch && loopIndex >= 0) { - if (content.charCodeAt(loopIndex) === 18) { - content = content.replaceAt(loopIndex, '>'); - isCounting = false; - } else if (content.charCodeAt(loopIndex) === 17) { - content = content.replaceAt(loopIndex, '<'); - isCounting = true; - } - - splitIndex = loopIndex; - loopIndex--; - isCounting && trimingIndex--; - } - - // Prefix will be added only in case we trim text from begining. - beforeMatchingPart = splitIndex > 0 ? PREFIX + content.slice(splitIndex) : content; - - return beforeMatchingPart + afterMatchingPart; - }; - - /** - * Removes restrictions from the encodeURIComponent() function - */ - Utils.enhanceEncodeURI = function (s) { - // mappings to replace '!,(,),#,{,},' with proper encoding - var charactersMap = { - '!': '%21', - '(': '%28', - ')': '%29', - '#': '%23', - '{': '%7B', - '}': '%7D' - }; - - return encodeURIComponent(s).replace(/[!\(\)#\{\}]/gi, function (matched) { - return charactersMap[matched]; - }); - }; - - Utils.normalizeDn = function (dn) { - if (!dn) { - return ''; - } - return dn.replace('+', ''); - }; - - Utils.cleanPhoneNumber = function (number) { - return number ? number.replace(/[^\*\#\d\+]/g, '') : ''; - }; - - /** - * Checks the meta tags for a given Html input to determine if the html data source is a MS document. - * Used when pasting in the conversation input. - */ - Utils.isMSDoc = function (content) { - // FF and Chrome paste tag with 'WordDocument like ''. - // IE pastes html with styles begin with 'mso-', e.g. ''. - // From Microsoft OneNote html is pasted with tag - return content.search(/<\S*?WordDocument>/i) >= 0 || - content.search(/<[^>]*?style=[^>]*?mso-[^<]+?>/i) >= 0 || - content.search(/]*?Microsoft/i) >= 0; - }; - - /** - * Checks the meta tags for a given Html input to determine if the html data source is a MS document. - * Used when pasting in the conversation input. - */ - Utils.isXCodeDoc = function (content) { - // Contain meta tag with attribute contain="Cocoa HTML Writer" - return content.search(/<[^>]*?content=[^>]*?Cocoa[^<]+?>/) >= 0; - }; - - /** - * HTML Sanitizer. For web only. Web needs to set this - * to the Angular sanitizer. - */ - Utils.sanitizer = null; - - /** - * Sanitize function. - * Bypass style attribute for Ansible rich text highlighting (not search highlighting) - */ - Utils.sanitize = function (content) { - if (!content || !Utils.sanitizer) { - return content; - } - - try { - // Angular sanitize does not allow style tags, so replace it with a class. - // NOTE: If the class name changes, be sure to also change in ConversationItemMessageCell.m for iOS client. - // Used for post messages - content = content.replace(/style="background-color: rgb\(212, 239, 181\);"/gi, 'class="rich-text-highlight"'); - - // Used for pasting highlighted content - content = content.replace(/<[^>]+background-color: rgb\(212, 239, 181\);.*?>/gi, function (matchTag) { - if (matchTag.indexOf('class=') > 0) { - // Check if class attribute is already exists - return matchTag.replace(/class=".*?"/gi, function (matchClass) { - if (matchClass.indexOf('rich-text-highlight') > 0) { - // Return If highlight class is already exists - return matchClass; - } else { - // Add highlight class - return matchClass.replace(/[^=]\s*(.*)/gi, function (match) { - return match.slice(0, -1) + ' rich-text-highlight'; - }); - } - }); - } else { - // If no class attribute exists, add highlight class to the end of the tag - return matchTag.replace('>', function (match) { - return ' class="rich-text-highlight" ' + match; - }); - } - }); - - // Remove image tags that are not emoticon ones (emoticon ones are e.g. - // or emoji ones - content = content.replace(/]*class="(emoticon-icon|emojione-icon))[^>]*>/gi, ''); - - // Remove tag because sanitize extracts title content as text, which can't be identified and - // removed later and can't be pasted inside content as well. - content = content.replace(/<title[^>]*>([\s\S]*?)<\/title>/gi, ''); - - // It also does not like the shortcut attribute used for emoticons. Replace 'shortcut' tag with 'abbr' - // (Needed for backward compatibility) - content = Emoticons.normalizeEmoticonHtml(content); - - // Remove <font> tags - content = content.replace(/<font[^>]*>|<\/font>/gi, ''); - - // Normalize <hr>,<hr></hr> to <hr/> - content = content.replace(/(<\s*hr\s*><\s*\/hr\s*>|<\s*hr\s*>)/gi, '<hr/>'); - - // Remove \n and \r inside tags - content = content.replace(/(\r|\n)+(?=[^<]*?>)/gi, ' '); - - // If any \r or \n characters exist (due to paste actions etc, convert them to <br> so they can be handled properly) - content = content.replace(/\r\n|\r|\n/g, '<br>'); - - if (typeof Utils.sanitizer === 'function') { - content = Utils.sanitizer(content); - } - - // Tokenize HTML in div/p elements. Removes unnecessary divs, added from RICH TEXT editor, without losing/adding new lines. - var tokens = content.split(/<\/?div.*?>|<\/?p.*?>/g).filter(function (token) { return !!token; }); - var tokensLen = tokens.length; - - var tempContent = ''; - if (tokensLen > 1) { // rich - // Find only last <br> tag: - // '...text<br>' - // '...<b>text<br></b>' - var lastBrTag = new RegExp('<br>(?![\\s\\S]*<br>)(?=(<[^\/][^>]*><\/[^>]*>)*$|(<\/[^>]*>)*$|$)', 'i'); - - var curr, prev; - for (var i = 0; i < tokensLen; i++) { - // <br>'s in bold, italics are removed in sanitization, so we need to replace them with single <br> - curr = tokens[i].replace(/((<span( class="[\S]*")*>)|(<b( class="[\S]*")*>)|(<i( class="[\S]*")*>))*(<br>|<br\/>)(<\/span>|<\/b>|<\/i>)*/g, '<br>'); - - if (prev && !prev.endsWith('</ul>') && !prev.endsWith('</ol>') && !lastBrTag.test(prev)) { - // The previous token does not end with a <br>, so we need to insert one - tempContent += '<br>'; - } - tempContent += curr; - prev = curr; - } - } else { // simple - tempContent = tokens[0]; - } - content = tempContent; - - } catch (e) { - logger.error('[Utils]: Error sanitizing content. ', e); - logger.error('[Utils]: Unexpected content: ', content); - return ''; - } - - return content; - }; - - Utils.sanitizeSymbols = function (content) { - // Available only to mobile clients who have he.js library loaded - if (!Utils.isMobile() || typeof he === 'undefined') { - return content; - } - // Convert symbols to html entities so they can be read in a uniform way. Also we should send encoded escaped html content in the backend. - // (but currently only emoji symbols need encoding) - // Used HE encoder/decoder to support all possible symbol encoding, https://github.com/mathiasbynens/he - // Other possible solution would be for mobiles to manually detect symbols when sending and convert them to html entities - // e.g. not send ✌ (victory hand emoji character) but ✌ Because newer emoji chars have more bytes and are combined with some non-printable - // modificator characters (e.g. black victory hand) we need a library to detect all manually, so we use he.js - // Encode only non-Ascii chars ('allowUnsafeSymbols = true' means leave &, <, >, ", ', and backtick unencoded if sent by mobiles) - // and convert it using decimal html entity to be alligned with web client (default in he.js is hex - it works in either case) - return content && he.encode(content, { 'allowUnsafeSymbols': true, 'decimal': true}); - }; - - Utils.convertSymbolCodes = function (content) { - if (!Utils.isMobile()) { - return content; - } - - // Match any &#<digits>; pattern e.g. 〹 - return content && content.replace(Emoji.HTML_ENTITY_REGEX, function (match) { - return decimalToSymbol(match); - }); - }; - - Utils.toPlainText = function (content, type) { - if (content) { - // If type parameter is omitted or is 'RICH', convert to plain - if (!type || (type === 'RICH')) { - // Replace emoticons with their shortcut - content = content.replace(Emoticons.HTML_REGEX, function (matchedString, submatchStr) { - if (Emoticons.isEmoticonContent(matchedString)) { - return submatchStr; - } - return matchedString; - }); - - var elem = document.createElement('div'); - elem.innerHTML = content.replace(/<(br[\/]?|\/li|hr[\/]?)>/gi, ' '); - return elem.textContent; - } - } - return content; // In any other case (type parameter set but not 'RICH' - e.g. 'PLAIN'), don't convert - }; - - Utils.toPlainTextWithEmoticonHtml = function (content, type) { - if (content) { - // If type parameter is omitted or is 'RICH', convert to plain - if (!type || (type === 'RICH')) { - var LT = String.fromCharCode(17); // Temporary mapping using non-printable ascii code x11 (DEC 17) - var GT = String.fromCharCode(18); // Temporary mapping using non-printable ascii code x12 (DEC 18) - - // Replace emoticons with their shortcut + some non-printable characters, so we can restore them - content = content.replace(Emoticons.HTML_REGEX, function (matchedString, groupContent) { - // Don't mix it with emoji's abbr - if (Emoticons.isEmoticonContent(matchedString)) { - return LT + groupContent + GT; - } - return matchedString; - }); - - - // Replace content with emoji html so we can escape what is needed so it is not lost throughout the conversion to text - content = Emoji.convertEmojis(content); - - var LT_EMOJI = String.fromCharCode(19); // Temporary mapping using non-printable ascii code x13 (DEC 19) - var GT_EMOJI = String.fromCharCode(20); // Temporary mapping using non-printable ascii code x14 (DEC 20) - - // Replace emoji with their abbr shortcut + some non-printable characters, so we can restore them - content = content.replace(Emoji.HTML_REGEX, function (matchedString, groupContent) { - // Don't mix it with emoticons abbr - if (Emoji.isEmojiContent(matchedString)) { - return LT_EMOJI + groupContent + GT_EMOJI; - } - return matchedString; - }); - - - var elem = document.createElement('div'); - elem.innerHTML = content.replace(/<(br[\/]?|\/li|hr[\/]?)>/gi, ' '); - var textContent = Utils.textToHtmlEscaped(elem.textContent, true); // Escape any user input html code typed e.g. '<' - - // Restore emoticon html replacing all shortcuts found inside LT and GT non-printable characters (x11, x12) with their html code equivalent - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter - textContent = textContent.replace(/\x11([^\x11\x12]*)\x12/gim, function (match, contents/*, offset, s*/) { - return Emoticons.getHtmlFromShortcut(contents, false, true, true); - }); - - // Restore emoji html replacing all shortcuts found inside LT and GT non-printable characters (x13, x14) with their html code equivalent - return textContent.replace(/\x13([^\x13\x14]*)\x14/gim, function (match, contents/*, offset, s*/) { - return Emoji.convertEmojis(contents.replace(/&/g, '&')); - }); - } - } - return content; // In any other case (type parameter set but not 'RICH' - e.g. 'PLAIN'), don't convert - }; - - // Truncates big file names and puts ellipsis in the middle - Utils.truncateFileName = function (fileName) { - if (!fileName || fileName.length <= 22) { - return fileName; - } - return fileName.substring(0, 10) + ' ...' + - fileName.substring(fileName.length - 8, fileName.length); - }; - - Utils.convertItemText = function (content, type, keepEmoticonHtml) { - if (!content) { - return ''; - } - if (keepEmoticonHtml) { - return Utils.toPlainTextWithEmoticonHtml(content, type); - } - return Utils.toPlainText(content, type); - }; - - Utils.createMentionedUsersArray = function (msg) { - var mentionedUsers = []; - // We cannot guarantee the order of the attributes within the span, so we need - // to split the patterns. - var mentionPattern = /<span.*?class=["']mention["'].*?>/g; - var abbrPattern = /abbr=["'](.*?)["']/; - var match = mentionPattern.exec(msg); - - while (match !== null) { - var abbr = abbrPattern.exec(match[0]); - abbr = abbr && abbr[1]; - if (mentionedUsers.indexOf(abbr) === -1) { - mentionedUsers.push(abbr); - } - match = mentionPattern.exec(msg); - } - - return mentionedUsers; - }; - - Utils.DEFAULT_HELP_URL = 'https://www.circuit.com/support'; - - // Symbols that are used to separate words, including white space characters. - Utils.SEPARATORS_PATTERN = '!-/:-@[-^{-~\\s'; - - // Pattern for numbers only (used in Cloud Telephony) - Utils.NUMERIC_PATTERN = '^[0-9]*$'; - - //AlphaNumeric Pattern - Utils.ALPHANUMERIC_PATTERN = '^[a-zA-Z0-9_]*$'; - - // Helper pattern for finding urls. - Utils.URL_SEPARATORS_PATTERN = /(?=[.,;!?]?\s+|[.,;!?]$)/; - - // http, https, ftp url pattern - Utils.URL_PATTERN = /(http|ftp|https):\/\/[\w-]+?((\.|:)[\w-]+?)+?([\w.,@?'^=$%&:\/~+#!\(\)\{\}\[\]-]*[\w@?'^=$%&\/~+#!\(\)\{\}-])?/gim; - - // Matches URL_PATTERN (exact match) - Utils.EXACT_URL_PATTERN = new RegExp('^' + Utils.URL_PATTERN.source + '$', 'i'); - - // starting with www. sans http:// or https:// - Utils.PSEUDO_URL_PATTERN = /www\.+[\w-]+?(\.[\w-]+?)+?([\w.,@?\'^=$%&:\/~+#!\(\)\{\}\[\]-]*[\w@?\'^=$%&\/~+#!\(\)\{\}-])?/gim; - - // Query expression retrieved from ui-bootstrap returning all tabbable elements - Utils.TABBABLE_ELEMENTS_PATTERN = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' + - 'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), ' + - 'textarea:not([disabled]):not([tabindex=\'-1\']), ' + - 'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]'; - - Utils.RICH_TEXT_LINK_CLASS = 'text-linkDarkGreen'; - - /** - * Email address patttern. `/^[_a-z0-9-\+]+(?:\.[_a-z0-9-\+]+)*@[a-z0-9-]+(?:\.[a-z0-9-]+)*(?:\.[a-z][a-z]+)$/im` - * @property EMAIL_ADDRESS_PATTERN - * @type string - * @memberOf Circuit.Utils - * @static - * @final - */ - // Email addresses(es) included in text - Utils.EMAIL_ADDRESS_INCLUDED_PATTERN = /[\w\*\+\-\$\?\^\{!#%&'\/=`|}~]+?(?:\.[\w\*\+\-\$\?\^\{!#%&'\/=`|}~]+?)*?@[a-z\d-]+?(?:\.[a-z\d-]{2,})+/gim; - Utils.EMAIL_ADDRESS_PATTERN = new RegExp('^' + Utils.EMAIL_ADDRESS_INCLUDED_PATTERN.source + '$', 'i'); - - // SSO email domains - if (circuit.isElectron && remote) { - Utils.SSO_EMAIL_DOMAINS_MAPPING = remote.getGlobal('constants').SSO_EMAIL_DOMAINS_MAPPING || []; - Utils.SSO_PATTERN = '[_a-z0-9-]+?@((' + Utils.SSO_EMAIL_DOMAINS_MAPPING.join(')|(') + '))'; - Utils.SSO_EMAIL_ADDRESS_PATTERN = new RegExp(Utils.SSO_PATTERN, 'i'); - } - - // Pattern to match circuit conversation hash - Utils.CIRCUIT_CONVERSATION_HASH_PATTERN = /\#\/(conversation|open|muted)\/[0-9\-a-z]+?(\?((user=(\d)+?)|(item=([0-9\-a-z])+?(\&user=(\d)+?)?)))?$/gim; - - // Pattern to match circuit user, user profile and phone hash - Utils.CIRCUIT_LINKS_HASH_PATTERN = /\#\/(profile|phone|user)\/[0-9\-a-z]+?$/gim; - - // Pattern to match circuit email hash - Utils.CIRCUIT_EMAIL_HASH_PATTERN = new RegExp('\#\/email\/' + Utils.EMAIL_ADDRESS_INCLUDED_PATTERN.source + '$', 'gim'); - - // Phone numbers. Examples: - // 15615551234 - // 1.561.555.1234 - // +15615551234 - // +1-561-555-1234 - // +1(561)555-1234 - Utils.PHONE_PATTERN = /^(\+\s*)?\d+(\s*\(\s*\d+\s*\)\s*\d+)?((\s*-?\s*\d+)*|(\s*\.\s*\d+)*)$/; - - Utils.GNF_PHONE_PATTERN = /^((\+)|(\+\s*\d+(\s*\(\s*\d+\s*\)\s*\d+)?((\s*-?\s*\d+)*|(\s*\.\s*\d+)*)))$/; - - // Prefix Access Code: must start with # or *, followed by digits, *, #, (), -, + or white spaces - // We don't check if parenthesis are properly opened and closed (like in other phone patterns) - Utils.PHONE_PAC = /^([#\*]+[#\*\(\)\-\s\+\d]*)$/; - - Utils.PHONE_DIAL_PATTERN = /^([#\*\(\)\\\/\-\.\s\+\d]*)$/; - - // Same as PHONE_DIAL_PATTERN but with an extension at the end (e.g.: +1 (231) 344-3455 x 1324) - Utils.PHONE_WITH_EXTENSION_PATTERN = /^([#\*\(\)\\\/\-\.\s\+\d]*)(\s*(x|X|ext\.)\s*\d+)$/; - - Utils.matchPhonePattern = function (str) { - if (!str || (typeof str !== 'string')) { - return null; - } - return str.match(Utils.PHONE_PATTERN); - }; - - Utils.matchUrlPattern = function (str) { - if (!str || (typeof str !== 'string')) { - return null; - } - return str.match(Utils.URL_PATTERN) || str.match(Utils.PSEUDO_URL_PATTERN); - }; - - Utils.matchEmailPattern = function (str) { - if (!str || (typeof str !== 'string')) { - return null; - } - return str.match(Utils.EMAIL_ADDRESS_INCLUDED_PATTERN); - }; - - Utils.getEmails = function (str) { - var emails = str && str.split(/[;,]\s*|\s+/i); - - emails = emails && emails.map(function (email) { - var match = email.match(Utils.EMAIL_ADDRESS_INCLUDED_PATTERN); - return match && match[0]; - }).filter(function (email) { - return !!email; - }); - - return emails || []; - }; - - Utils.matchNames = function (str, query, flag) { - if (!str || (typeof str !== 'string') || !query) { - return false; - } - var reName = new RegExp('^' + RegExp.escape(query), flag || 'i'); - return reName.test(str) || str.split(' ').some(function (name) { - return reName.test(name); - }); - }; - - // Compares strings case-insensitive - Utils.compareStrings = function (str1, str2) { - if ((typeof str1 !== 'string') || (typeof str2 !== 'string')) { - return false; - } - var reCompare = new RegExp('^' + RegExp.escape(str1) + '$', 'i'); - return reCompare.test(str2); - }; - - Utils.rstring = function () { - return Math.floor(Math.random() * 1e9).toString(); - }; - - Utils.randomNumber = function (min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }; - - Utils.randomBoolean = function () { - return 0.5 > Math.random(); - }; - - Utils.generateCallId = function () { - return 'WRTC-' + Utils.rstring() + '-' + Utils.rstring(); - }; - - Utils.createTransactionId = function () { - var s = []; - var hexDigits = '0123456789abcdef'; - for (var i = 0; i < 36; i++) { - s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); - } - s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010 - s[19] = hexDigits.substr((s[19] && 0x3) || 0x8, 1);// bits 6-7 of the clock_seq_hi_and_reserved to 01 - s[8] = s[13] = s[18] = s[23] = '-'; - - var id = s.join(''); - return id; - }; - - // Get the legal region ('us', 'uk', 'de') associated to the regionId ('americas', 'apac', 'europe') provided - Utils.getLegalRegion = function (regionId) { - switch (regionId) { - case 'europe': - var languageSplit = (navigator.language || navigator.browserLanguage).split('-'); //supports different browsers (Chrome/IE) - var country = languageSplit.last(); //supports locale with and without country - return country.toLowerCase() === 'de' ? 'de' : 'uk'; - case 'americas': - case 'apac': - return 'us'; - default: - return 'us'; - } - }; - - /** - * Provides browser type and version information. - * @method getBrowserInfo - * @returns {Object} Object with type, version and browser attributes - * @memberof Circuit.Utils - */ - Utils.getBrowserInfo = function () { - if (window.navigator.platform === 'iOS') { - return { - ios: true, - type: 'ios' - }; - } else if (window.navigator.platform === 'Android') { - return { - android: true, - type: 'android' - }; - } else if (window.navigator.platform === 'node') { - return { - node: true, - type: 'node' - }; - } else if (Circuit.isElectron) { - return { - chrome: true, - type: 'chrome' - }; - } else if (window.navigator.platform === 'dotnet') { - return { - dotnet: true, - type: 'dotnet' - }; - } - - var browserData = {}; - var ua = navigator.userAgent.toLowerCase(); - var match = /(msie|trident)(?: |.*rv:)([\d.]+)/.exec(ua) || - /(opera|opr)(?:.*version|)\/([\d.]+)/.exec(ua) || - /(firefox)\/([\d.]+)/.exec(ua) || - /(edge)\/([\d.]+)/.exec(ua) || - /(chrome)\/([\d.]+)/.exec(ua) || - /version\/([\d.]+).*(safari)/.exec(ua) || - /(phantomjs)\/([\d.]+)/.exec(ua) || // Used for unit test (shouldn't impact performance here) - []; - var browser = match[1] || ''; - var version = match[2] || 0; - - // Swap matches for Safari because of inverted user agent - if (version === 'safari') { - browser = match[2]; - version = match[1]; - } else if (browser === 'trident') { - // IE no longer reported as MSIE starting from 11 version - browser = 'msie'; - } else if (browser === 'opr') { - // Correctly define Opera - browser = 'opera'; - } else if (browser === 'phantomjs') { - browser = 'phantomjs'; - } - - browserData[browser] = true; - browserData.type = browser; - browserData.version = version; - - return browserData; - }; - - // Define the possible OS types for Utils.getOSInfo() - var OS_TYPES = [ - {s: 'Windows 10', r: /(Windows.* 10|Windows 10.0|Windows[ _]NT.* 10.0)/}, - {s: 'Windows 8.1', r: /(Windows.* 8.1|Windows[ _]NT.* 6.3)/}, - {s: 'Windows 8', r: /(Windows.* 8|Windows[ _]NT.* 6.2)/}, - {s: 'Windows 7', r: /(Windows.* 7|Windows[ _]NT.* 6.1)/}, - {s: 'Windows Vista', r: /(Windows.* Vista|Windows[ _]NT.* 6.0)/}, - {s: 'Windows Server 2003', r: /(Windows Server 2003|Windows[ _]NT.* 5.2)/}, - {s: 'Windows XP', r: /(Windows[ _]NT.* 5.1|Windows XP)/}, - {s: 'Windows 2000', r: /(Windows[ _]NT.* 5.0|Windows 2000)/}, - {s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/}, - {s: 'Windows 98', r: /(Windows 98|Win98)/}, - {s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/}, - {s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/}, - {s: 'Windows CE', r: /Windows CE/}, - {s: 'Windows 3.11', r: /Windows 3.11|Win16/}, - {s: 'Android', r: /Android/}, - {s: 'Open BSD', r: /Open BSD|OpenBSD/}, - {s: 'Sun OS', r: /Sun OS|SunOS/}, - {s: 'Linux', r: /(Linux|X11)/}, - {s: 'iOS', r: /(iOS|iPhone|iPad|iPod)/}, - {s: 'Mac OS X', r: /Mac OS X/}, - {s: 'Mac OS', r: /(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh|Darwin)/}, - {s: 'QNX', r: /QNX/}, - {s: 'UNIX', r: /UNIX/}, - {s: 'BeOS', r: /BeOS/}, - {s: 'OS/2', r: /OS\/2/}, - {s: 'Search Bot', r: /(Search Bot|nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/} - ]; - - Utils.getOSInfo = function (osVersion) { - if (!osVersion) { - return {type: '', version: '', info: ''}; - } - var info = osVersion; - var type = ''; - var version = ''; - - var inList = OS_TYPES.some(function (os) { - if (os.r.test(osVersion)) { - info = os.s; - return true; - } - }); - - // Get OS version info from navigator - if (inList && /Windows/.test(info)) { - version = /Windows (.*)/.exec(info)[1]; - type = 'Windows'; - } else { - type = info; - - try { - switch (info) { - case 'Mac OS X': - version = /Mac OS X (10[\.\_\d]+)/.exec(osVersion)[1]; - break; - - case 'Android': - version = /Android ([\.\_\d]+)/.exec(osVersion)[1]; - break; - } - } catch (e) { - logger.debug('[Utils]: osVersion does not contain version on it. ', osVersion); - } - } - - return { - type: type, - version: version, - info: info - }; - }; - - Utils.checkUserDomain = function (domain) { - return circuit.isElectron ? - circuit.__server.split(':')[0].endsWith(domain) : window.location.hostname.endsWith(domain); - }; - - /** - * Parses an string with IP+port (IPv4 or IPv6) and returns an object with ipAddress and port properties. - * If parsing is not successful, null is returned. - * Input examples: - * IPv4: '172.20.41.178:3823' - * IPv6: '[2001:db8:a0b:12f0::1]:3422' - */ - Utils.parseIpWithPort = function (ip) { - if (!ip && typeof ip !== 'string') { - return null; - } - if (ip[0] === '[') { - // IPV6+port - var ipv6 = /\[(.*)\]:(\d*)/.exec(ip); // Extract the IPV6 and port (we're not validating the IP address) - if (ipv6 && ipv6.length === 3) { - return { - ipAddress: ipv6[1], - port: ipv6[2] - }; - } - } else if (ip.indexOf(':') > 0) { - var split = ip.split(':'); - if (split.length === 2) { - return { - ipAddress: split[0], - port: split[1] - }; - } - } - return null; - }; - - Utils.getChannelInfo = function (res) { - // Get the local and remote IP addresses and port numbers - var ipl = Utils.parseIpWithPort(res.stat('googLocalAddress')) || {}; - var ipr = Utils.parseIpWithPort(res.stat('googRemoteAddress')) || {}; - var normalizeType = function (type) { - // Chrome sends local and stun instead of host and srflx - switch (type) { - case 'local': - return 'host'; - case 'stun': - return 'srflx'; - default: - return type; - } - }; - return { - ipl: ipl.ipAddress, - ptl: ipl.port, - ipr: ipr.ipAddress, - ptr: ipr.port, - lct: normalizeType(res.stat('googLocalCandidateType')), - rct: normalizeType(res.stat('googRemoteCandidateType')), - tt: res.stat('googTransportType') - }; - }; - - /** - * Get ICE candidate info. For host and srflx candidates, it - * returns the local connection IP address. For relay candidates, it returns - * the related IP address and the client transport type. - * - * @param {type} res - Stat resource containing the candidate info - * @param {type} sdp - Local SDP (needed for relay candidates) - * @param {type} media - Media type (audio or video) - * @param {type} port - Port used by the candidate. We use it to find the relay candidate in the local SDP - * @returns {Object} - Object containing the candidate IP address and the relay transport type (if applicable) - */ - Utils.getIceCandidateInfo = function (res, sdp, media, port) { - var info = {}; - - if (!res || !sdp || !media || !port) { - return info; - } - - var getHostAddressByNetworkId = function (parsedMLine, selectedCandidate) { - var host; - // Find corresponding host candidate - selectedCandidate['network-id'] && parsedMLine.a.some(function (a) { - if (a.field === 'candidate' && a.value && a.value.indexOf('network-id') >= 0) { - var candidate = new circuit.IceCandidate(a.value); - if (candidate.typ === 'host' && candidate['network-id'] === selectedCandidate['network-id']) { - host = candidate.address; - return true; - } - } - }); - return host; - }; - - var parsed = (typeof sdp === 'string') ? circuit.sdpParser.parse(sdp) : sdp; - // Find the selected candidate in the SDP - parsed.m.some(function (m) { - if (m.media === media) { - m.a.some(function (a) { - if (a.field !== 'candidate') { - return; - } - // Only parse the ICE candidate if the port is found in it - if (a.value && a.value.indexOf(port) >= 0) { - var candidate = new circuit.IceCandidate(a.value); - if (String(candidate.port) === port) { - info.value = a.value; - var type = res.stat('candidateType'); - if (type === 'host') { - info.sAddr = info.hAddr = res.stat('ipAddress'); - } else { - info.sAddr = candidate.raddr; - info.hAddr = getHostAddressByNetworkId(m, candidate); - - if (type === 'relayed' || type === 'relay') { - info.tt = candidate.getRelayClientTransportType(); - } - } - return true; - } - } - }); - return true; - } - }); - return info; - }; - - Utils.isEmptyObject = function (obj) { - return Object.keys(obj).length === 0; - }; - - // Get the prototypical base object (__proto__) and optionally update the base - // object with properties set on the object itself. - // Note: Only first-level properties are updated, no recursion. - Utils.getBaseObject = function (obj, updateBaseObj) { - if (!obj || (typeof obj !== 'object')) { - return null; - } - - if (obj instanceof Array) { - return obj; - } - - var base = Object.getPrototypeOf(obj); - - if (Utils.isEmptyObject(base)) { - // obj was constructed directly from Object - return obj; - } - - if (updateBaseObj !== false) { - Object.getOwnPropertyNames(base).forEach(function (name) { - if (typeof base[name] !== 'function' && obj.hasOwnProperty(name)) { - base[name] = obj[name]; - } - }); - } - return base; - }; - - // Get the prototypical base object (__proto__), updates the base object with - // properties set on the object itself, and finally deletes those properties - // from the object itself. - // Note: Only first-level properties are updated, no recursion. - Utils.syncBaseObject = function (obj) { - var base = Utils.getBaseObject(obj, true); - if (!base || base === obj) { - return; - } - Object.getOwnPropertyNames(base).forEach(function (name) { - if (obj.hasOwnProperty(name)) { - delete obj[name]; - } - }); - }; - - - // Flattens prototyped objects, so JSON.stringify will also include the prototype's properties. - Utils.flattenObj = function (obj) { - if (!obj || (typeof obj !== 'object')) { - return obj; - } - - var base = Object.getPrototypeOf(obj); - if (Utils.isEmptyObject(base)) { - // obj was constructed directly from Object - return obj; - } - - var tmp = {}; - for (var key in obj) { - if (typeof obj[key] !== 'function') { - tmp[key] = obj[key]; - } - } - return tmp; - }; - - Utils.getNormalizedFileExtension = function (file) { - var fileName = file && (file.fileName || file.name); - if (!fileName) { - return null; - } - - if ((/audio/i).test(file.mimeType)) { - return 'audio'; - } - - if ((/video/i).test(file.mimeType)) { - return 'video'; - } - - var fileExt = fileName.split('.').pop(); - if (fileExt) { - switch (fileExt.toLowerCase()) { - case 'doc': - case 'docx': - return 'doc'; - case 'ppt': - case 'pptx': - return 'ppt'; - case 'pdf': - return 'pdf'; - case 'xls': - case 'xlsx': - return 'xls'; - case 'gz': - case 'zip': - return 'zip'; - case 'html': - return 'html'; - case 'csv': - return 'csv'; - case 'png': - return 'png'; - case 'jpg': - case 'jpeg': - return 'jpg'; - /* need to add new 'svg' icon - case 'svg': - return 'svg'; - */ - /* need to add new 'bmp' icon - case 'bmp': - return 'bmp'; - */ - case 'log': - return 'log'; - case 'flv': - case 'mp4': - case 'ogv': - case 'webm': - return 'video'; - case 'wav': - case 'mp3': - case 'ogg': - return 'audio'; - } - } - - return 'def'; - }; - - Utils.getImageFileExtension = function (file) { - if (file.type) { - switch (file.type.toLowerCase()) { - case 'image/png': - return 'png'; - case 'image/jpeg': - return 'jpg'; - case 'image/gif': - return 'gif'; - case 'image/bmp': - return 'bmp'; - case 'image/svg+xml': - return 'svg'; - } - } - return null; - }; - //CQ:CQ00283052 - Utils.getMimeTypes = function (fileName) { - if (!fileName) { - return null; - } - - var fileExt = fileName.split('.').pop(); - if (fileExt) { - switch (fileExt.toLowerCase()) { - case 'xlsx': - return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; - case 'xltx': - return 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'; - case 'potx': - return 'application/vnd.openxmlformats-officedocument.presentationml.template'; - case 'ppsx': - return 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'; - case 'pptx': - return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; - case 'sldx': - return 'application/vnd.openxmlformats-officedocument.presentationml.slide'; - case 'docx': - return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; - case 'dotx': - return 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'; - case 'xlam': - return 'application/vnd.ms-excel.addin.macroEnabled.12'; - case 'xlsb': - return 'application/vnd.ms-excel.sheet.binary.macroEnabled.12'; - } - } - - return 'application/octet-stream'; - }; - - /** - * Utility function to escape text as html, but keep line changes. - * @method textToHtmlEscaped - * @param {String} str Text content to escape - * @param {Boolean} [spaceForNewlines] If true, newlines (\n) are converted to   instead of <br> - * @returns {String} Escaped string - * @example - * Circuit.Utils.textToHtmlEscaped('<b>bold</b>\ntest'); - * // returns <b>bold</b><br>test - * - * Circuit.Utils.textToHtmlEscaped('<b>bold</b>\ntest', true); - * // returns <b>bold</b> test - */ - Utils.textToHtmlEscaped = function (str, handleTextNewLines) { // Method to escape text as html but keep line changes - if (!str) { - return ''; - } - var newLinesReplace = handleTextNewLines ? ' ' : '<br>'; // In selector we want spaces instead of new \n or \r - return str - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/\r\n|\r|\n/gm, newLinesReplace); - }; - - Utils.escapedHtmlToText = function (str) { // Method to transform escaped html to text - if (!str) { - return ''; - } - return str - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, '\'') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/ /g, ' ') - .replace(/<br\/?>/g, '\r\n'); - }; - - Utils.stripParenthesis = function (token) { - token = token.trim();//linkified text adds empty characters at the end of token - var openingPar = (token.match(/\(/g) || []); - var closingPar = (token.match(/\)/g) || []); - var countParenOnStart = 0; - while (token[countParenOnStart] === '(') { - countParenOnStart++; - } - - return token.slice(countParenOnStart, token.length - Math.abs(((openingPar.length - countParenOnStart) - closingPar.length))); - }; - - - function linkifyHtml(content) { - if (!content) { - return ''; - } - var inputDOM = document.createElement('div'); - var linkifiedDOM = document.createElement('div'); - // After optimizing sanitization we don't need to (.replace(/(<br\/>|<hr\/>)/g, '\n');) - // Need to replace $ chars with $$ so they are displayed correctly ($ is an escape char so if we have $$ only one is displayed) - content = content.replace(/\$/gm, '$$$$'); - // Add a link to user profile in span with mention, ignore all other spans that can be inside the mention - var isReplace = 0; - content = content.replace(/(<span.*?>)|(<\/span>)/gi, function (match, openTag, closeTag) { - if (openTag) { - if (openTag.match(/class="mention"/i)) { - return match.replace(/.*abbr="(.*?)".*?>/gi, '<span class="mention" abbr="$1"><a href="#/profile/$1">'); - } else { - isReplace++; - return openTag; - } - } - if (closeTag) { - if (!isReplace) { - return '</a></span>'; - } else { - isReplace--; - return closeTag; - } - } - }); - - //convert emojis - content = Emoji.convertEmojis(content); - - inputDOM.innerHTML = content; - return linkifyNode(inputDOM, linkifiedDOM).innerHTML.replace(/ /gi, ' '); - } - - function getOrigin() { - return circuit.isElectron ? ('https://' + circuit.__server) : window.location && window.location.origin; - } - - function linkifyText(text) { - text = text.replace(/\xA0| |\x0A|\x0D/gim, ' '); - var LT = String.fromCharCode(17); // Temporary mapping using non-printable ascii code x11 (DEC 17) for char "<" replacements - var GT = String.fromCharCode(18); // Temporary mapping using non-printable ascii code x12 (DEC 18) for char ">" replacements - var QUOT = String.fromCharCode(19); // Temporary mapping using non-printable ascii code x13 (DEC 19) for char "'" replacements - - var checkForCircuitLink = function (match) { - var target = ''; - var rel = ''; - var origin = getOrigin(); - var url = Utils.isCircuitLink(origin, match); - if (!url) { - url = match; - // Make sure that protocol is specified - if (!/^(?:http|https|ftp):\/\//.exec(match)) { - url = 'http://' + url; - } - target = ' target=' + QUOT + '_blank' + QUOT; - rel = ' rel=' + QUOT + 'noopener noreferrer' + QUOT; - } - return LT + 'a href=' + QUOT + url + QUOT + target + rel + GT + match + LT + '/a' + GT; - }; - - // Splits text on white spaces or punctuation marks with white spaces. Use Positive Lookahead to keep characters in place. - var tokens = text.split(Utils.URL_SEPARATORS_PATTERN); - for (var i = 0; i < tokens.length; i++) { - // order of replacements matters. - var token = tokens[i]; - var strippedText = Utils.stripParenthesis(token); - var linkifiedText = strippedText; - // Perform simply search at first, in order not to use more complex regular expressions if it is not needed. - if (linkifiedText.search(/http|ftp|https/gim) >= 0 && linkifiedText.search(Utils.URL_PATTERN) >= 0) { - linkifiedText = linkifiedText.replace(Utils.URL_PATTERN, checkForCircuitLink); - } else if (linkifiedText.search(/www\./gim) >= 0 && linkifiedText.search(Utils.PSEUDO_URL_PATTERN) >= 0) { - linkifiedText = linkifiedText.replace(Utils.PSEUDO_URL_PATTERN, checkForCircuitLink); - } else if (linkifiedText.search(/\S@\S/gim) >= 0 && linkifiedText.search(Utils.EMAIL_ADDRESS_INCLUDED_PATTERN) >= 0) { - linkifiedText = linkifiedText.replace(Utils.EMAIL_ADDRESS_INCLUDED_PATTERN, LT + 'a href=' + QUOT + 'mailto:$&' + QUOT + ' target=' + QUOT + '_blank' + QUOT + ' rel=' + QUOT + 'noopener noreferrer' + QUOT + GT + '$&' + LT + '/a' + GT); - } - - token = token.replace(strippedText, linkifiedText); - tokens[i] = token; - } - - text = tokens.join(''); - - // This temporary replacement using the non-printable chars is done in order to - // exclude the chars used to compose an href tag (<,>,') from the html escape process when in plain text mode. - return Utils.textToHtmlEscaped(text) - .replace(/\x11/gi, '<') // Linkify was done, replace them back - .replace(/\x12/gi, '>') - .replace(/\x13/gi, '\''); - } - - function linkifyNode(startNode, linkifiedDOM) { // Node traversing method in order to find text nodes and linkify them - if (!startNode || !startNode.childNodes || !startNode.childNodes.length) { - return startNode; - } - - var currentNode; - for (var i = 0, len = startNode.childNodes.length; i < len; i++) { - currentNode = startNode.childNodes[i]; - switch (currentNode.nodeType) { - case 1: // ELEMENT_NODE - if (currentNode.nodeName === 'A') { - // Add target="_blank" if not a Circuit link. SDK can send a tag without target. - var origin = circuit.isElectron ? circuit.__server : window.location && window.location.origin; - if (!Utils.isCircuitLink(origin, currentNode.href)) { - currentNode.target = '_blank'; - currentNode.rel = 'noopener noreferrer'; - } - } - linkifyNode(currentNode, linkifiedDOM); - break; - case 3: // TEXT_NODE - linkifiedDOM.innerHTML = linkifyText(currentNode.textContent); - i += linkifiedDOM.childNodes.length - 1; - while (linkifiedDOM.childNodes.length) { - startNode.insertBefore(linkifiedDOM.childNodes[0], currentNode); - } - startNode.removeChild(currentNode); - len = startNode.childNodes.length; - } - } - return startNode; - } - - Utils.linkifyContent = function (content, contentType) { - if (Utils.isMobile()) { - // This function is not applicable for mobile clients - return ''; - } - content = Emoticons.addEmoticonsAltAttr(content); - - switch (contentType) { - case 'RICH': - return linkifyHtml(content); // When content is html, parsing is done node-wise, and every text nodes is parsed for links - case 'PLAIN': - return linkifyText(content); // When content is plain text, html tags are escaped and content is linkified as text - } - return content; - }; - - Utils.invalidFileTypes = ['ade', 'adp', 'app', 'asp', 'bas', 'bat', 'cer', 'chm', 'cmd', 'cnt', 'com', 'cpl', 'crt', 'csh', 'der', 'exe', 'fxp', 'gadget', 'hlp', 'hpj', 'hta', 'inf', 'ins', 'isp', 'its', 'js', 'jse', 'ksh', 'lnk', 'mad', 'maf', 'mag', 'mam', 'maq', 'mar', 'mas', 'mat', 'mau', 'mav', 'maw', 'mda', 'mdb', 'mde', 'mdt', 'mdw', 'mdz', 'msc', 'msh', 'msh1', 'msh1xml', 'msh2', 'msh2xml', 'mshxml', 'msi', 'msp', 'mst', 'ops', 'osd', 'pcd', 'pif', 'plg', 'prf', 'prg', 'ps1', 'ps1xml', 'ps2', 'ps2xml', 'psc1', 'psc2', 'pst', 'reg', 'scf', 'scr', 'sct', 'shb', 'shs', 'tmp', 'url', 'vb', 'vbe', 'vbp', 'vbs', 'vsmacros', 'vsw', 'ws', 'wsc', 'wsf', 'wsh', 'xnk']; - - /** - * Check if container has a scrollbar - */ - Utils.hasScrollbar = function (container) { - if (container && container.length > 0) { - var elm = container[0]; - return !!elm && (elm.scrollHeight > elm.clientHeight); - } - return false; - }; - /** - * Returns true if child element is either equal the ancestor element or a nested descendant of the ancestor element. - */ - Utils.isDescendantOrEqual = function (child, ancestor) { - if (child === ancestor) { - return true; - } - var _parent = child; - while (_parent) { - _parent = _parent.parentElement; - if (_parent === ancestor) { - return true; - } - } - return false; - }; - - Utils.isSupportedImage = function (mimeType) { - var regExp = /^image\/(jpeg|gif|bmp|png|svg\+xml)$/i; - return regExp.test(mimeType); // Accept only image/jpeg, image/gif, image/bmp, image/png and image/svg+xml MIME types - }; - - /** - * We handle video types that we can play in embeddedPlayer. - * Handling done based on MIME type. - * Note: Probably more types should be added on the future. - */ - Utils.isVideoSupportedByBrowser = function (mimeType) { - var regExp = /^video\/(quicktime|mp4|webm)$/i; - return regExp.test(mimeType); - }; - - /** - * Returns a copy of the source object - */ - Utils.shallowCopy = function (src) { - if (!src || (typeof src !== 'object')) { - return src; - } - - if (Array.isArray(src)) { - return src.slice(); - } - - var flattenSrc = Utils.flattenObj(src); - var obj = {}; - for (var key in flattenSrc) { - if (flattenSrc.hasOwnProperty(key)) { - obj[key] = flattenSrc[key]; - } - } - return obj; - }; - - /** - * Returns the normalized locale string ('en-US' to 'EN_US' or 'de-DE' to 'DE_DE' or 'DE_DE' to 'DE_DE') or - * undefined if the parameter is not valid - */ - Utils.normalizeLocaleProto = function (locale) { - var proto; - if (locale && locale.length >= 5) { - proto = locale.replace(/-/g, '_').toUpperCase(); - } - return proto; - }; - - /** - * Returns the normalized locale string ('EN_US' to 'en-US' or 'DE_DE' to 'de-DE' or 'de-DE' to 'de-DE') or - * undefined if the parameter is not valid - */ - Utils.normalizeLocale = function (proto) { - var locale; - if (proto && proto.length >= 5) { - locale = proto.substr(0, 2).toLowerCase() + proto.substr(2, proto.length - 2).replace(/_/g, '-'); - } - return locale; - }; - - /** - * Calculates local time based on a time zone offset (optional) - */ - Utils.getLocalTime = function (timeZoneOffset) { - var currentDate = new Date(); - if (timeZoneOffset === null || timeZoneOffset === undefined) { // 0 value is a possible value as well - return currentDate.getTime(); - } - // Include my offset in calc as well - currentDate.setTime(currentDate.getTime() + (currentDate.getTimezoneOffset() - timeZoneOffset) * 60 * 1000); - return currentDate.getTime(); - }; - - /** - * Returns new calculated timestamp instead of provided one when delayMS (milliseconds) is correctly set - */ - Utils.delayOrTimestamp = function (delayMS, timestamp) { - return delayMS > 0 ? Date.now() + delayMS : timestamp; - }; - - /** - * Build the attachmentMetaData array used in the client API from a given list of uploaded files - */ - Utils.buildAttachmentMetaData = function (files) { - var buildMetaData = function (attachment) { - return { - fileId: attachment.fileId, - fileName: attachment.fileName, - itemId: attachment.itemId, - mimeType: attachment.mimeType, - size: attachment.size - }; - }; - - var attachments = []; - var externalAttachments = []; - files && files.forEach(function (file) { - // thirdparty files are handled differently - if (file.isThirdParty) { - var inner = buildMetaData(file); - externalAttachments.push({ - type: file.provider, - attachment: inner, - downloadLocation: file.url, - previewLocation: file.url, - shareLinkToken: file.shareLinkToken - }); - return; - } - - attachments.push(buildMetaData(file)); - if (file.thumbnail) { - attachments.push(buildMetaData(file.thumbnail)); - } - }); - - return { - attachments: attachments, - externalAttachments: externalAttachments - }; - }; - - /* - * Pseudo-classical OOP pattern - * Child and Parent must be constructor function - */ - Utils.inherit = function (Child, Parent) { - function F() {} - F.prototype = Parent.prototype; - Child.prototype = new F(); - Child.prototype.constructor = Child; - Child.parent = Parent.prototype; - }; - - - /** - * Checks if a given timestamp is older than a given day period - */ - Utils.isOlderThan = function (timeToCheck, daysOlder) { - if (!timeToCheck) { - return true; - } - var ONE_DAY = 86400000; // One day in milliseconds - daysOlder = daysOlder || 1; - return (Date.now() - timeToCheck) > (daysOlder * ONE_DAY); - }; - - Utils.bytesToSize = function (bytes, precision) { - // Make sure we are working with an integer - bytes = parseInt(bytes, 10); - if (!bytes) { return '0 Bytes'; } - var k = 1024; - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; - var i = Math.floor(Math.log(bytes) / Math.log(k)); - return Number((bytes / Math.pow(k, i)).toPrecision(precision || 3)) + ' ' + sizes[i]; - }; - - // Check if Mac OS, default false - Utils.isMacOs = window.navigator && window.navigator.userAgent ? (window.navigator.userAgent.indexOf('Mac') !== -1 || window.navigator.userAgent.indexOf('Darwin') !== -1) : false; - - // Check if Windows, default false - Utils.isWindowsOs = window.navigator && window.navigator.platform ? window.navigator.platform.substring(0, 3).toLowerCase() === 'win' : false; - - - Utils.daysBetween = function (firstDate, secondDate) { - // ensure that both objects are dates, and that the first date precedes the second - if (firstDate instanceof Date && secondDate instanceof Date) { - var MS_ONE_DAY = 1000 * 60 * 60 * 24; - return Math.floor(Math.abs(secondDate.getTime() - firstDate.getTime()) / MS_ONE_DAY); - } - }; - - /** - * Function used to determine if client is mobile - */ - Utils.isMobile = function () { - return (window.navigator.platform === 'iOS' || window.navigator.platform === 'Android'); - }; - - /** - * Format PIN to readable string: - * tenant PIN, space, session PIN separated every SESSION_PIN_CHUNK_LEN - * every session PIN chunck should be smaller than SESSION_PIN_CHUNK_MIN_LEN - * set noDelimiters to true if the string shouldn't have any delimiters (spaces) - */ - Utils.formatPIN = function (tenantPIN, sessionPIN, noDelimiters) { - if (!sessionPIN) { - return ''; - } - if (noDelimiters) { - return (tenantPIN || '') + sessionPIN + '#'; - } - - // initial block chunck length - var SESSION_PIN_FIRST_CHUNK_LEN = 4; - // session PIN chunck length - var SESSION_PIN_CHUNK_LEN = 3; - // session PIN chunck minimum length - var SESSION_PIN_CHUNK_MIN_LEN = 2; - - var pin = []; - var chunk; - // during PIN migration assure that format always looks the same for - // new (10 digit access code, no TIN) and old (TIN + 6 digit access code) - var chunkLen = tenantPIN ? SESSION_PIN_CHUNK_LEN : SESSION_PIN_FIRST_CHUNK_LEN; - for (var i = 0; i < sessionPIN.length; i += chunkLen) { - if (i > 0) { - chunkLen = SESSION_PIN_CHUNK_LEN; - } - chunk = sessionPIN.substr(i, chunkLen); - if (chunk.length >= SESSION_PIN_CHUNK_MIN_LEN || pin.length === 0) { - pin.push(chunk); - } else { - pin[pin.length - 1] += chunk; - } - } - if (tenantPIN) { - pin.unshift(tenantPIN); - } - pin.push('#'); - return pin.join(' '); - }; - - Utils.CountriesArray = [ - { name: 'res_countryName_Andorra', code: 'AD' }, - { name: 'res_countryName_United_Arab_Emirates', code: 'AE' }, - { name: 'res_countryName_Afghanistan', code: 'AF' }, - { name: 'res_countryName_Antigua_and_Barbuda', code: 'AG' }, - { name: 'res_countryName_Anguilla', code: 'AI' }, - { name: 'res_countryName_Albania', code: 'AL' }, - { name: 'res_countryName_Armenia', code: 'AM' }, - { name: 'res_countryName_Netherland_Antilles', code: 'AN' }, - { name: 'res_countryName_Angola', code: 'AO' }, - { name: 'res_countryName_Antarctic', code: 'AQ' }, - { name: 'res_countryName_Argentina', code: 'AR' }, - { name: 'res_countryName_American_Samoa', code: 'AS' }, - { name: 'res_countryName_Austria', code: 'AT' }, - { name: 'res_countryName_Australia', code: 'AU' }, - { name: 'res_countryName_Aruba', code: 'AW' }, - { name: 'res_countryName_Azerbaijan', code: 'AZ' }, - { name: 'res_countryName_Bosnia_and_Herzegovina', code: 'BA' }, - { name: 'res_countryName_Barbados', code: 'BB' }, - { name: 'res_countryName_Bangladesh', code: 'BD' }, - { name: 'res_countryName_Belgium', code: 'BE' }, - { name: 'res_countryName_Burkina_Faso', code: 'BF' }, - { name: 'res_countryName_Bulgaria', code: 'BG' }, - { name: 'res_countryName_Bahrain', code: 'BH' }, - { name: 'res_countryName_Burundi', code: 'BI' }, - { name: 'res_countryName_Benin', code: 'BJ' }, - { name: 'res_countryName_Bermuda', code: 'BM' }, - { name: 'res_countryName_Brunei_Darussalam', code: 'BN' }, - { name: 'res_countryName_Bolivia', code: 'BO' }, - { name: 'res_countryName_Brazil', code: 'BR' }, - { name: 'res_countryName_Bahamas', code: 'BS' }, - { name: 'res_countryName_Bhutan', code: 'BT' }, - { name: 'res_countryName_Botswana', code: 'BW' }, - { name: 'res_countryName_Belarus', code: 'BY' }, - { name: 'res_countryName_Belize', code: 'BZ' }, - { name: 'res_countryName_Canada', code: 'CA' }, - { name: 'res_countryName_Dem_Republic_Congo', code: 'CD' }, - { name: 'res_countryName_Central_African_Republik', code: 'CF' }, - { name: 'res_countryName_Congo', code: 'CG' }, - { name: 'res_countryName_Switzerland', code: 'CH' }, - { name: 'res_countryName_Cote_d_Ivoire', code: 'CI' }, - { name: 'res_countryName_Chile', code: 'CL' }, - { name: 'res_countryName_Cameroon', code: 'CM' }, - { name: 'res_countryName_China_Peoples_Republic', code: 'CN' }, - { name: 'res_countryName_Colombia', code: 'CO' }, - { name: 'res_countryName_Costa_Rica', code: 'CR' }, - { name: 'res_countryName_Cuba', code: 'CU' }, - { name: 'res_countryName_Cap_Verde', code: 'CV' }, - { name: 'res_countryName_Cyprus', code: 'CY' }, - { name: 'res_countryName_Czech_Republik', code: 'CZ' }, - { name: 'res_countryName_Germany', code: 'DE' }, - { name: 'res_countryName_Djibouti', code: 'DJ' }, - { name: 'res_countryName_Denmark', code: 'DK' }, - { name: 'res_countryName_Dominica', code: 'DM' }, - { name: 'res_countryName_Dominican_Republik', code: 'DO' }, - { name: 'res_countryName_Algeria', code: 'DZ' }, - { name: 'res_countryName_Ecuador', code: 'EC' }, - { name: 'res_countryName_Estonia', code: 'EE' }, - { name: 'res_countryName_Egypt', code: 'EG' }, - { name: 'res_countryName_Westsahara', code: 'EH' }, - { name: 'res_countryName_Eritrea', code: 'ER' }, - { name: 'res_countryName_Spain', code: 'ES' }, - { name: 'res_countryName_Ethiopia', code: 'ET' }, - { name: 'res_countryName_Finland', code: 'FI' }, - { name: 'res_countryName_Fiji', code: 'FJ' }, - { name: 'res_countryName_Falkland_Islands', code: 'FK' }, - { name: 'res_countryName_Micronesia_Fed_States', code: 'FM' }, - { name: 'res_countryName_Faroer', code: 'FO' }, - { name: 'res_countryName_France', code: 'FR' }, - { name: 'res_countryName_Gaboon', code: 'GA' }, - { name: 'res_countryName_United_Kingdom', code: 'GB' }, - { name: 'res_countryName_United_Kingdom', code: 'UK' }, - { name: 'res_countryName_Grenada', code: 'GD' }, - { name: 'res_countryName_Georgia', code: 'GE' }, - { name: 'res_countryName_French_Guayana', code: 'GF' }, - { name: 'res_countryName_Ghana', code: 'GH' }, - { name: 'res_countryName_Gibraltar', code: 'GI' }, - { name: 'res_countryName_Greenland', code: 'GL' }, - { name: 'res_countryName_Gambia', code: 'GM' }, - { name: 'res_countryName_Guinea', code: 'GN' }, - { name: 'res_countryName_Guadeloupe', code: 'GP' }, - { name: 'res_countryName_Equatorial_Guinea', code: 'GQ' }, - { name: 'res_countryName_Greece', code: 'GR' }, - { name: 'res_countryName_South_Georgia_South_Sandwichisland', code: 'GS' }, - { name: 'res_countryName_Guatemala', code: 'GT' }, - { name: 'res_countryName_Guam', code: 'GU' }, - { name: 'res_countryName_Guinea_Bissau', code: 'GW' }, - { name: 'res_countryName_Guyana', code: 'GY' }, - { name: 'res_countryName_Hong_Kong', code: 'HK' }, - { name: 'res_countryName_Honduras', code: 'HN' }, - { name: 'res_countryName_Croatia', code: 'HR' }, - { name: 'res_countryName_Haiti', code: 'HT' }, - { name: 'res_countryName_Hungary', code: 'HU' }, - { name: 'res_countryName_Canary_Island', code: 'IC' }, - { name: 'res_countryName_Indonesia', code: 'ID' }, - { name: 'res_countryName_Ireland', code: 'IE' }, - { name: 'res_countryName_Israel', code: 'IL' }, - { name: 'res_countryName_India', code: 'IN' }, - { name: 'res_countryName_British_Indian_Ocean_Territory', code: 'IO' }, - { name: 'res_countryName_Iraq', code: 'IQ' }, - { name: 'res_countryName_Iran_Islamic_Republic', code: 'IR' }, - { name: 'res_countryName_Iceland', code: 'IS' }, - { name: 'res_countryName_Italy', code: 'IT' }, - { name: 'res_countryName_Jamaica', code: 'JM' }, - { name: 'res_countryName_Jordan', code: 'JO' }, - { name: 'res_countryName_Japan', code: 'JP' }, - { name: 'res_countryName_Kenya', code: 'KE' }, - { name: 'res_countryName_Kyrgyzstan', code: 'KG' }, - { name: 'res_countryName_Cambodia', code: 'KH' }, - { name: 'res_countryName_Kiribati', code: 'KI' }, - { name: 'res_countryName_Comoros', code: 'KM' }, - { name: 'res_countryName_St_Kitts_and_Nevis', code: 'KN' }, - { name: 'res_countryName_Korea_Dem_Peoples_Rep_North_Korea', code: 'KP' }, - { name: 'res_countryName_Korea_South', code: 'KR' }, - { name: 'res_countryName_Kuwait', code: 'KW' }, - { name: 'res_countryName_Cayman_Islands', code: 'KY' }, - { name: 'res_countryName_Kazakhstan', code: 'KZ' }, - { name: 'res_countryName_Laos_Democratic_Peoples_Republic', code: 'LA' }, - { name: 'res_countryName_Lebanon', code: 'LB' }, - { name: 'res_countryName_Saint_Lucia', code: 'LC' }, - { name: 'res_countryName_Liechtenstein', code: 'LI' }, - { name: 'res_countryName_Sri_Lanka', code: 'LK' }, - { name: 'res_countryName_Liberia', code: 'LR' }, - { name: 'res_countryName_Lesotho', code: 'LS' }, - { name: 'res_countryName_Lithuania', code: 'LT' }, - { name: 'res_countryName_Luxembourg', code: 'LU' }, - { name: 'res_countryName_Latvia', code: 'LV' }, - { name: 'res_countryName_Libya_Libyan_Arabian_Dschamahirija', code: 'LY' }, - { name: 'res_countryName_Morocco', code: 'MA' }, - { name: 'res_countryName_Monaco', code: 'MC' }, - { name: 'res_countryName_Moldova_Republic', code: 'MD' }, - { name: 'res_countryName_Montenegro', code: 'ME' }, - { name: 'res_countryName_Madagascar', code: 'MG' }, - { name: 'res_countryName_Marshall_Islands', code: 'MH' }, - { name: 'res_countryName_Mazedonia_former_Rep_of_Yugoslavia', code: 'MK' }, - { name: 'res_countryName_Mali', code: 'ML' }, - { name: 'res_countryName_Myanmar_Burma', code: 'MM' }, - { name: 'res_countryName_Mongolia', code: 'MN' }, - { name: 'res_countryName_Macau', code: 'MO' }, - { name: 'res_countryName_Northern_Marians', code: 'MP' }, - { name: 'res_countryName_Martinique', code: 'MQ' }, - { name: 'res_countryName_Mauretania', code: 'MR' }, - { name: 'res_countryName_Montserrat', code: 'MS' }, - { name: 'res_countryName_Malta', code: 'MT' }, - { name: 'res_countryName_Mauritius', code: 'MU' }, - { name: 'res_countryName_Maledives', code: 'MV' }, - { name: 'res_countryName_Malawi', code: 'MW' }, - { name: 'res_countryName_Mexico', code: 'MX' }, - { name: 'res_countryName_Malaysia', code: 'MY' }, - { name: 'res_countryName_Mozambique', code: 'MZ' }, - { name: 'res_countryName_Namibia', code: 'NA' }, - { name: 'res_countryName_New_Caledonia', code: 'NC' }, - { name: 'res_countryName_Niger', code: 'NE' }, - { name: 'res_countryName_Norfolk_Islands', code: 'NF' }, - { name: 'res_countryName_Nigeria', code: 'NG' }, - { name: 'res_countryName_Nicaragua', code: 'NI' }, - { name: 'res_countryName_Netherlands', code: 'NL' }, - { name: 'res_countryName_Norway', code: 'NO' }, - { name: 'res_countryName_Nepal', code: 'NP' }, - { name: 'res_countryName_Nauru', code: 'NR' }, - { name: 'res_countryName_New_Zealand', code: 'NZ' }, - { name: 'res_countryName_Oman', code: 'OM' }, - { name: 'res_countryName_Panama', code: 'PA' }, - { name: 'res_countryName_Peru', code: 'PE' }, - { name: 'res_countryName_French_Polynesia', code: 'PF' }, - { name: 'res_countryName_Papua_New_Guinea', code: 'PG' }, - { name: 'res_countryName_Philippines', code: 'PH' }, - { name: 'res_countryName_Pakistan', code: 'PK' }, - { name: 'res_countryName_Poland', code: 'PL' }, - { name: 'res_countryName_Saint_Pierre_and_Miquelon', code: 'PM' }, - { name: 'res_countryName_Pitcairn_Island', code: 'PN' }, - { name: 'res_countryName_Puerto_Rico', code: 'PR' }, - { name: 'res_countryName_Palestine_regions_occupied', code: 'PS' }, - { name: 'res_countryName_Portugal', code: 'PT' }, - { name: 'res_countryName_Palau', code: 'PW' }, - { name: 'res_countryName_Paraguay', code: 'PY' }, - { name: 'res_countryName_Quatar', code: 'QA' }, - { name: 'res_countryName_Reunion', code: 'RE' }, - { name: 'res_countryName_Romania', code: 'RO' }, - { name: 'res_countryName_Russian_Federation', code: 'RU' }, - { name: 'res_countryName_Rwanda', code: 'RW' }, - { name: 'res_countryName_Saudi_Arabia', code: 'SA' }, - { name: 'res_countryName_Solomonen', code: 'SB' }, - { name: 'res_countryName_Seychelles', code: 'SC' }, - { name: 'res_countryName_Sudan', code: 'SD' }, - { name: 'res_countryName_Sweden', code: 'SE' }, - { name: 'res_countryName_Singapore', code: 'SG' }, - { name: 'res_countryName_Saint_Helena', code: 'SH' }, - { name: 'res_countryName_Slovenia', code: 'SI' }, - { name: 'res_countryName_Spitzbergen', code: 'SJ' }, - { name: 'res_countryName_Slovakia', code: 'SK' }, - { name: 'res_countryName_Sierra_Leone', code: 'SL' }, - { name: 'res_countryName_San_Marino', code: 'SM' }, - { name: 'res_countryName_Senegal', code: 'SN' }, - { name: 'res_countryName_Somalia', code: 'SO' }, - { name: 'res_countryName_Suriname', code: 'SR' }, - { name: 'res_countryName_South_Sudan_Republic_of', code: 'SS' }, - { name: 'res_countryName_Sao_Tome_and_Principe', code: 'ST' }, - { name: 'res_countryName_El_Salvador', code: 'SV' }, - { name: 'res_countryName_Syria_Arabian_Republic', code: 'SY' }, - { name: 'res_countryName_Swaziland', code: 'SZ' }, - { name: 'res_countryName_Turks_and_Caicos_Islands', code: 'TC' }, - { name: 'res_countryName_Chad', code: 'TD' }, - { name: 'res_countryName_Togo', code: 'TG' }, - { name: 'res_countryName_Thailand', code: 'TH' }, - { name: 'res_countryName_Tajikstan', code: 'TJ' }, - { name: 'res_countryName_Tokelau', code: 'TK' }, - { name: 'res_countryName_Timor_Leste', code: 'TL' }, - { name: 'res_countryName_Turkmenistan', code: 'TM' }, - { name: 'res_countryName_Tunisia', code: 'TN' }, - { name: 'res_countryName_Tonga', code: 'TO' }, - { name: 'res_countryName_Turkey', code: 'TR' }, - { name: 'res_countryName_Trinidad_and_Tobago', code: 'TT' }, - { name: 'res_countryName_Tuvalu', code: 'TV' }, - { name: 'res_countryName_Taiwan', code: 'TW' }, - { name: 'res_countryName_Tanzania_United_Republic', code: 'TZ' }, - { name: 'res_countryName_Ukraine', code: 'UA' }, - { name: 'res_countryName_Uganda', code: 'UG' }, - { name: 'res_countryName_Minor_American_Oversea_Islands', code: 'UM' }, - { name: 'res_countryName_United_States_of_America', code: 'US' }, - { name: 'res_countryName_Uruguay', code: 'UY' }, - { name: 'res_countryName_Uzbekistan', code: 'UZ' }, - { name: 'res_countryName_Vatican_City', code: 'VA' }, - { name: 'res_countryName_Saint_Vincent_and_Grenadines', code: 'VC' }, - { name: 'res_countryName_Venezuela', code: 'VE' }, - { name: 'res_countryName_British_Virgin_Islands', code: 'VG' }, - { name: 'res_countryName_American_Virgin_Islands', code: 'VI' }, - { name: 'res_countryName_Vietnam', code: 'VN' }, - { name: 'res_countryName_Vanuatu', code: 'VU' }, - { name: 'res_countryName_Wallis_and_Futuna', code: 'WF' }, - { name: 'res_countryName_Samoa', code: 'WS' }, - { name: 'res_countryName_Kosovo', code: 'XK' }, - { name: 'res_countryName_Serbia', code: 'XS' }, - { name: 'res_countryName_Yemen', code: 'YE' }, - { name: 'res_countryName_Mayotte', code: 'YT' }, - { name: 'res_countryName_South_Africa', code: 'ZA' }, - { name: 'res_countryName_Zambia', code: 'ZM' }, - { name: 'res_countryName_Zimbabwe', code: 'ZW' } - ]; - - Utils.CountryCodeToCountryNameResourceMap = {}; - - Utils.CountriesArray.forEach(function (country) { - Utils.CountryCodeToCountryNameResourceMap[country.code] = country.name; - }); - - /** - * Function that tests if a given url is an accepted Circuit link so it can be opened within circuit - * Currently accepted links are: - * - {host}/#/conversation/{convId} - * - {host}/#/conversation/{convId}?user={userId} - * - {host}/#/conversation/{convId}?item={itemId} - * - {host}/#/conversation/{convId}?item={itemId}&user={userId} - * - same for /open and /muted - * @param {String} host : The host to test against, with or without protocol. If omitted, http is assumed, e.g. https://eu.yourcircuit.com/ . Ending '/' is mandatory. - * @param {String} link : The link to test if it belongs to the given Circuit host - * @returns {String} : the relative link to this domain (with the domain stripped so we can navigate internally) or false; - */ - Utils.isCircuitLink = function (host, link) { - var HTTPS = 'https://'; - var PARTNER_SUBDOMAIN = 'partner.'; - if (!host || !link) { - return; - } - // Test Electron Circuit links (look like file://...index.html#/...) - if (circuit.isElectron && link.search(/^file:\/\/\S+?\.html#\/\S+?$/i) === 0) { - return true; - } - if (link.startsWith('#/')) { // relative links - return true; - } - var protocolPattern = /^(?:http|https|ftp):\/\//; - // Normalization of params - failsafe - if (!protocolPattern.exec(host)) { - host = HTTPS + host; - } - if (!host.endsWith('\/')) { - host = host + '\/'; - } - if (!protocolPattern.exec(link)) { - link = HTTPS + link; - } - if (!link.startsWith(host)) { - // https://host.com and https://partner.host.com systems are the same, - // it means that the links should lead to the same host which user is logged into (ANS-50527) - var partnerDomain = HTTPS + PARTNER_SUBDOMAIN; - var linkDomain = host.startsWith(partnerDomain) ? host.replace(partnerDomain, HTTPS) : - host.replace(HTTPS, partnerDomain); - if (link.startsWith(linkDomain)) { - link = link.replace(linkDomain, host); - } - } - - if ((link.length > host.length) && link.startsWith(host)) { - var urlHash = link.slice(host.length); - var res = urlHash.match(Utils.CIRCUIT_CONVERSATION_HASH_PATTERN); - if (res) { - // If conversation is muted, return it as normal so other users can see it if I share its link to them. - // We also still need to support old links to open conversations, but we must replace with a normal link. - return res[0].replace(/\/(muted|open)/gim, '\/conversation'); - } - // Check if this is a user, user profile, email and phone links (e.g. mention) - res = urlHash.match(Utils.CIRCUIT_LINKS_HASH_PATTERN) || urlHash.match(Utils.CIRCUIT_EMAIL_HASH_PATTERN); - return res && res[0]; - } - return false; - }; - - /** - * Gratefully borrowed from Underscore. http://underscorejs.org/#throttle - * - * Creates and returns a new, throttled version of the passed function, that, - * when invoked repeatedly, will only actually call the original function at - * most once per every wait milliseconds. Useful for rate-limiting events that - * occur faster than you can keep up with. - */ - Utils.throttle = function (func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - options = options || {}; - - var later = function () { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - context = args = null; - }; - return function () { - var now = Date.now(); - if (!previous && options.leading === false) { - previous = now; - } - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - if (timeout) { - window.clearTimeout(timeout); - timeout = null; - } - previous = now; - result = func.apply(context, args); - context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = window.setTimeout(later, remaining); - } - return result; - }; - }; - - Utils.hasEmptyPrototype = function (obj) { - if (!obj || (typeof obj !== 'object')) { - return false; - } - return Utils.isEmptyObject(Object.getPrototypeOf(obj)); - }; - - Utils.compareElements = function (el1, el2) { - if (el1 === el2) { - return true; - } - if (!el1 || !el2) { - return false; - } - if (typeof el1 !== 'object' || typeof el2 !== 'object') { - return false; - } - - if (el1 instanceof Array && el2 instanceof Array) { - return (function (arr1, arr2) { - var length = arr1.length; - var comparisons = []; - - if (arr1 === arr2) { - return true; - } - if (arr1.length !== arr2.length) { - return false; - } - - for (var i = 0; i < length; ++i) { - for (var j = 0; j < length; ++j) { - if (comparisons.indexOf(arr2[j]) !== -1) { - continue; - } - if (Utils.compareElements(arr1[i], arr2[j])) { - comparisons.push(arr2[j]); - break; - } - } - } - return comparisons.length === length; - })(el1, el2); - } - - if (!Utils.hasEmptyPrototype(el1) || !Utils.hasEmptyPrototype(el2)) { - return false; - } - - for (var key in el1) { - // Don't compare inheritable or angular properties - if (el1.hasOwnProperty(key) && key.indexOf('$$') !== 0) { - if (!Utils.compareElements(el1[key], el2[key])) { return false; } - } - } - return true; - }; - - - Utils.copyToClipboard = function (copyText, cb, asText) { - // Do not use document.queryCommandSupported('copy') to check if copy is enabled - seems it's buggy now - // https://code.google.com/p/chromium/issues/detail?id=476508 - var selection; - var range; - var buffer; - var bodyOverflow; - var isMsie; - if (!copyText) { - return; - } - - try { - range = document.createRange(); - selection = window.getSelection(); - bodyOverflow = document.body.style.overflow; - isMsie = this.getBrowserInfo().msie; - - buffer = document.createElement('div'); - // Replace new line with <br> for correct view in Outlook - copyText = copyText.replace(/\r\n/gi, '<br>'); - - // Note: Chrome doesn't override the bg-color with transparent in Outlook, so use the #fff; - buffer.style.backgroundColor = '#fff'; - buffer.style.font = 'normal normal 15px Calibri, sans-serif'; - buffer.style['white-space'] = 'pre-wrap'; - if (asText) { - buffer.innerText = copyText; - } else { - buffer.innerHTML = copyText; - } - document.body.appendChild(buffer); - - range.selectNode(buffer); - selection.removeAllRanges(); - selection.addRange(range); - - if (isMsie) { - document.body.style.overflow = 'hidden'; - } - - var successful = document.execCommand('copy'); - if (successful) { - cb && cb(); - } else { - cb && cb('Failed to copy'); - } - - } catch (e) { - logger.error('[Utils]: Copy to clipboard failed'); - cb && cb('Internal error'); - - } finally { - if (selection) { - if (typeof selection.removeRange === 'function') { - selection.removeRange(range); - } else { - selection.removeAllRanges(); - } - } - - if (isMsie) { - document.body.style.overflow = bodyOverflow; - } - - if (buffer) { - document.body.removeChild(buffer); - } - } - }; - - Utils.sslProxify = function (url) { - if (url && url.startsWith('http:')) { - return SSL_IMAGE_PROXY + encodeURIComponent(url); - } - return url; - }; - - Utils.appIsFocused = function () { - if (circuit.isElectron) { - // https://github.com/electron/electron/issues/9744 - var electronWindow = remote.getCurrentWindow(); - return electronWindow.isFocused() && electronWindow.isVisible() && !electronWindow.isMinimized(); - } - - return document.hidden !== true; - }; - - Utils.validatePreviewProtocol = function (html) { - if (!html) { - return ''; - } - if (html.match(/https:/)) { - return html; - } - return html.replace('src="//', 'src="https://'); - }; - - // Used for switching between tabs (feed, details, questions...) - // only one name can be selected (=true) - // selecting: tabSelector.[tab-name] = true (setter) - // checking: tabSelector.[tab-name] (getter) - // tabSelector.selected : returns last selected tab name - // first tab name during initialization will be selected automatically - Utils.TabSelector = function (tabNames) { - if (!Array.isArray(tabNames) || tabNames.length === 0) { - return; - } - - var _that = this; - var _tabs = {}; - var _selected = ''; - - function reset() { - Object.keys(_tabs).forEach(function (name) { - _tabs[name] = false; - }); - } - - tabNames.forEach(function (name, idx) { - if (idx === 0) { - _tabs[name] = true; - _selected = name; - } else { - _tabs[name] = false; - } - Object.defineProperty(_that, name, { - get: function () { - return _tabs[name]; - }, - set: function (value) { - if (value) { - reset(); - _tabs[name] = true; - _selected = name; - } - }, - enumerable: true, - configurable: false - }); - }); - - Object.defineProperty(this, 'selected', { - get: function () { - return _selected; - }, - enumerable: true, - configurable: false - }); - - Object.defineProperty(this, 'selectedIdx', { - get: function () { - return tabNames.indexOf(_selected); - }, - enumerable: true, - configurable: false - }); - }; - - /** - * Get current origin, e.g. https://eu.yourcircuit.com - */ - Utils.getOrigin = getOrigin; - - /** - * Function to parse a querystring - */ - Utils.parseQS = function (qs) { - var params = {}; - qs = qs.substring(1); - var regex = /([^&=]+)=([^&]*)/g; - var m = regex.exec(qs); - while (m) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - m = regex.exec(qs); - } - return params; - }; - - /** - * Function to build a querystring - */ - Utils.toQS = function (obj) { - var str = []; - for (var p in obj) { - if (obj.hasOwnProperty(p) && obj[p] !== undefined) { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); - } - } - return str.join('&'); - }; - - Utils.extractConfigurableText = function (configurableTexts, type, language) { - var requestedTranslations = configurableTexts && configurableTexts.find(function (configurableText) { - return configurableText.type === type; - }); - if (!requestedTranslations || !requestedTranslations.texts) { - return; - } - - var findText = function (lang) { - return requestedTranslations.texts.find(function (text) { - return text.language === lang; - }); - }; - - var text; - if (language) { - // Look for a translation for the given language (e.g. 'en-US') - text = findText(language); - if (!text) { - // Remove the country code and look for a translation for the given language (e.g. 'en') - language = language.split('-', 1)[0]; - text = findText(language); - } - } - if (!text) { - // Look for a default translation - text = findText('default'); - } - return text && text.translation; - }; - - /** - * Use this function to select one device from the preferred list based on the list of - * available devices - * - * @param {Array} available - List of available devices - * @param {Array} preferred - List of preferred devices (in order of preference) - * @param {Boolean} matchByLabel - (Optional) Match devices by label if matching by ID fails - * @returns {Object} - Selected device object or null if no preferred devices were found - */ - Utils.selectMediaDevice = function (available, preferred, matchByLabel) { - if (!available || !preferred) { - return; - } - var found; - preferred.some(function (p) { - if (typeof p.id === 'undefined') { - // There should be only 1 "idless" device and it's the OS default device - found = p; - } else { - found = available.find(function (a) { - return p.id === a.id; - }); - } - return !!found; - }); - if (!found && matchByLabel) { - preferred.some(function (p) { - found = available.find(function (a) { - return p.label === a.label; - }); - return !!found; - }); - } - return found; - }; - - /** - * Remove a single element from an array. - * @param {Array} array - Array of which to remove an element - * @param {any} elem - Element to remove - */ - Utils.removeArrayElement = function (array, elem) { - if (Array.isArray(array) && typeof elem !== 'undefined' && elem) { - var index = array.indexOf(elem); - if (index > -1) { - array.splice(index, 1); - } - } - }; - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // Polyfills for Object - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - - if (typeof Object.values !== 'function') { - Object.values = function (obj) { - return Object.keys(obj).map(function (key) { return obj[key]; }); - }; - } - - if (typeof Object.assign !== 'function') { - Object.assign = function assign(target) { - if (target === null || target === undefined) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource) { - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }; - } - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // Polyfills for String objects - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - - // Simplified polyfill for ECMAScript 2015 String.prototype.startsWith(). - if (!String.prototype.startsWith) { - Object.defineProperty(String.prototype, 'startsWith', { - value: function (searchString, position) { - if (searchString === null || searchString === undefined) { - return false; - } - searchString = searchString.toString(); - position = Math.floor(position) || 0; - return this.substr(position, searchString.length) === searchString; - } - }); - } - - // Simplified polyfill for ECMAScript 2015 String.prototype.endsWith(). - if (!String.prototype.endsWith) { - Object.defineProperty(String.prototype, 'endsWith', { - value: function (searchString, position) { - if (searchString === null || searchString === undefined) { - return false; - } - if (position === undefined) { - position = this.length; - } else { - position = Math.min(Math.floor(position) || 0, this.length); - } - searchString = searchString.toString(); - position -= searchString.length; - if (position < 0) { - return false; - } - return this.substr(position, searchString.length) === searchString; - } - }); - } - - // Simplified polyfill for ECMAScript 2015 String.prototype.includes(). - if (!String.prototype.includes) { - Object.defineProperty(String.prototype, 'includes', { - value: function (value) { - return this.indexOf(value) !== -1; - } - }); - } - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // New APIs for String objects - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - - // Equivalent of C# String.Format method - Object.defineProperty(String.prototype, 'format', { - value: function () { - var args = arguments; - return this.replace(/{(\d+)}/g, function (match, index) { - return args[index] || match; - }); - } - }); - - - // Replaces character at given index - Object.defineProperty(String.prototype, 'replaceAt', { - value: function (index, character) { - return this.substr(0, index) + character + this.substr(index + character.length); - } - }); - - Object.defineProperty(String.prototype, 'randomCharacter', { - value: function () { - return this[Math.floor(this.length * Math.random())]; - } - }); - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // Polyfills for Array objects - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - - // Simplified polyfill for ECMAScript 2016 Array.prototype.includes(). - if (!Array.prototype.includes) { - Object.defineProperty(Array.prototype, 'includes', { - value: function (value) { - return this.indexOf(value) !== -1; - } - }); - } - - - - // Polyfill for ECMAScript 2015 (ES6) Array.prototype.find(). - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find - if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - value: function (predicate) { - if (this === null || this === undefined) { - throw new TypeError('Array.prototype.find called on null or undefined'); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - // eslint-disable-next-line no-bitwise - var length = list.length >>> 0; - var thisArg = arguments[1]; - var value; - - for (var i = 0; i < length; i++) { - value = list[i]; - if (predicate.call(thisArg, value, i, list)) { - return value; - } - } - return undefined; - } - }); - } - - // Polyfill for ECMAScript 2015 (ES6) Array.prototype.findIndex(). - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex - if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - value: function (predicate) { - if (this === null || this === undefined) { - throw new TypeError('Array.prototype.findIndex called on null or undefined'); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - // eslint-disable-next-line no-bitwise - var length = list.length >>> 0; - var thisArg = arguments[1]; - var value; - - for (var i = 0; i < length; i++) { - value = list[i]; - if (predicate.call(thisArg, value, i, list)) { - return i; - } - } - return -1; - } - }); - } - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // New APIs for Array objects - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - Object.defineProperty(Array.prototype, 'isEmpty', { - value: function () { - return this.length === 0; - } - }); - - Object.defineProperty(Array.prototype, 'last', { - value: function () { - if (this.length > 0) { - return this[this.length - 1]; - } - } - }); - - Object.defineProperty(Array.prototype, 'shuffle', { - value: function () { - var l = this.length; - for (var i = 0; i < l; i++) { - var r = Math.floor(Math.random() * l); - var o = this[r]; - this[r] = this[i]; - this[i] = o; - } - return this; - } - }); - - Object.defineProperty(Array.prototype, 'randomCopy', { - value: function (numElems) { - var copy = this.slice(0); - copy.shuffle(); - return copy.splice(0, numElems); - } - }); - - // Move one element from old position to new position - Object.defineProperty(Array.prototype, 'move', { - value: function (oldPos, newPos) { - if (oldPos === undefined || newPos === undefined) { - return; - } - if (newPos >= this.length) { - var k = newPos - this.length; - while ((k--) + 1) { - this.push(undefined); - } - } - this.splice(newPos, 0, this.splice(oldPos, 1)[0]); - } - }); - - // Thanks to the BinarySearch from http://oli.me.uk/2013/06/08/searching-javascript-arrays-with-a-binary-search/ - // This function finds the right position and inserts the searchElement. - // It works even when the array is empty. - Object.defineProperty(Array.prototype, 'binaryInsert', { - value: function (searchElement, compareFunction) { - if (typeof compareFunction !== 'function') { - // no compare function supplied - return false; - } - - var minIndex = 0; - var maxIndex = this.length - 1; - var currentIndex; - var currentElement; - var found = false; - - while (minIndex <= maxIndex) { - currentIndex = Math.floor((minIndex + maxIndex) / 2); - currentElement = this[currentIndex]; - - if (compareFunction(currentElement, searchElement) < 0) { - minIndex = currentIndex + 1; - } else if (compareFunction(currentElement, searchElement) > 0) { - maxIndex = currentIndex - 1; - } else { - found = true; - break; - } - } - - var indexToInsert = found ? currentIndex + 1 : minIndex; - if (indexToInsert >= 0) { - this.splice(indexToInsert, 0, searchElement); - return true; - } - - return false; - } - }); - - // Empties an array. The most performant way to achieve this. - if (!Array.prototype.empty) { - Object.defineProperty(Array.prototype, 'empty', { - value: function () { - while (this.length > 0) { - this.pop(); - } - } - }); - } - - // Array difference. - if (!Array.prototype.diff) { - Object.defineProperty(Array.prototype, 'diff', { - value: function (someArray) { - return this.filter(function (i) { return !someArray.includes(i); }); - } - }); - } - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // Polyfills for Number object - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - // Number.isInteger() method - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger - Number.isInteger = Number.isInteger || function (value) { - return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; - }; - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // New static APIs for RegExp type - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - RegExp.escape = function (str) { - if (!str) { - return ''; - } - var regex = str.replace(/[.*+?|()\[\]{}\\$^]/g, '\\$&'); - return regex; - }; - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // New APIs for Date objects - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - Date.prototype.moveToEndOfDay = function () { - return this.set({hour: 23, minute: 59, second: 59}); - }; - - Date.prototype.moveToFirstDayOfYear = function () { - return this.set({day: 1, month: 0}); - }; - - Date.prototype.moveToLastDayOfYear = function () { - return this.set({month: 11}).moveToLastDayOfMonth(); - }; - - /////////////////////////////////////////////////////////////////////////////////////////////////// - // - // Wrapper for the PhoneNumberUtil object - // - /////////////////////////////////////////////////////////////////////////////////////////////////// - var PhoneNumberFormatter = {}; - PhoneNumberFormatter.format = function (phoneNumber) { - if ((typeof PhoneNumberUtil !== 'undefined') && phoneNumber && phoneNumber.charAt(0) === '+') { - try { - var number = PhoneNumberUtil.parseAndKeepRawInput(phoneNumber, null); - phoneNumber = PhoneNumberUtil.formatInternational(number); - } catch (e) { - } - } - return phoneNumber; - }; - - // Exports - circuit.Utils = Utils; - circuit.PhoneNumberFormatter = PhoneNumberFormatter; - - return circuit; -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Utils = circuit.Utils || {}; - - /** - * Emoticon mapping used when Circuit.Client is configured with emoticonMode `standard`. Can be enhanced or overwritten if needed. - * @property EMOTICON_MAPPING - * @type Enum - * @static - * @final - */ - Utils.EMOTICON_MAPPING = { - ':)': 0x1F603, - ':D': 0x1F604, - ':(': 0x1F61E, - ':heart': 0x1F60D, - ';)': 0x1F609, - ':awesome': 0x1F604, - ':angry': 0x1F620, - ':zzz': 0x1F62A, - ':O': 0x1F632, - ';(': 0x1F622 - }; - - var EMOTICON_REGEX = /<(img[^>|<]*(?:shortcut|abbr)="(\S*)"[^\/?>]*\/?)>/gi; - - function convert(str, p1, p2/*, offset, s*/) { - var code = Utils.EMOTICON_MAPPING[p2]; - return code ? String.fromCodePoint(code) : ''; - } - - Utils.standardizeEmoticon = function (text) { - return text ? text.replace(EMOTICON_REGEX, convert) : ''; - }; - - Utils.removeMentionHtml = function (content) { - var isReplace = 0; - return content.replace(/(<span.*?>)|(<\/span>)/gi, function (match, openTag, closeTag) { - if (openTag) { - if (openTag.match(/class="mention"/i)) { - return ''; - } else { - isReplace++; - return openTag; - } - } - if (closeTag) { - if (!isReplace) { - return ''; - } else { - isReplace--; - return closeTag; - } - } - }); - }; - - return circuit; - -})(Circuit || {}); - -// Define globals for JSHint - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = circuit.logger; - var Utils = circuit.Utils; - - var Constants = Object.freeze({ - WSMessageType: { - REQUEST: 'REQUEST', - RESPONSE: 'RESPONSE', - EVENT: 'EVENT' - }, - - ContentType: { - SYSTEM: 'SYSTEM', - NONE: 'NONE', - EXTENSION: 'EXTENSION', // Not used by client - CONVERSATION: 'CONVERSATION', - USER: 'USER', - RTC_SESSION: 'RTC_SESSION', - RTC_CALL: 'RTC_CALL', - SEARCH: 'SEARCH', - KEEPALIVE: 'KEEPALIVE', // Not used by client - INSTRUMENTATION: 'INSTRUMENTATION', - GUEST: 'GUEST', - ADMINISTRATION: 'ADMINISTRATION', - THIRDPARTYCONNECTORS: 'THIRDPARTYCONNECTORS', - USER_TO_USER: 'USER_TO_USER', - VERSION: 'VERSION', - ACCOUNT: 'ACCOUNT', - ACTIVITYSTREAM: 'ACTIVITYSTREAM', - CONVERSATION_USER_DATA: 'CONVERSATION_USER_DATA', - - // Content type used exclusively between client and access server - USER_DATA: 'USER_DATA' - }, - - ReturnCode: { - // Client API Errors - OK: 'OK', - AUTHORIZATION_FAILED: 'AUTHORIZATION_FAILED', - AUTHORIZATION_REQUIRED: 'AUTHORIZATION_REQUIRED', - SERVICE_EXCEPTION: 'SERVICE_EXCEPTION', - PROTOBUF_CONVERSION_EXCEPTION: 'PROTOBUF_CONVERSION_EXCEPTION', // Client API V1 - CONVERSION_EXCEPTION: 'CONVERSION_EXCEPTION', - NO_MESSAGE_HANDLER: 'NO_MESSAGE_HANDLER', - INVALID_CLIENT: 'INVALID_CLIENT', - NO_RESULT: 'NO_RESULT', - OPERATION_NOT_SUPPORTED: 'OPERATION_NOT_SUPPORTED', - ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS', - FRAME_SIZE_TO_LONG: 'FRAME_SIZE_TO_LONG', - BACKEND_UNAVAILABLE: 'BACKEND_UNAVAILABLE', - OVERLOADED: 'OVERLOADED', - OLD_VERSION: 'OLD_VERSION', - STORAGE_OVERLOADED: 'STORAGE_OVERLOADED', - USER_NOT_FOUND: 'USER_NOT_FOUND', - CLOUD_TELEPHONY_EXCEPTION: 'CLOUD_TELEPHONY_EXCEPTION', - - // Access Server Errors - SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE', - - // Internal Client Errors - REQUEST_TIMEOUT: 'REQUEST_TIMEOUT', - DISCONNECTED: 'DISCONNECTED', - INVALID_MESSAGE: 'INVALID_MESSAGE', - FAILED_TO_SEND: 'FAILED_TO_SEND', - UNEXPECTED_RESPONSE: 'UNEXPECTED_RESPONSE', - MISSING_REQUIRED_PARAMETER: 'MISSING_REQUIRED_PARAMETER', - CHOOSE_DESKTOP_MEDIA_CANCELLED: 'CHOOSE_DESKTOP_MEDIA_CANCELLED', - - // SDK Errors - SDK_ERROR: 'SDK_ERROR' - }, - - // Error codes used when return code is SERVICE_EXCEPTION - ErrorCode: { - UNEXPECTED_ERROR: 'UNEXPECTED_ERROR', - CONSTRAINT_VIOLATION: 'CONSTRAINT_VIOLATION', - MAX_NUM_CONV_PARTICIPANTS: 'MAX_NUM_CONV_PARTICIPANTS', - THIRDPARTY_ERROR: 'THIRDPARTY_ERROR', - AUTHORIZATION_FAILED: 'AUTHORIZATION_FAILED', - PERMISSION_DENIED: 'PERMISSION_DENIED', - RTC_NO_MEDIA_NODES_AVAILABLE: 'RTC_NO_MEDIA_NODES_AVAILABLE', - RTC_CONCURRENT_INCOMING_CALL: 'RTC_CONCURRENT_INCOMING_CALL', - DUPLICATED_ENTITY_ERROR: 'DUPLICATED_ENTITY_ERROR', - RTC_MEDIA_NODE_UNREACHABLE: 'RTC_MEDIA_NODE_UNREACHABLE', - CLOUD_TELEPHONY_ERROR: 'CLOUD_TELEPHONY_ERROR' - }, - - ThirdPartyError: { - UNSUPPORTED_PROVIDER: 'UNSUPPORTED_PROVIDER', // Unknown provider - BAD_REQUEST: 'BAD_REQUEST', // A bad request was send to the provider server - GENERAL_ERROR: 'GENERAL_ERROR', // A general Error occured - INSUFFICIENT_PERMISSION: 'INSUFFICIENT_PERMISSION', // There are insufficient Permission accessing a resource - NOT_AUTHORIZED: 'NOT_AUTHORIZED', // User is not authorized - RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND', // Resource could not be found - SERVER_DOWN: 'SERVER_DOWN', // Server is down. No response from server - SESSION_EXPIRED: 'SESSION_EXPIRED', // Session has been expired - UNSUPPORTED_FILE_TYPE: 'UNSUPPORTED_FILE_TYPE', // The file is not supported for online editing - WRONG_SERVER: 'WRONG_SERVER', // The server of the file attached, is different from the server the user is connected to - - // Internal client errors - EMPTY_SETTINGS: 'EMPTY_SETTINGS', // Settings not provided (validation error) - EMPTY_USERNAME: 'EMPTY_USERNAME', // Username not provided (validation error) - EMPTY_PASSWORD: 'EMPTY_PASSWORD', // Password not provided (validation error) - EMPTY_SERVER_ADDRESS: 'EMPTY_SERVER_ADDRESS', // Server address not provided (validation error) - INVALID_SERVER_ADDRESS: 'INVALID_SERVER_ADDRESS', // Invalid server url (validation error) - NOT_CONNECTED: 'NOT_CONNECTED', // Not connected to a thirdparty provider - ALREADY_CONNECTED: 'ALREADY_CONNECTED', // Already connected to a thirdparty provider - INVALID_ITEMIDS: 'INVALID_ITEMIDS' - }, - - CloudTelephonyErrorType: { - CMP_NEXT: 'CMP_NEXT', // Error caused in CMPNext side - VALIDATION: 'VALIDATION' // Error during user's input validation - }, - - CloudTelephonyValidationError: { - UNKNOWN: 'UNKNOWN', - DN_NOT_FOUND_IN_TENANT: 'DN_NOT_FOUND_IN_TENANT', - DUPLICATE_SITE_NAME: 'DUPLICATE_SITE_NAME', - OVERLAPPING_PUBLIC_RANGES_WITHIN_SITE: 'OVERLAPPING_PUBLIC_RANGES_WITHIN_SITE', - OVERLAPPING_PUBLIC_RANGES_ACROSS_DEPLOYMENT: 'OVERLAPPING_PUBLIC_RANGES_ACROSS_DEPLOYMENT', - OVERLAPPING_PRIVATE_RANGES_WITHIN_SITE: 'OVERLAPPING_PRIVATE_RANGES_WITHIN_SITE', - OVERLAPPING_PRIVATE_RANGES_ACROSS_TENANT: 'OVERLAPPING_PRIVATE_RANGES_ACROSS_TENANT', - EXISTING_USERS_IN_RANGE: 'EXISTING_USERS_IN_RANGE' - }, - - CloudTelephonyCmpNextError: { - COUNTRY_DETAILS_NOT_FOUND: 'COUNTRY_DETAILS_NOT_FOUND' - }, - - /////////////////////////////////////////////////////////////////////////// - // Conversation Action - /////////////////////////////////////////////////////////////////////////// - ConversationActionType: { - CREATE: 'CREATE', - UPDATE: 'UPDATE', - DELETE: 'DELETE', - ADD_PARTICIPANT: 'ADD_PARTICIPANT', - REMOVE_PARTICIPANT: 'REMOVE_PARTICIPANT', - ADD_TEXT_ITEM: 'ADD_TEXT_ITEM', - UPDATE_TEXT_ITEM: 'UPDATE_TEXT_ITEM', - DELETE_TEXT_ITEM: 'DELETE_TEXT_ITEM', - GET_CONVERSATION_BY_ID: 'GET_CONVERSATION_BY_ID', - GET_CONVERSATIONS: 'GET_CONVERSATIONS', - GET_ITEM_BY_ID: 'GET_ITEM_BY_ID', - GET_ITEMS_BY_CONVERSATION: 'GET_ITEMS_BY_CONVERSATION', - GET_ITEMS_BY_CONVERSATION_ITEM_ID: 'GET_ITEMS_BY_CONVERSATION_ITEM_ID', - SET_READ_POINTER: 'SET_READ_POINTER', - SUBSCRIBE: 'SUBSCRIBE', - UNSUBSCRIBE: 'UNSUBSCRIBE', - TYPING: 'TYPING', - GET_CONVERSATION_USER_DATA: 'GET_CONVERSATION_USER_DATA', - SET_FLAG_ITEM: 'SET_FLAG_ITEM', - CLEAR_FLAG_ITEM: 'CLEAR_FLAG_ITEM', - GET_FLAGGED_ITEMS: 'GET_FLAGGED_ITEMS', - GET_JOIN_DETAILS: 'GET_JOIN_DETAILS', - GET_SUPPORT_CONVERSATION_ID: 'GET_SUPPORT_CONVERSATION_ID', - GET_SUPPORT_CONVERSATION: 'GET_SUPPORT_CONVERSATION', - GET_OPEN_CONVERSATIONS: 'GET_OPEN_CONVERSATIONS', - JOIN_OPEN_CONVERSATION: 'JOIN_OPEN_CONVERSATION', - MARK_CONVERSATION: 'MARK_CONVERSATION', - UNMARK_CONVERSATION: 'UNMARK_CONVERSATION', - GET_MARKED_CONVERSATIONS_LIST: 'GET_MARKED_CONVERSATIONS_LIST', - SET_CONVERSATION_AVATAR: 'SET_CONVERSATION_AVATAR', - GET_TELEPHONY_CONVERSATION_ID: 'GET_TELEPHONY_CONVERSATION_ID', - GET_ATTACHMENTS: 'GET_ATTACHMENTS', - GET_CONVERSATION_BY_USER: 'GET_CONVERSATION_BY_USER', - GET_CONVERSATION_SUMMARY: 'GET_CONVERSATION_SUMMARY', - LIKE_TEXT_ITEM: 'LIKE_TEXT_ITEM', - UNLIKE_TEXT_ITEM: 'UNLIKE_TEXT_ITEM', - DELETE_RECORDING: 'DELETE_RECORDING', - MODERATE_CONVERSATION: 'MODERATE_CONVERSATION', - UNMODERATE_CONVERSATION: 'UNMODERATE_CONVERSATION', - ADD_SCHEDULE_ITEM: 'ADD_SCHEDULE_ITEM', // Obsolete - UPDATE_SCHEDULE_ITEM: 'UPDATE_SCHEDULE_ITEM', // Obsolete - DELETE_SCHEDULE_ITEM: 'DELETE_SCHEDULE_ITEM', // Obsolete - GET_ALL_USER_SCHEDULE_ITEMS: 'GET_ALL_USER_SCHEDULE_ITEMS', // Obsolete - GRANT_MODERATOR_RIGHTS: 'GRANT_MODERATOR_RIGHTS', - DROP_MODERATOR_RIGHTS: 'DROP_MODERATOR_RIGHTS', - GET_CONVERSATION_THREADS: 'GET_CONVERSATION_THREADS', - GET_THREAD_COMMENTS: 'GET_THREAD_COMMENTS', - GET_THREADS_BY_IDS: 'GET_THREADS_BY_IDS', - ADD_JOURNAL_ENTRY: 'ADD_JOURNAL_ENTRY', - GET_THREADS_BY_ITEM_IDS: 'GET_THREADS_BY_ITEM_IDS', - DELETE_ATTACHMENTS: 'DELETE_ATTACHMENTS', - UPDATE_GUEST_ACCESS: 'UPDATE_GUEST_ACCESS', - GET_CONVERSATION_PARTICIPANTS: 'GET_CONVERSATION_PARTICIPANTS', - GET_CONVERSATION_FEED: 'GET_CONVERSATION_FEED', - SET_FAVORITE_POSITION: 'SET_FAVORITE_POSITION', - GET_ITEMS_BY_IDS: 'GET_ITEMS_BY_IDS', - ADD_LABELS: 'ADD_LABELS', - ASSIGN_LABELS: 'ASSIGN_LABELS', - UNASSIGN_LABELS: 'UNASSIGN_LABELS', - GET_LABELS: 'GET_LABELS', - GET_LABEL_PAGE: 'GET_LABEL_PAGE', - REMOVE_LABELS: 'REMOVE_LABELS', - EDIT_LABEL: 'EDIT_LABEL', - UPDATE_RTC_ITEM_ATTACHMENTS: 'UPDATE_RTC_ITEM_ATTACHMENTS', - GET_CONVERSATIONS_BY_FILTER: 'GET_CONVERSATIONS_BY_FILTER', - ADD_FILTER: 'ADD_FILTER', - REMOVE_FILTERS: 'REMOVE_FILTERS', - GET_FILTERS: 'GET_FILTERS', - EDIT_FILTER: 'EDIT_FILTER', - GET_FAVORITE_CONVERSATION_IDS: 'GET_FAVORITE_CONVERSATION_IDS', - GET_CONVERSATIONS_BY_IDS: 'GET_CONVERSATIONS_BY_IDS', - GET_USER_DATA_SINCE: 'GET_USER_DATA_SINCE', - GET_CONFERENCE_INVITATION_TEXT: 'GET_CONFERENCE_INVITATION_TEXT', - GET_CONFERENCE_INVITATION_EXAMPLE: 'GET_CONFERENCE_INVITATION_EXAMPLE' - }, - - /////////////////////////////////////////////////////////////////////////// - // Conversation Event - /////////////////////////////////////////////////////////////////////////// - ConversationEventType: { - CREATE: 'CREATE', - UPDATE: 'UPDATE', - DELETE: 'DELETE', - ADD_ITEM: 'ADD_ITEM', - UPDATE_ITEM: 'UPDATE_ITEM', - READ_ITEMS: 'READ_ITEMS', - TYPING: 'TYPING', - DRAFT_MESSAGE_SAVED: 'DRAFT_MESSAGE_SAVED', - DRAFT_MESSAGE_DISCARDED: 'DRAFT_MESSAGE_DISCARDED', - DRAFT_MESSAGE_PUBLISHED: 'DRAFT_MESSAGE_PUBLISHED', - FLAG_ITEM: 'FLAG_ITEM', - CLEAR_FLAGGED_ITEM: 'CLEAR_FLAGGED_ITEM', - CONVERSATION_MARKED: 'CONVERSATION_MARKED', - CONVERSATION_UNMARKED: 'CONVERSATION_UNMARKED', - FAVORITE_POSITION_CHANGED: 'FAVORITE_POSITION_CHANGED', - USER_DATA_CHANGED: 'USER_DATA_CHANGED' - }, - - /////////////////////////////////////////////////////////////////////////// - // Conversation Data - /////////////////////////////////////////////////////////////////////////// - SearchDirection: { - AFTER: 'AFTER', - BEFORE: 'BEFORE', - BOTH: 'BOTH' - }, - - // General filter that defines which items to retrieve. - TimestampFilter: { - MODIFICATION: 'MODIFICATION', - CREATION: 'CREATION' - }, - - // General filter that defines the order in which the items will be retrieved. - OrderBy: { - TIMESTAMP_ASC: 'TIMESTAMP_ASC', - TIMESTAMP_DESC: 'TIMESTAMP_DESC' - }, - - OrderType: { - MODIFICATION: 'MODIFICATION', - CREATION: 'CREATION' - }, - - ConversationType: { - DIRECT: 'DIRECT', - GROUP: 'GROUP', - OPEN: 'OPEN', - LARGE: 'LARGE' - }, - - ConversationItemType: { - TEXT: 'TEXT', - RTC: 'RTC', - SYSTEM: 'SYSTEM' - }, - - ConversationFilter: { - ALL: 'ALL', - OPEN_MEMBER: 'OPEN_MEMBER', - MARKED: 'MARKED', - ARCHIVED: 'ARCHIVED', - UNARCHIVED: 'UNARCHIVED' - }, - - ConversationMarkFilter: { - MUTE: 'MUTE', - FAVORITE: 'FAVORITE' - }, - - GetConversationParticipantCriteria: { - DISPLAY_NAME: 'DISPLAY_NAME', - TYPE: 'TYPE' - }, - - ConversationParticipantType: { - ACTIVE: 'ACTIVE', - REGULAR: 'REGULAR', - FORMER: 'FORMER', - MODERATOR: 'MODERATOR', - GUEST: 'GUEST' - }, - - DataRetentionState: { - UNTOUCHED: 'UNTOUCHED', - OUTDATED: 'OUTDATED', - DELETED: 'DELETED' - }, - - RetrieveAction: { - CONVERSATIONS: 'CONVERSATIONS', - CONVERSATION_IDS: 'CONVERSATION_IDS', - CONVERSATIONS_AND_IDS: 'CONVERSATIONS_AND_IDS' - }, - - RTCItemType: { - STARTED: 'STARTED', - ENDED: 'ENDED', - MISSED: 'MISSED', - PARTICIPANT_JOINED: 'PARTICIPANT_JOINED', - PARTICIPANT_LEFT: 'PARTICIPANT_LEFT', - MOVED: 'MOVED' - }, - - RTCItemMissed: { - UNREACHABLE: 'UNREACHABLE', - TIMEOUT: 'TIMEOUT', - USER_BUSY: 'USER_BUSY', - DECLINED: 'DECLINED', - CANCELED: 'CANCELED', - INVALID_NUMBER: 'INVALID_NUMBER', - TEMPORARILY_UNAVAILABLE: 'TEMPORARILY_UNAVAILABLE' - }, - - RTCItemMoved: { - MOVED_FROM: 'MOVED_FROM', - MOVED_TO: 'MOVED_TO' - }, - - ExternalUnshareProviderType: { - SYNCPLICITY: 'SYNCPLICITY' - }, - - SortingType: { - RECENT_ACTIVITY_ASC: 'RECENT_ACTIVITY_ASC', - RECENT_ACTIVITY_DESC: 'RECENT_ACTIVITY_DESC', - ALPHABETICALLY_ASC: 'ALPHABETICALLY_ASC', - ALPHABETICALLY_DESC: 'ALPHABETICALLY_DESC', - POPULARITY_ASC: 'POPULARITY_ASC', - POPULARITY_DESC: 'POPULARITY_DESC', - TIMESTAMP_ASC: 'TIMESTAMP_ASC' - }, - - TextItemContentType: { - PLAIN: 'PLAIN', - RICH: 'RICH' - }, - - TextItemState: { - CREATED: 'CREATED', - EDITED: 'EDITED', - DELETED: 'DELETED', - CLUSTERED: 'CLUSTERED' - }, - - SystemItemType: { - CONVERSATION_CREATED: 'CONVERSATION_CREATED', - PARTICIPANT_ADDED: 'PARTICIPANT_ADDED', - PARTICIPANT_REMOVED: 'PARTICIPANT_REMOVED', - CONVERSATION_RENAMED: 'CONVERSATION_RENAMED', - GUEST_JOINED: 'GUEST_JOINED', - GUEST_INVITED: 'GUEST_INVITED', - CONFERENCE_DETAILS_CHANGED: 'CONFERENCE_DETAILS_CHANGED', - CONVERSATION_MODERATED: 'CONVERSATION_MODERATED', - AVATAR_CHANGED: 'AVATAR_CHANGED' - }, - - // Attachments data - AttachmentOrderType: { - CREATION: 'CREATION', - FILENAME: 'FILENAME', - SIZE: 'SIZE', - MIMETYPE: 'MIMETYPE', - FILEEXTENSION: 'FILEEXTENSION' - }, - - AttachmentOrderDirection: { - ASC: 'ASC', - DSC: 'DSC' - }, - - BridgeLocale: { - EN_US: 'EN_US', - EN_GB: 'EN_GB', - DE_DE: 'DE_DE', - ES_ES: 'ES_ES', - FR_FR: 'FR_FR', - IT_IT: 'IT_IT', - RU_RU: 'RU_RU', - ZH_HANS_CN: 'ZH_HANS_CN' - }, - - BridgeNumberType: { - TOLL: 'TOLL', - TOLL_FREE: 'TOLL_FREE', - LOCAL: 'LOCAL' - }, - - FilterTarget: { - CONVERSATION_TYPE: 'CONVERSATION_TYPE', - LABEL_ID: 'LABEL_ID' - }, - - FilterTargetComparator: { - EQ: 'EQ', - NOT: 'NOT' - }, - - FilterBoolConnector: { - AND: 'AND', - OR: 'OR' - }, - - SystemLabel: { - ARCHIVED: 'ARCHIVED' - }, - - IncludedUserData: { - LAST_READ_TS: 'LASTREADTS', - UNREAD_COUNT: 'UNREADCOUNT', - LABEL_IDS: 'LABELIDS', - SYSTEM_LABEL_IDS: 'SYSTEMLABELIDS' - }, - - ChangedUserData: { - LAST_READ_TS: 'LASTREADTS', - LABEL_IDS: 'LABELIDS', - SYSTEM_LABEL_IDS: 'SYSTEMLABELIDS' - }, - - /////////////////////////////////////////////////////////////////////////// - // Instrumentation Action - /////////////////////////////////////////////////////////////////////////// - InstrumentationActionType: { - SUBMIT_CLIENT_DATA: 'SUBMIT_CLIENT_DATA', - SUBMIT_QOS_DATA: 'SUBMIT_QOS_DATA' - }, - - SubmitDataResult: { - UNKNOWN: 'UNKNOWN', - OK: 'OK', - NO_USERID: 'NO_USERID', - NO_CLIENTID: 'NO_CLIENTID', - NO_DATA: 'NO_DATA', - PROCESSING_ERROR: 'PROCESSING_ERROR' - }, - - QOSMediaType: { - AUDIO: 'AUDIO', - VIDEO: 'VIDEO', - SCREEN_SHARE: 'SCREEN_SHARE' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Call Action - /////////////////////////////////////////////////////////////////////////// - RTCCallActionType: { - JOIN: 'JOIN', - ANSWER: 'ANSWER', - ICE_CANDIDATES: 'ICE_CANDIDATES', - LEAVE: 'LEAVE', - TERMINATE: 'TERMINATE', - CHANGE_MEDIA_TYPE: 'CHANGE_MEDIA_TYPE', - INVITE_REJECT: 'INVITE_REJECT', - CHANGE_MEDIA_ACCEPT: 'CHANGE_MEDIA_ACCEPT', - CHANGE_MEDIA_REJECT: 'CHANGE_MEDIA_REJECT', - PREPARE: 'PREPARE', - RENEW_TURN_CREDENTIALS: 'RENEW_TURN_CREDENTIALS', - SEND_PROGRESS: 'SEND_PROGRESS', - SUBMIT_RTC_QUALITY_RATING: 'SUBMIT_RTC_QUALITY_RATING' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Call Event - /////////////////////////////////////////////////////////////////////////// - RTCCallEventType: { - SDP_ANSWER: 'SDP_ANSWER', - SDP_FAILED: 'SDP_FAILED', - INVITE: 'INVITE', - INVITE_CANCEL: 'INVITE_CANCEL', - INVITE_FAILED: 'INVITE_FAILED', - CHANGE_MEDIA_TYPE_REQUESTED: 'CHANGE_MEDIA_TYPE_REQUESTED', - PROGRESS: 'PROGRESS', - CHANGE_MEDIA_TYPE_FORCED: 'CHANGE_MEDIA_TYPE_FORCED', - ICE_CANDIDATES: 'ICE_CANDIDATES', - RTC_QUALITY_RATING_EVENT: 'RTC_QUALITY_RATING_EVENT' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Call Data - /////////////////////////////////////////////////////////////////////////// - SdpFailedCause: { - SESSION_STARTED_FAILED: 'SESSION_STARTED_FAILED', - CONNECTION_LOST: 'CONNECTION_LOST', - SERVICE_ERROR: 'SERVICE_ERROR', - CHANGE_MEDIA_REJECT: 'CHANGE_MEDIA_REJECT', - JOIN_FORBIDDEN: 'JOIN_FORBIDDEN', - SESSION_TERMINATED: 'SESSION_TERMINATED', - MAX_PARTICIPANTS_REACHED: 'MAX_PARTICIPANTS_REACHED' - }, - - InviteCancelCause: { - ACCEPT: 'ACCEPT', - BUSY: 'BUSY', - DECLINE: 'DECLINE', - REVOKED: 'REVOKED' - }, - - InviteFailedCause: { - BUSY: 'BUSY', - DECLINE: 'DECLINE', - TIMEOUT: 'TIMEOUT', - NOT_REACHABLE: 'NOT_REACHABLE', - INVALID_NUMBER: 'INVALID_NUMBER', - TEMPORARILY_UNAVAILABLE: 'TEMPORARILY_UNAVAILABLE', - REVOKED: 'REVOKED' - }, - - InviteRejectCause: { - BUSY: 'BUSY', - DECLINE: 'DECLINE', - TIMEOUT: 'TIMEOUT' - }, - - RTCProgressType: { - ALERTING: 'ALERTING', - PROGRESS: 'PROGRESS', - EARLY_CONNECT: 'EARLY_CONNECT' - }, - - DisconnectCause: { - HANGUP: 'HANGUP', - CONNECTION_LOST: 'CONNECTION_LOST', - NEGOTIATION_FAILED: 'NEGOTIATION_FAILED', - TRANSPORT_NEGOTIATION_FAILED: 'TRANSPORT_NEGOTIATION_FAILED', - SECURITY_NEGOTIATION_FAILED: 'SECURITY_NEGOTIATION_FAILED', - REMOTE_SDP_FAILED: 'REMOTE_SDP_FAILED', - STREAM_LOST: 'STREAM_LOST', - CALL_SETUP_FAILED: 'CALL_SETUP_FAILED' - }, - - DisconnectReason: { - PAGE_UNLOADED: 'PAGE_UNLOADED', - CALL_LOST: 'CALL_LOST', - LOGOUT: 'LOGOUT' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Session Action - /////////////////////////////////////////////////////////////////////////// - RTCSessionActionType: { - LOCK: 'LOCK', - MUTE_SESSION: 'MUTE_SESSION', - MUTE: 'MUTE', - ADD_PARTICIPANT: 'ADD_PARTICIPANT', - REMOVE_PARTICIPANT: 'REMOVE_PARTICIPANT', - GET_ACTIVE_SESSIONS: 'GET_ACTIVE_SESSIONS', - GET_SESSION: 'GET_SESSION', - MOVE: 'MOVE', - START_RECORDING: 'START_RECORDING', - STOP_RECORDING: 'STOP_RECORDING', - RAISE_QUESTION: 'RAISE_QUESTION', - INVITE_TO_STAGE: 'INVITE_TO_STAGE', - INVITE_TO_STAGE_ANSWER: 'INVITE_TO_STAGE_ANSWER', - UPDATE_QUESTION_STATE: 'UPDATE_QUESTION_STATE', - INVITE_TO_STAGE_CANCEL: 'INVITE_TO_STAGE_CANCEL', - REMOVE_FROM_STAGE: 'REMOVE_FROM_STAGE', - GET_QUESTIONS: 'GET_QUESTIONS', - OPEN_CURTAIN: 'OPEN_CURTAIN', - CLOSE_CURTAIN: 'CLOSE_CURTAIN', - GET_NODE_STATE: 'GET_NODE_STATE', - ENABLE_WHITEBOARD: 'ENABLE_WHITEBOARD', - DISABLE_WHITEBOARD: 'DISABLE_WHITEBOARD', - ADD_WHITEBOARD_ELEMENT: 'ADD_WHITEBOARD_ELEMENT', - REMOVE_WHITEBOARD_ELEMENT: 'REMOVE_WHITEBOARD_ELEMENT', - UPDATE_WHITEBOARD_ELEMENT: 'UPDATE_WHITEBOARD_ELEMENT', - CLEAR_WHITEBOARD: 'CLEAR_WHITEBOARD', - GET_WHITEBOARD: 'GET_WHITEBOARD', - SET_WHITEBOARD_BACKGROUND: 'SET_WHITEBOARD_BACKGROUND', - CLEAR_WHITEBOARD_BACKGROUND: 'CLEAR_WHITEBOARD_BACKGROUND', - TOGGLE_WHITEBOARD_OVERLAY: 'TOGGLE_WHITEBOARD_OVERLAY', - UNDO_WHITEBOARD: 'UNDO_WHITEBOARD', - SEND_CLIENT_INFO: 'SEND_CLIENT_INFO' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Session Event - /////////////////////////////////////////////////////////////////////////// - RTCSessionEventType: { - SESSION_STARTED: 'SESSION_STARTED', - SESSION_TERMINATED: 'SESSION_TERMINATED', - SESSION_UPDATED: 'SESSION_UPDATED', - SESSION_MOVED: 'SESSION_MOVED', - PARTICIPANT_JOINED: 'PARTICIPANT_JOINED', - PARTICIPANT_LEFT: 'PARTICIPANT_LEFT', - PARTICIPANT_UPDATED: 'PARTICIPANT_UPDATED', - ACTIVE_SPEAKER: 'ACTIVE_SPEAKER', - VIDEO_ACTIVE_SPEAKER: 'VIDEO_ACTIVE_SPEAKER', - SESSION_RECORDING_INFO: 'SESSION_RECORDING_INFO', - SESSION_RECORDING_STARTED: 'SESSION_RECORDING_STARTED', - SESSION_RECORDING_STOPPED: 'SESSION_RECORDING_STOPPED', - SESSION_RECORDING_FAILED: 'SESSION_RECORDING_FAILED', - SESSION_TERMINATION_TIMER_STARTED: 'SESSION_TERMINATION_TIMER_STARTED', - SESSION_TERMINATION_TIMER_CANCELLED: 'SESSION_TERMINATION_TIMER_CANCELLED', - SESSION_ATTRIBUTES_CHANGED_EVENT: 'SESSION_ATTRIBUTES_CHANGED_EVENT', - QUESTION_EVENT: 'QUESTION_EVENT', - INVITE_TO_STAGE_EVENT: 'INVITE_TO_STAGE_EVENT', - INVITE_TO_STAGE_CANCEL_EVENT: 'INVITE_TO_STAGE_CANCEL_EVENT', - CURTAIN_EVENT: 'CURTAIN_EVENT', - WHITEBOARD_ENABLED: 'WHITEBOARD_ENABLED', - WHITEBOARD_DISABLED: 'WHITEBOARD_DISABLED', - WHITEBOARD_ELEMENT_ADDED: 'WHITEBOARD_ELEMENT_ADDED', - WHITEBOARD_ELEMENT_REMOVED: 'WHITEBOARD_ELEMENT_REMOVED', - WHITEBOARD_ELEMENT_UPDATED: 'WHITEBOARD_ELEMENT_UPDATED', - WHITEBOARD_CLEARED: 'WHITEBOARD_CLEARED', - WHITEBOARD_BACKGROUND_SET: 'WHITEBOARD_BACKGROUND_SET', - WHITEBOARD_BACKGROUND_CLEARED: 'WHITEBOARD_BACKGROUND_CLEARED', - WHITEBOARD_OVERLAY_TOGGLED: 'WHITEBOARD_OVERLAY_TOGGLED', - WHITEBOARD_SYNC: 'WHITEBOARD_SYNC' - }, - - /////////////////////////////////////////////////////////////////////////// - // RTC Session Data - /////////////////////////////////////////////////////////////////////////// - RTCSessionTerminatedCause: { - FORCED: 'FORCED', - NO_USERS_LEFT: 'NO_USERS_LEFT', - CONDITIONAL: 'CONDITIONAL' - }, - - RTCSessionParticipantLeftCause: { - LEFT: 'LEFT', - CONNECTION_LOST: 'CONNECTION_LOST', - REMOVED: 'REMOVED', - CONDITIONAL_TERMINATE: 'CONDITIONAL_TERMINATE', - FORCED_TERMINATE: 'FORCED_TERMINATE', - MAX_PARTICIPANTS_REACHED: 'MAX_PARTICIPANTS_REACHED', - USER_LEFT_STAGE: 'USER_LEFT_STAGE', - NEGOTIATION_FAILED: 'NEGOTIATION_FAILED', - OFFER_NOT_ACCEPTED: 'OFFER_NOT_ACCEPTED', - STREAM_LOST: 'STREAM_LOST', - TRANSPORT_NEGOTIATION_FAILED: 'TRANSPORT_NEGOTIATION_FAILED', - SECURITY_NEGOTIATION_FAILED: 'SECURITY_NEGOTIATION_FAILED', - CALL_SETUP_FAILED: 'CALL_SETUP_FAILED', - REMOTE_SDP_FAILED: 'REMOTE_SDP_FAILED' - }, - - RealtimeMediaType: { - AUDIO: 'AUDIO', - VIDEO: 'VIDEO', - DESKTOP_SHARING: 'DESKTOP_SHARING' - }, - - RTCParticipantType: { - USER: 'USER', // Circuit users - EXTERNAL: 'EXTERNAL', // PSTN dial-in participants - TELEPHONY: 'TELEPHONY', // Telephony connector users (dial in or dial out) - SESSION_GUEST: 'SESSION_GUEST', // Session guests - MEETING_POINT: 'MEETING_POINT' // Conference Meeting Points - }, - - QuestionState: { - RAISED: 'RAISED', - ANSWERED: 'ANSWERED', - DISCARDED: 'DISCARDED', - ANSWER_LATER: 'ANSWER_LATER' - }, - - StageState: { - INITIAL: 'INITIAL', - INVITED: 'INVITED', - REJECTED: 'REJECTED', - ACCEPTED: 'ACCEPTED', - WAS_ON_STAGE: 'WAS_ON_STAGE', - TIMEOUT: 'TIMEOUT', - LEFT_CONFERENCE: 'LEFT_CONFERENCE' - }, - - QuestionEventType: { - CREATED: 'CREATED', - UPDATED: 'UPDATED', - REVOKED: 'REVOKED' - }, - - StageAction: { - USER_ENTERED_STAGE: 'USER_ENTERED_STAGE', - USER_LEFT_STAGE: 'USER_LEFT_STAGE' - }, - - CurtainState: { - OPEN: 'OPEN', - INITIAL: 'INITIAL', - FINAL: 'FINAL', - PAUSED: 'PAUSED' - }, - - NodeType: { - MEDIA_ACCESS: 'MEDIA_ACCESS', - APPLICATION: 'APPLICATION', - STORAGE: 'STORAGE', - ACCESS: 'ACCESS', - LOAD_BALANCER: 'LOAD_BALANCER', - ZOOKEEPER: 'ZOOKEEPER', - MANAGEMENT: 'MANAGEMENT', - OPERATIONS: 'OPERATIONS' - }, - - NodeState: { - UP: 'UP', - DOWN: 'DOWN', - OVERLOAD: 'OVERLOAD', - MAINTENANCE: 'MAINTENANCE' - }, - - RecordingInfoState: { - INITIAL: 'INITIAL', // no recording was ever started - START_PENDING: 'START_PENDING', // recording was started, but recorder was disabled(e.g. by curtain). recording is not running - STARTED: 'STARTED', // recording was started and is running currently - STOPPED: 'STOPPED', // recording was stopped - FINISHED: 'FINISHED' // recording was finally stopped(on session terminate) - }, - - RecordingInfoReason: { - NONE: 'NONE', - STOPPED_MANUALLY: 'STOPPED_MANUALLY', - STOPPED_AUTOMATICALLY: 'STOPPED_AUTOMATICALLY', - NO_INPUT_TIMEOUT: 'NO_INPUT_TIMEOUT', - MAX_INPUT_TIMEOUT: 'MAX_INPUT_TIMEOUT', - NO_STREAMING_DATA: 'NO_STREAMING_DATA', - LENGTH_LIMIT_REACHED: 'LENGTH_LIMIT_REACHED', - NO_MORE_DISK_SPACE_LEFT: 'NO_MORE_DISK_SPACE_LEFT', - UNKNOWN_ERROR: 'UNKNOWN_ERROR' - }, - - RecordingTriggerReason: { - USER: 'USER', - CURTAIN: 'CURTAIN' - }, - - RtcClientInfoType: { - OFFLINE_JOIN_FAILURE: 'OFFLINE_JOIN_FAILURE', - DEVICE_DIAGNOSTICS: 'DEVICE_DIAGNOSTICS' - }, - - RtcActionInfoType: { - REQUEST_RESPONSE: 'REQUEST_RESPONSE', - EVENT: 'EVENT' - }, - - RtcDiagnosticsAction: { - // REQUEST_RESPONSE - RENEW_TURN_CREDENTIALS: 'RENEW_TURN_CREDENTIALS', - PREPARE: 'PREPARE', - JOIN: 'JOIN', - ICE_CANDIDATES: 'ICE_CANDIDATES', - - // EVENT - SDP_CONNECTED: 'SDP_CONNECTED', - SDP_ANSWER: 'SDP_ANSWER', - REMOTE_ICE_CANDIDATES: 'REMOTE_ICE_CANDIDATES' - }, - - RtcClientInfoReason: { - DISCONNECTED: 'DISCONNECTED', - FAILED_TO_SEND: 'FAILED_TO_SEND', - REQUEST_TIMEOUT: 'REQUEST_TIMEOUT', - JOIN_DELAY: 'JOIN_DELAY' - }, - - /////////////////////////////////////////////////////////////////////////// - // Search Action - /////////////////////////////////////////////////////////////////////////// - SearchActionType: { - START_BASIC_SEARCH: 'START_BASIC_SEARCH', - START_DETAIL_SEARCH: 'START_DETAIL_SEARCH', - START_USER_SEARCH: 'START_USER_SEARCH', - CANCEL_SEARCH: 'CANCEL_SEARCH', - ADD_RECENT_SEARCH: 'ADD_RECENT_SEARCH', - GET_RECENT_SEARCHES: 'GET_RECENT_SEARCHES', - SEARCH_CONVERSATION_PARTICIPANTS: 'SEARCH_CONVERSATION_PARTICIPANTS' - }, - - /////////////////////////////////////////////////////////////////////////// - // Search Event - /////////////////////////////////////////////////////////////////////////// - SearchEventType: { - BASIC_SEARCH_RESULT: 'BASIC_SEARCH_RESULT', - DETAILED_SEARCH_RESULT: 'DETAILED_SEARCH_RESULT', - SEARCH_STATUS: 'SEARCH_STATUS', - RECENT_SEARCH_ADDED: 'RECENT_SEARCH_ADDED' - }, - - /////////////////////////////////////////////////////////////////////////// - // System Action - /////////////////////////////////////////////////////////////////////////// - SystemActionType: { - GET_GLOBAL_PROPERTY: 'GET_GLOBAL_PROPERTY', - GET_GLOBAL_PROPERTIES: 'GET_GLOBAL_PROPERTIES' - }, - - /////////////////////////////////////////////////////////////////////////// - // System Event - /////////////////////////////////////////////////////////////////////////// - SystemEventType: { - MAINTENANCE: 'MAINTENANCE', - MAINTENANCE_REMOVED: 'MAINTENANCE_REMOVED', - MAINTENANCE_ADDED: 'MAINTENANCE_ADDED' - }, - - /////////////////////////////////////////////////////////////////////////// - // System Data - /////////////////////////////////////////////////////////////////////////// - GlobalPropertyName: { - ATTACHMENT_UPLOAD_MAX_SIZE: 'ATTACHMENT_UPLOAD_MAX_SIZE', - CONV_PARTICIPANTS_MAX_SIZE: 'CONV_PARTICIPANTS_MAX_SIZE', - CONV_PARTICIPANTS_ADD_LIMIT: 'CONV_PARTICIPANTS_ADD_LIMIT', - XMPP_FEDERATION_ENABLED: 'XMPP_FEDERATION_ENABLED', - CLIENT_AUTOUPDATE_MAX_TIME: 'CLIENT_AUTOUPDATE_MAX_TIME' - }, - - /////////////////////////////////////////////////////////////////////////// - // Search Data - /////////////////////////////////////////////////////////////////////////// - SearchTypes: { - CONVERSATION: 'CONVERSATION', - USER: 'USER' - }, - - SearchStatusCode: { - ERROR: 'ERROR', - CANCELED: 'CANCELED', - FINISHED: 'FINISHED', - TIMEOUT: 'TIMEOUT', - NO_RESULT: 'NO_RESULT', - MORE_RESULT: 'MORE_RESULT' - }, - - SearchResultType: { - TOPIC_MATCH: 'TOPIC_MATCH', - USER_MATCH: 'USER_MATCH', - ATTACHMENT_MATCH: 'ATTACHMENT_MATCH', - ITEM_MATCH: 'ITEM_MATCH' - }, - - SearchScope: { - ALL: 'ALL', - PEOPLE: 'PEOPLE', - MEMBERS: 'MEMBERS', - MESSAGES: 'MESSAGES', // not supported anymore but still needed for saved recent searches - CONVERSATIONS: 'CONVERSATIONS', - FILES: 'FILES', - SENT_BY: 'SENT_BY', - DATE: 'DATE', - LABEL: 'LABEL', - FILTER: 'FILTER' - }, - - /////////////////////////////////////////////////////////////////////////// - // User Action - /////////////////////////////////////////////////////////////////////////// - UserActionType: { - WAKE_UP: 'WAKE_UP', // Used between clients and Access Server. Not part of official API. - GO_TO_SLEEP: 'GO_TO_SLEEP', // Used between clients and Access Server. Not part of official API. - LOGON: 'LOGON', - LOGOUT: 'LOGOUT', - RENEW_TOKEN: 'RENEW_TOKEN', - GET_USER_BY_ID: 'GET_USER_BY_ID', - UPDATE: 'UPDATE', - GET_LOGGED_ON: 'GET_LOGGED_ON', - GET_STUFF: 'GET_STUFF', - SET_PRESENCE: 'SET_PRESENCE', - GET_PRESENCE: 'GET_PRESENCE', - SUBSCRIBE_PRESENCE: 'SUBSCRIBE_PRESENCE', - UNSUBSCRIBE_PRESENCE: 'UNSUBSCRIBE_PRESENCE', - CHANGE_PASSWORD: 'CHANGE_PASSWORD', - GET_USERS_BY_IDS: 'GET_USERS_BY_IDS', - GET_DEVICES: 'GET_DEVICES', - GET_USER_SETTINGS: 'GET_USER_SETTINGS', - SET_USER_SETTINGS: 'SET_USER_SETTINGS', - GET_USER_BY_MAIL: 'GET_USER_BY_MAIL', - GET_USERS_BY_MAILS: 'GET_USERS_BY_MAILS', - EMAIL_UPDATE: 'EMAIL_UPDATE', - GENERATE_EMAIL_UPDATE_TOKEN: 'GENERATE_EMAIL_UPDATE_TOKEN', - GET_TELEPHONY_DATA: 'GET_TELEPHONY_DATA', - GET_USER_EXTENSIONS: 'GET_USER_EXTENSIONS', - SET_USER_EXTENSION_STATE: 'SET_USER_EXTENSION_STATE', - GET_LDAP_TECHNICAL_USER_PRESENCE: 'GET_LDAP_TECHNICAL_USER_PRESENCE', - GET_ATTACHMENTS: 'GET_ATTACHMENTS', - SET_PASSWORD: 'SET_PASSWORD', - GET_TENANT_SETTINGS: 'GET_TENANT_SETTINGS', - SUBSCRIBE_DEVICES: 'SUBSCRIBE_DEVICES', - UNSUBSCRIBE_DEVICES: 'UNSUBSCRIBE_DEVICES', - OAUTH_GET_GRANTED_ACCESS_TOKENS: 'OAUTH_GET_GRANTED_ACCESS_TOKENS', - OAUTH_REVOKE_ACCESS_TOKEN: 'OAUTH_REVOKE_ACCESS_TOKEN', - REVOKE_MANAGED_DEVICES: 'REVOKE_MANAGED_DEVICES', - GET_SECURITY_TOKEN_INFO: 'GET_SECURITY_TOKEN_INFO', - REVOKE_SECURITY_TOKEN: 'REVOKE_SECURITY_TOKEN', - RESET_OPENSCAPE_DEVICE_PINS: 'RESET_OPENSCAPE_DEVICE_PINS' - }, - - /////////////////////////////////////////////////////////////////////////// - // User Event - /////////////////////////////////////////////////////////////////////////// - UserEventType: { - ATC_REFRESH_FAILED: 'ATC_REFRESH_FAILED', // Used between clients and Access Server. Not part of official API. - USER_PRESENCE_CHANGE: 'USER_PRESENCE_CHANGE', - USER_UPDATED: 'USER_UPDATED', - SESSION_EXPIRES: 'SESSION_EXPIRES', - SESSION_EXPIRING: 'SESSION_EXPIRING', - SESSION_CLOSED: 'SESSION_CLOSED', - PASSWORD_CHANGED: 'PASSWORD_CHANGED', - USER_SETTING_UPDATED: 'USER_SETTING_UPDATED', - TELEPHONY_DATA: 'TELEPHONY_DATA', - DEVICE_LIST_CHANGED: 'DEVICE_LIST_CHANGED', - NOTIFICATION_SUBSCRIPTION_CHANGE: 'NOTIFICATION_SUBSCRIPTION_CHANGE', - TENANT_CONFIGURATION_UPDATED: 'TENANT_CONFIGURATION_UPDATED', - UCAAS_DEVICE_UPDATED: 'UCAAS_DEVICE_UPDATED' - }, - - /////////////////////////////////////////////////////////////////////////// - // User Data - /////////////////////////////////////////////////////////////////////////// - GetStuffType: { - USER: 'USER', - ACCOUNTS: 'ACCOUNTS', - FIRST_WEB_LOGIN: 'FIRST_WEB_LOGIN', - PRESENCE_STATE: 'PRESENCE_STATE', - RECENT_SEARCHES: 'RECENT_SEARCHES', - SETTINGS: 'SETTINGS', - TENANT_DATA: 'TENANT_DATA', - SUPPORT_CONVERSATION_ID: 'SUPPORT_CONVERSATION_ID', - TELEPHONY_CONVERSATION_ID: 'TELEPHONY_CONVERSATION_ID', - GLOBAL_PROPERTIES: 'GLOBAL_PROPERTIES', - TENANT: 'TENANT', - NOTIFICATION_SUBSCRIPTIONS: 'NOTIFICATION_SUBSCRIPTIONS', - PENDING_SYSTEM_NOTIFICATIONS: 'PENDING_SYSTEM_NOTIFICATIONS' - }, - - NotificationSubscriptionType: { - ONLINE_STATUS: 'ONLINE_STATUS' - }, - - NotificationSubscriptionAction: { - SUBSCRIBE: 'SUBSCRIBE', - UNSUBSCRIBE: 'UNSUBSCRIBE' - }, - - DeviceType: { - PHONE: 'PHONE', - WEB: 'WEB', - APPLICATION: 'APPLICATION', - MOBILE: 'MOBILE', - SDK: 'SDK' - }, - - PresenceState: { - AVAILABLE: 'AVAILABLE', - OFFLINE: 'OFFLINE', - AWAY: 'AWAY', - BUSY: 'BUSY', - DND: 'DND' - }, - - ChangePasswordResult: { - OK: 'OK', - NEW_INVALID: 'NEW_INVALID', - OLD_INVALID: 'OLD_INVALID', - ALREADY_CHANGED: 'ALREADY_CHANGED', - // Used when the CHANGE_PASSWORD action returns an error - OPERATION_ERROR: 'OPERATION_ERROR' - }, - - SetPasswordResult: { - OK: 'OK', - NOT_OK: 'NOT_OK' - }, - - SessionClosedReason: { - NEW_CONNECTION_DETECTED: 'NEW_CONNECTION_DETECTED', - UPGRADE_REQUIRED: 'UPGRADE_REQUIRED', - SUSPENDED: 'SUSPENDED', - DELETED: 'DELETED', - TENANT_DELETED: 'TENANT_DELETED', - TENANT_SUSPENDED: 'TENANT_SUSPENDED' - }, - - // Reason in SESSION_RECORDING_STOPPED event - RecordingStoppedReason: { - LENGTH_LIMIT_REACHED: 'LENGTH_LIMIT_REACHED', - NO_INPUT: 'NO_INPUT' - }, - - RecordingUploadState: { - IN_PROGRESS: 'IN_PROGRESS', - RETRYING: 'RETRYING', - FINISHED: 'FINISHED', - FAILED: 'FAILED', - DELETED: 'DELETED' - }, - - UserRole: { - GUEST: 'GUEST', - USER: 'USER', - TENANT_ADMIN: 'TENANT_ADMIN', - SUPPORT: 'SUPPORT', - SYSTEM_ADMIN: 'SYSTEM_ADMIN', - TELEPHONY_CONNECTOR: 'TELEPHONY_CONNECTOR', - VIRTUAL_TELEPHONY_CONNECTOR: 'VIRTUAL_TELEPHONY_CONNECTOR', - SESSION_GUEST: 'SESSION_GUEST', - MEETING_POINT: 'MEETING_POINT', - BOT: 'BOT' - }, - - UserType: { - REGULAR: 'REGULAR', - GUEST: 'GUEST', - SUPPORT: 'SUPPORT', - TELEPHONY: 'TELEPHONY', - SESSION_GUEST: 'SESSION_GUEST', - MEETING_POINT: 'MEETING_POINT', - TECHNICAL: 'TECHNICAL', - BOT: 'BOT', - XMPP: 'XMPP' - }, - - UserState: { - /* User is just created or deleted */ - INACTIVE: 'INACTIVE', - /* User is active (but may still have to register) */ - ACTIVE: 'ACTIVE', - /* User is suspended */ - SUSPENDED: 'SUSPENDED', - /* User is deleted */ - DELETED: 'DELETED', - /* User is deleted and user data has been purged from DB */ - PURGED: 'PURGED' - }, - - Locale: { - EN_US: 'EN_US', - DE_DE: 'DE_DE', - ES_ES: 'ES_ES', - FR_FR: 'FR_FR', - IT_IT: 'IT_IT', - RU_RU: 'RU_RU', - ZH_HANS_CN: 'ZH_HANS_CN' - }, - - PhoneNumberType: { - WORK: 'WORK', - MOBILE: 'MOBILE', - HOME: 'HOME', - FAX: 'FAX', - OTHER: 'OTHER', - UCAAS: 'UCAAS' - }, - - PhoneNumberNpi: { - PUBLIC: 'PUBLIC', - PRIVATE: 'PRIVATE' - }, - - EmailAddressType: { - WORK: 'WORK', - HOME: 'HOME', - OTHER: 'OTHER' - }, - - UserSettingArea: { - ALL: 'ALL', - EMAIL: 'EMAIL', - DESKTOP: 'DESKTOP', - PRIVACY: 'PRIVACY', - CLIENT: 'CLIENT' - }, - - UserSettingDataType: { - BOOLEAN: 'BOOLEAN', - NUMBER: 'NUMBER', - STRING: 'STRING', - TIMESTAMP: 'TIMESTAMP' - }, - - UserSettingKey: { - EMAIL_INVITES: 'EMAIL_INVITES', - EMAIL_MISSED_RTC: 'EMAIL_MISSED_RTC', - EMAIL_SUMMARY: 'EMAIL_SUMMARY', - EMAIL_UPDATE: 'EMAIL_UPDATE', - DESKTOP_RTC: 'DESKTOP_RTC', - DESKTOP_MISSED_MESSAGE: 'DESKTOP_MISSED_MESSAGE', - DESKTOP_CONVERSATION_CHANGE: 'DESKTOP_CONVERSATION_CHANGE', - DESKTOP_SYSINFO: 'DESKTOP_SYSINFO', - SHARE_LOCATION: 'SHARE_LOCATION', - RELEASE_NOTES_WEB_VERSION: 'RELEASE_NOTES_WEB_VERSION', - RELEASE_NOTES_MOBILE_VERSION: 'RELEASE_NOTES_MOBILE_VERSION', - TM_WEB_START_CONV_WITH: 'TM_WEB_START_CONV_WITH', - TM_WEB_ENTER_NAME: 'TM_WEB_ENTER_NAME', - TM_WEB_CALL_ACTION_BUTTONS: 'TM_WEB_CALL_ACTION_BUTTONS', - TM_WEB_SUPPORT_FEEDBACK: 'TM_WEB_SUPPORT_FEEDBACK', // Not used - TM_WEB_SUPPORT_ISSUE: 'TM_WEB_SUPPORT_ISSUE', // Not used - TM_WEB_SCREEN_SHARING_ENABLED: 'TM_WEB_SCREEN_SHARING_ENABLED', // Not used - TM_WEB_SEARCH_FILTER: 'TM_WEB_SEARCH_FILTER', - AUDIO_WEB_SOUNDS: 'AUDIO_WEB_SOUNDS', - TM_WEB_OPEN_CONVERSATION: 'TM_WEB_OPEN_CONVERSATION', - TM_WEB_ADD_CONVERSATION_TITLE: 'TM_WEB_ADD_CONVERSATION_TITLE', // Not used - TM_WEB_RICH_TEXT_EDITING: 'TM_WEB_RICH_TEXT_EDITING', - TM_WEB_MUTE_CONVERSATION: 'TM_WEB_MUTE_CONVERSATION', // Not used - TM_WEB_VIEW_MUTED_CONVERSATIONS: 'TM_WEB_VIEW_MUTED_CONVERSATIONS', // Not used - TM_WEB_LEAVING_CONVERSATION: 'TM_WEB_LEAVING_CONVERSATION', - TM_WEB_FOCUS_MODE: 'TM_WEB_FOCUS_MODE', // Deprecated - TM_WEB_CONFERENCE_DIAL_IN: 'TM_WEB_CONFERENCE_DIAL_IN', - TM_WEB_REMOVING_PARTICIPANTS: 'TM_WEB_REMOVING_PARTICIPANTS', - TM_WEB_FULL_SCREEN_MODES: 'TM_WEB_FULL_SCREEN_MODES', - TM_WEB_ADD_MULTIPLE_USERS: 'TM_WEB_ADD_MULTIPLE_USERS', - TM_WEB_THIRD_COLUMN: 'TM_WEB_THIRD_COLUMN', // Deprecated - TM_WEB_RESERVE_1: 'TM_WEB_RESERVE_1', - TM_WEB_RESERVE_2: 'TM_WEB_RESERVE_2', - TM_IOS_SEARCH: 'TM_IOS_SEARCH', - MOBILE_ENABLE_VIDEO_WIFI_ONLY: 'MOBILE_ENABLE_VIDEO_WIFI_ONLY', - TM_WEB_ADMIN_TELEPHONY_ENABLED: 'TM_WEB_ADMIN_TELEPHONY_ENABLED', - TM_WEB_TELPHONY_NUMBER_ASSIGNED: 'TM_WEB_TELPHONY_NUMBER_ASSIGNED', - TM_WEB_ACTIVE_SPEAKER_VIDEO: 'TM_WEB_ACTIVE_SPEAKER_VIDEO', // Not used - TM_WEB_FIRST_DIRECT_CONVERSATION: 'TM_WEB_FIRST_DIRECT_CONVERSATION', // Not used - TM_WEB_FIRST_GROUP_CONVERSATION: 'TM_WEB_FIRST_GROUP_CONVERSATION', - TM_WEB_FIRST_CONV_ADD_PARTCPNTS: 'TM_WEB_FIRST_CONV_ADD_PARTCPNTS', - TM_WEB_FIRST_CONV_SET_NAME: 'TM_WEB_FIRST_CONV_SET_NAME', - TM_WEB_FIRST_CONV_START_CONV: 'TM_WEB_FIRST_CONV_START_CONV', - TM_WEB_INVITE_USERS: 'TM_WEB_INVITE_USERS', - TM_WEB_EMERGENCY_CALL_DISCLAIMER: 'TM_WEB_EMERGENCY_CALL_DISCLAIMER', - TM_WEB_FIRST_DIRECT_PRIVATE_CONV: 'TM_WEB_FIRST_DIRECT_PRIVATE_CONV', - TM_WEB_FIRST_DIRECT_CONV_SAY_HI: 'TM_WEB_FIRST_DIRECT_CONV_SAY_HI', - TM_WEB_FIRST_DIRECT_CONV_ADD: 'TM_WEB_FIRST_DIRECT_CONV_ADD', - TM_WEB_GUEST_ACCESS: 'TM_WEB_GUEST_ACCESS', - TM_WEB_SHARE_CONFERENCE_DETAILS: 'TM_WEB_SHARE_CONFERENCE_DETAILS', - TM_WEB_START_RECORDING: 'TM_WEB_START_RECORDING', - TM_WEB_STOP_RECORDING_INIT: 'TM_WEB_STOP_RECORDING_INIT', - TM_WEB_STOP_RECORDING_PART: 'TM_WEB_STOP_RECORDING_PART', - TM_WEB_FIRST_CONF_RING_ALL: 'TM_WEB_FIRST_CONF_RING_ALL', - TM_WEB_FIRST_CALL_ACTIONS: 'TM_WEB_FIRST_CALL_ACTIONS', - TM_WEB_FIRST_CALL_GO_TO_FEED: 'TM_WEB_FIRST_CALL_GO_TO_FEED', - TM_WEB_FIRST_CALL_FULLSCREEN: 'TM_WEB_FIRST_CALL_FULLSCREEN', - FIRST_WEB_LOGIN_TIMESTAMP: 'FIRST_WEB_LOGIN_TIMESTAMP', - VOICEMAIL_ENABLED: 'VOICEMAIL_ENABLED', - VOICEMAIL_TIMEOUT: 'VOICEMAIL_TIMEOUT', - STATUS_MESSAGE_TEXT: 'STATUS_MESSAGE_TEXT', - OPT_OUT_PRESENCE: 'OPT_OUT_PRESENCE', - SECOND_TELEPHONY_CALL_ROUTING: 'SECOND_TELEPHONY_CALL_ROUTING', - ATC_ROUTING: 'ATC_ROUTING', - VOICEMAIL_CUSTOMGREETING_ENABLED: 'VOICEMAIL_CUSTOMGREETING_ENABLED', - VOICEMAIL_CUSTOMGREETING_URI: 'VOICEMAIL_CUSTOMGREETING_URI', - DESKTOP_MESSAGE_NOTIFICATIONS_SETTING: 'DESKTOP_MESSAGE_NOTIFICATIONS_SETTING', - MOBILE_MESSAGE_NOTIFICATIONS_SETTING: 'MOBILE_MESSAGE_NOTIFICATIONS_SETTING', - PLAY_SOUND_MESSAGE_NOTIFICATIONS: 'PLAY_SOUND_MESSAGE_NOTIFICATIONS', - PLAY_SOUND_RTC: 'PLAY_SOUND_RTC', - PLAY_SYSTEM_SOUNDS: 'PLAY_SYSTEM_SOUNDS' - }, - - OAuthScope: { - ALL: 'ALL', - CALLS: 'CALLS', - READ_CONVERSATIONS: 'READ_CONVERSATIONS', - READ_USER: 'READ_USER', - READ_USER_PROFILE: 'READ_USER_PROFILE', - WRITE_CONVERSATIONS: 'WRITE_CONVERSATIONS', - WRITE_USER_PROFILE: 'WRITE_USER_PROFILE' - }, - - OAuthGrantTypes: { - AUTHORIZATION_CODE: 'AUTHORIZATION_CODE', - IMPLICIT: 'IMPLICIT', - CLIENT_CREDENTIALS: 'CLIENT_CREDENTIALS', - RESOURCE_OWNER_CREDENTIALS: 'RESOURCE_OWNER_CREDENTIALS', - RESOURCE_OWNER: 'RESOURCE_OWNER' - }, - - MessageNotificationsSetting: { - ALL: 0, - DIRECT_AND_MENTIONS: 1, - NONE: 2 - }, - - /////////////////////////////////////////////////////////////////////////// - // Administration Action - /////////////////////////////////////////////////////////////////////////// - AdministrationActionType: { - INVITE_USER: 'INVITE_USER', - ADD_USER: 'ADD_USER', - INVITE_MULTIPLE_USERS: 'INVITE_MULTIPLE_USERS', - DELETE_USER: 'DELETE_USER', - VALIDATE_USER_INVITATION_TOKEN: 'VALIDATE_USER_INVITATION_TOKEN', - ACTIVATE_INVITED_USER: 'ACTIVATE_INVITED_USER', - RESEND_USER_INVITATION: 'RESEND_USER_INVITATION', - CHECK_INVALID_TENANTS: 'CHECK_INVALID_TENANTS', - GET_TENANT: 'GET_TENANT', - GET_SHARED_KEY: 'GET_SHARED_KEY', - EXPORT_TENANT: 'EXPORT_TENANT', - GET_ALL_TENANT_LOGIN_PROVIDERS: 'GET_ALL_TENANT_LOGIN_PROVIDERS', - UPLOAD_EXTENSION_CONFIGURATION: 'UPLOAD_EXTENSION_CONFIGURATION', - GET_TENANT_EXTENSIONS: 'GET_TENANT_EXTENSIONS', - SET_EXTENSION_STATE: 'SET_EXTENSION_STATE', - DELETE_EXTENSION: 'DELETE_EXTENSION', - REGENERATE_API_KEY: 'REGENERATE_API_KEY', - SET_TELEPHONY_TRUNK_DATA: 'SET_TELEPHONY_TRUNK_DATA', - GET_TELEPHONY_TRUNK_STATUS: 'GET_TELEPHONY_TRUNK_STATUS', - GET_TENANT_BRIDGE_NUMBERS: 'GET_TENANT_BRIDGE_NUMBERS', - SET_TENANT_BRIDGE_NUMBERS: 'SET_TENANT_BRIDGE_NUMBERS', - ADD_TRUNK: 'ADD_TRUNK', - GET_TENANT_TELEPHONY_CONFIGURATION: 'GET_TENANT_TELEPHONY_CONFIGURATION', - ADD_TRUNK_WITHOUT_PARAMETERS: 'ADD_TRUNK_WITHOUT_PARAMETERS', - SET_TRUNK_PASSWORD: 'SET_TRUNK_PASSWORD', - UPDATE_TRUNK: 'UPDATE_TRUNK', - GET_TELEPHONY_ROUTING_RULES: 'GET_TELEPHONY_ROUTING_RULES', - ADD_TELEPHONY_ROUTING_RULE: 'ADD_TELEPHONY_ROUTING_RULE', - UPDATE_TELEPHONY_ROUTING_RULE: 'UPDATE_TELEPHONY_ROUTING_RULE', - REMOVE_TELEPHONY_ROUTING_RULE: 'REMOVE_TELEPHONY_ROUTING_RULE', - TEST_TELEPHONY_ROUTING_RULES: 'TEST_TELEPHONY_ROUTING_RULES', - UPDATE_TENANT_TELEPHONY_CONFIGURATION: 'UPDATE_TENANT_TELEPHONY_CONFIGURATION', - GET_TELEPHONY_TRUNK_GROUPS: 'GET_TELEPHONY_TRUNK_GROUPS', - GET_TELEPHONY_TRUNK_GROUP: 'GET_TELEPHONY_TRUNK_GROUP', - ADD_TELEPHONY_TRUNK_GROUP: 'ADD_TELEPHONY_TRUNK_GROUP', - REMOVE_TELEPHONY_TRUNK_GROUP: 'REMOVE_TELEPHONY_TRUNK_GROUP', - UPDATE_TELEPHONY_TRUNK_GROUP: 'UPDATE_TELEPHONY_TRUNK_GROUP', - SUSPEND_USER: 'SUSPEND_USER', - UNSUSPEND_USER: 'UNSUSPEND_USER', - ASSIGN_PHONE_NUMBER: 'ASSIGN_PHONE_NUMBER', - INVITE_MULTIPLE_PARTNER_USERS: 'INVITE_MULTIPLE_PARTNER_USERS', - ADD_MEETING_POINT: 'ADD_MEETING_POINT', - ADD_MEETING_POINT_V2: 'ADD_MEETING_POINT_V2', - UPDATE_MEETING_POINT_V2: 'UPDATE_MEETING_POINT_V2', - SET_TENANT_SETTINGS: 'SET_TENANT_SETTINGS', - UPDATE_TENANT: 'UPDATE_TENANT', - SAVE_TENANT_LOGIN_PROVIDER: 'SAVE_TENANT_LOGIN_PROVIDER', - UPDATE_TENANT_LOGIN_PROVIDER: 'UPDATE_TENANT_LOGIN_PROVIDER', - DELETE_TENANT_LOGIN_PROVIDER: 'DELETE_TENANT_LOGIN_PROVIDER', - GET_VACANT_HOME_DIRECTORY_NUMBERS: 'GET_VACANT_HOME_DIRECTORY_NUMBERS', - CREATE_OPENSCAPE_USER: 'CREATE_OPENSCAPE_USER', - UPDATE_OPENSCAPE_USER: 'UPDATE_OPENSCAPE_USER', - DELETE_OPENSCAPE_USER: 'DELETE_OPENSCAPE_USER', - GET_OPENSCAPE_USER: 'GET_OPENSCAPE_USER', - GET_OPENSCAPE_USER_V2: 'GET_OPENSCAPE_USER_V2', - GET_OPENSCAPE_TENANT: 'GET_OPENSCAPE_TENANT', - DELETE_OPENSCAPE_SITES: 'DELETE_OPENSCAPE_SITES', - CREATE_OPENSCAPE_SITE: 'CREATE_OPENSCAPE_SITE', - GET_OPENSCAPE_SITE: 'GET_OPENSCAPE_SITE', - UPDATE_OPENSCAPE_SITE: 'UPDATE_OPENSCAPE_SITE', - DELETE_PUBLIC_NUMBER_RANGE: 'DELETE_PUBLIC_NUMBER_RANGE', - DELETE_PRIVATE_NUMBER_RANGE: 'DELETE_PRIVATE_NUMBER_RANGE', - GET_AUTOMATEDATTENDANT_CONFIGURATION: 'GET_AUTOMATEDATTENDANT_CONFIGURATION', - SET_AUTOMATEDATTENDANT_CONFIGURATION: 'SET_AUTOMATEDATTENDANT_CONFIGURATION', - GET_CUSTOM_APPS: 'GET_CUSTOM_APPS', - ADD_CUSTOM_APP: 'ADD_CUSTOM_APP', - GET_PARTNER_MANAGEABLE_TENANTS: 'GET_PARTNER_MANAGEABLE_TENANTS', - GET_CMR_SETTINGS: 'GET_CMR_SETTINGS', - SET_CMR_SETTINGS: 'SET_CMR_SETTINGS' - }, - - InvitationResponseCode: { - INVITATION_SEND: 'INVITATION_SEND', - INVITATION_NOT_SEND: 'INVITATION_NOT_SEND', - INVALID_USER_STATE: 'INVALID_USER_STATE', - EMAIL_ALREADY_EXISTS: 'EMAIL_ALREADY_EXISTS', - ACCOUNT_ASSIGNMENT_FAILED: 'ACCOUNT_ASSIGNMENT_FAILED', - ERROR: 'ERROR', - SYSTEM_OVERLOADED: 'SYSTEM_OVERLOADED', - EMAIL_ALREADY_EXISTS_ON_SAME_TENANT: 'EMAIL_ALREADY_EXISTS_ON_SAME_TENANT', - UNTRUSTED_PARTNER_TENANT: 'UNTRUSTED_PARTNER_TENANT', - XMPP_FEDERATED_USER_NOT_FOUND: 'XMPP_FEDERATED_USER_NOT_FOUND', - LICENSE_LIMIT_EXCEEDED: 'LICENSE_LIMIT_EXCEEDED', - LICENSE_ERROR: 'LICENSE_ERROR' - }, - - InvitationStatusCode: { - RUNNING: 'RUNNING', - FINISHED: 'FINISHED' - }, - - GetUsersOrdering: { - ASCENDING: 'ASCENDING', - DESCENDING: 'DESCENDING' - }, - - GetUsersSorting: { - USER_NAME: 'USER_NAME', - DATA_USAGE: 'DATA_USAGE', - LAST_ACCESS: 'LAST_ACCESS', - ROLE: 'ROLE', // Not supported in Client API - STATUS: 'STATUS', // Not supported in Client API - PHONE_NUMBER_ASSIGNED: 'PHONE_NUMBER_ASSIGNED', // Not supported in Client API - EMAIL: 'EMAIL' // Meeting Point ID / Not supported in Client API - }, - - GetUsersSearchFor: { - ROLE: 'ROLE', - STATUS: 'STATUS', - NAME: 'NAME', - PRIMARY_EMAIL: 'PRIMARY_EMAIL' - }, - - SetRoleRole: { - ADMIN: 'ADMIN', - USER: 'USER' - }, - - ExportTenantFormat: { - CSV: 'CSV', - JSON: 'JSON' - }, - - /////////////////////////////////////////////////////////////////////////// - // Administration Event - /////////////////////////////////////////////////////////////////////////// - AdministrationEventType: { - INVITE_MULTIPLE_USERS: 'INVITE_MULTIPLE_USERS', - INVITE_MULTIPLE_PARTNER_USERS: 'INVITE_MULTIPLE_PARTNER_USERS' - }, - - /////////////////////////////////////////////////////////////////////////// - // Administration Data - /////////////////////////////////////////////////////////////////////////// - TenantSettingsType: { - CREDENTIALS_LOGIN_ENABLED: 'CREDENTIALS_LOGIN_ENABLED', - HELP_URL: 'HELP_URL', - PLUGIN_SELF_MANAGED_ENABLED: 'PLUGIN_SELF_MANAGED_ENABLED', - EXTENSION_MICROSOFT_EXCHANGE_ENABLED: 'EXTENSION_MICROSOFT_EXCHANGE_ENABLED', - EXTENSION_SYNCPLICITY_ENABLED: 'EXTENSION_SYNCPLICITY_ENABLED', - EXTENSION_BOX_ENABLED: 'EXTENSION_BOX_ENABLED', - EXTENSION_GOOGLE_DRIVE_ENABLED: 'EXTENSION_GOOGLE_DRIVE_ENABLED', - EXTENSION_ONE_DRIVE_ENABLED: 'EXTENSION_ONE_DRIVE_ENABLED', - EXTENSION_JABRA_ENABLED: 'EXTENSION_JABRA_ENABLED', - EXTENSION_VDI_ENABLED: 'EXTENSION_VDI_ENABLED', - EXTENSION_PLANTRONICS_ENABLED: 'EXTENSION_PLANTRONICS_ENABLED', - EXTENSION_SKYPE_FOR_BUSINESS_ENABLED: 'EXTENSION_SKYPE_FOR_BUSINESS_ENABLED', - DT_LEGAL_INFORMATION: 'DT_LEGAL_INFORMATION', - SSO_REDIRECT_URL: 'SSO_REDIRECT_URL', - PHONE_DIALOUT_CALLER_ID_PREFIX: 'PHONE_DIALOUT_CALLER_ID_PREFIX', - PHONE_DIALOUT_MODE: 'PHONE_DIALOUT_MODE', - PHONE_DIALOUT_ENABLED: 'PHONE_DIALOUT_ENABLED', - SKYPE4B_CLIENT_ID: 'SKYPE4B_CLIENT_ID', - SKYPE4B_ROOT_URL: 'SKYPE4B_ROOT_URL', - DA_AUTO_UPDATE: 'DA_AUTO_UPDATE', - EXTENSION_SENNHEISER_ENABLED: 'EXTENSION_SENNHEISER_ENABLED' - }, - - TenantConfigurableTextType: { - GUEST_ACCESS_ACCEPTANCE_TEXT: 'GUEST_ACCESS_ACCEPTANCE_TEXT', - USER_INVITE_ACCEPTANCE_TEXT: 'USER_INVITE_ACCEPTANCE_TEXT' - }, - - TrunkState: { - UP: 'UP', - DOWN: 'DOWN', - NOT_REQUESTED: 'NOT_REQUESTED', - REQUESTED: 'REQUESTED' - }, - - GtcTrunkType: { - GTC_TRUNK_TYPE: 'GTC_TRUNK_TYPE', - ETC_TRUNK_TYPE: 'ETC_TRUNK_TYPE', - ATC_TRUNK_TYPE: 'ATC_TRUNK_TYPE', - EMBEDDED_TC_TRUNK_TYPE: 'EMBEDDED_TC_TRUNK_TYPE', - OSBIZ_TRUNK_TYPE: 'OSBIZ_TRUNK_TYPE' - }, - - TelephonyRoutingRuleCriteria: { - ORIGIN: 'ORIGIN', - DESTINATION: 'DESTINATION' - }, - - TelephonyRoutingRuleOperator: { - AND: 'AND', - OR: 'OR' - }, - - TelephonyRoutingRuleApplication: { - TELEPHONY: 'TELEPHONY', - CONF_DIAL_OUT: 'CONF_DIAL_OUT', - BOTH: 'BOTH' - }, - - TelephonyConfigurationDefaultRouting: { - ANY: 'ANY', - NONE: 'NONE' - }, - - TelephonyRoutingRuleResult: { - NO_MATCH: 'NO_MATCH', - MATCH: 'MATCH', - SELECTED: 'SELECTED' - }, - - ProviderType: { - CREDENTIALS: 'CREDENTIALS', - SAML: 'SAML', - OPENID: 'OPENID' - }, - - PhoneDialoutMode: { - PHONE_USERS_ONLY: 'PHONE_USERS_ONLY', - ALL_USERS: 'ALL_USERS' - }, - - RemoveTrunkGroupResult: { - SUCCESS: 'SUCCESS', - REFERENCES_EXIST: 'REFERENCES_EXIST', - UNKNOWN_ERROR: 'UNKNOWN_ERROR' - }, - - CMRSettingKey: { - CMR_ADDRESS: 'CMR_ADDRESS' - }, - - UcaasDeviceStatus: { - NO_DEVICE: 'NO_DEVICE', - NOT_CONFIGURED: 'NOT_CONFIGURED', - CONFIGURED: 'CONFIGURED' - }, - - /////////////////////////////////////////////////////////////////////////// - // Third Party Connectors - /////////////////////////////////////////////////////////////////////////// - ThirdPartyConnectorsActionType: { - GET_SETTINGS: 'GET_SETTINGS', - SAVE_SETTINGS: 'SAVE_SETTINGS', - CONNECT: 'CONNECT', - DISCONNECT: 'DISCONNECT', - SEND_AUTH_CODE: 'SEND_AUTH_CODE', - GET_CONNECTION_STATUS: 'GET_CONNECTION_STATUS', - GET_FOLDER_ITEMS: 'GET_FOLDER_ITEMS', - SHARE_FOLDER_ITEMS: 'SHARE_FOLDER_ITEMS', - UNSHARE_FOLDER_ITEMS: 'UNSHARE_FOLDER_ITEMS', - GET_EDIT_URL: 'GET_EDIT_URL', - GET_FOLDER_ITEMS_WITH_CURSOR: 'GET_FOLDER_ITEMS_WITH_CURSOR', - CONNECT_STORAGE: 'CONNECT_STORAGE', - REFRESH_CONNECTION: 'REFRESH_CONNECTION' - }, - - ThirdPartyConnectorType: { - EXCHANGE_CONNECTOR: 'EXCHANGE_CONNECTOR', - BOX: 'BOX', - OPEN_XCHANGE: 'OPEN_XCHANGE', - GOOGLE_DRIVE: 'GOOGLE_DRIVE', - SYNCPLICITY: 'SYNCPLICITY', - ONE_DRIVE: 'ONE_DRIVE', - CLIENT_THIRDPARTY: 'CLIENT_THIRDPARTY' - }, - - /////////////////////////////////////////////////////////////////////////// - // Third Party Event - /////////////////////////////////////////////////////////////////////////// - ThirdPartyEventType: { - CONNECTED_TO_THIRDPARTY: 'CONNECTED_TO_THIRDPARTY', - DISCONNECTED_FROM_THIRDPARTY: 'DISCONNECTED_FROM_THIRDPARTY' - }, - - /////////////////////////////////////////////////////////////////////////// - // Third Party Content types - /////////////////////////////////////////////////////////////////////////// - ThirdPartyContentType: { - SKYPE4B: 'SKYPE4B' - }, - - Skype4BAuthentication: { - BASIC: 'BASIC', - AZUREAD: 'AZUREAD' - }, - - /////////////////////////////////////////////////////////////////////////// - // Guest Action - /////////////////////////////////////////////////////////////////////////// - GuestActionType: { - VALIDATE_SESSION_INVITE_TOKEN: 'VALIDATE_SESSION_INVITE_TOKEN', - REGISTER_SESSION_GUEST: 'REGISTER_SESSION_GUEST' - }, - - SessionInviteInfoResult: { - INVALID: 'INVALID', - VALID_NOSESSION: 'VALID_NOSESSION', - VALID_SESSION: 'VALID_SESSION', - VALID_NO_GUEST_ACCESS: 'VALID_NO_GUEST_ACCESS' - }, - - /////////////////////////////////////////////////////////////////////////// - // User to User info Action - /////////////////////////////////////////////////////////////////////////// - UserRoutingMessageType: { - ATC: 'ATC', - CMP: 'CMP', - CONTACT_CARD: 'CONTACT_CARD', - DESKTOP_APP: 'DESKTOP_APP', - MOBILE_BREAKOUT: 'MOBILE_BREAKOUT', - SDK: 'SDK' - }, - - /////////////////////////////////////////////////////////////////////////// - // Version Action - /////////////////////////////////////////////////////////////////////////// - VersionActionType: { - GET_VERSION: 'GET_VERSION' // Message between client and access server - }, - - /////////////////////////////////////////////////////////////////////////// - // Account Action - /////////////////////////////////////////////////////////////////////////// - AccountActionType: { - GET_PACKAGE_TEMPLATES: 'GET_PACKAGE_TEMPLATES', - GET_ASSIGNED_PACKAGES: 'GET_ASSIGNED_PACKAGES', - VALIDATE_ASSIGN_ACCOUNT_TEMPLATE: 'VALIDATE_ASSIGN_ACCOUNT_TEMPLATE', - ASSIGN_ACCOUNT_TEMPLATE: 'ASSIGN_ACCOUNT_TEMPLATE', - VALIDATE_MIGRATE_ACCOUNT: 'VALIDATE_MIGRATE_ACCOUNT', - MIGRATE_ACCOUNT: 'MIGRATE_ACCOUNT', - MIGRATE_MULTIPLE_USERS: 'MIGRATE_MULTIPLE_USERS', - GET_ACCOUNTS: 'GET_ACCOUNTS', - GET_SEARCH_TAGS: 'GET_SEARCH_TAGS', - SUSPEND_ACCOUNT: 'SUSPEND_ACCOUNT', - UNSUSPEND_ACCOUNT: 'UNSUSPEND_ACCOUNT', - ASSIGN_TELEPHONY_CONFIGURATION: 'ASSIGN_TELEPHONY_CONFIGURATION', - DELETE_TELEPHONY_CONFIGURATION: 'DELETE_TELEPHONY_CONFIGURATION', - GET_ACCOUNT_BY_ID: 'GET_ACCOUNT_BY_ID', - GET_ACCOUNT_BY_USER_ID: 'GET_ACCOUNT_BY_USER_ID', - GET_TECHNICAL_ADMIN_USER_ID: 'GET_TECHNICAL_ADMIN_USER_ID', - RENEW_ASSOCIATED_TELEPHONY_USER: 'RENEW_ASSOCIATED_TELEPHONY_USER', - ADD_ACCOUNT_PERMISSION: 'ADD_ACCOUNT_PERMISSION', - REMOVE_ACCOUNT_PERMISSION: 'REMOVE_ACCOUNT_PERMISSION' - }, - - /////////////////////////////////////////////////////////////////////////// - // UserData Action - /////////////////////////////////////////////////////////////////////////// - UserDataActionType: { - GET_USER_DATA: 'GET_USER_DATA' - }, - - /////////////////////////////////////////////////////////////////////////// - // Account Event - /////////////////////////////////////////////////////////////////////////// - AccountEventType: { - MIGRATE_MULTIPLE_USERS: 'MIGRATE_MULTIPLE_USERS', - TELEPHONY_CONFIGURATION_UPDATED: 'TELEPHONY_CONFIGURATION_UPDATED' - }, - - /////////////////////////////////////////////////////////////////////////// - // Account Data - /////////////////////////////////////////////////////////////////////////// - - // The templateName is defined as a string in the protobuf. - // The definition below only includes the core template names that the client - // needs to know about. The USER and ADMIN account names have been created - // for backwards compatibility to match with the previous roles concept. The - // clients should avoid adding any logic based on these template names since they - // could change in the future. - // The MEETING_POINT template name, on the other hand, MUST be used for all Circuit - // Meeting Point (CMP) user accounts and it will be used by the clients whenever it - // needs to query the available CMPs in the system. - AccountTemplateName: { - USER: 'USER', - ADMIN: 'ADMIN', - MEETING_POINT: 'MEETING_POINT', - XMPP_FEDERATED_USER: 'XMPP_FEDERATED_USER' - }, - - AccountStatus: { - NONE: 'NONE', - CREATED: 'CREATED', - INITIAL: 'INITIAL', - INVITED: 'INVITED', - ACTIVE: 'ACTIVE', - SUSPENDED: 'SUSPENDED', - DELETED: 'DELETED' - }, - - GetAccountsOrdering: { - ASCENDING: 'ASCENDING', - DESCENDING: 'DESCENDING' - }, - - GetAccountsSorting: { - BY_FIRST_NAME: 'BY_FIRST_NAME', - BY_LAST_NAME: 'BY_LAST_NAME', - BY_NAME: 'BY_NAME', - BY_PRIMARY_EMAIL: 'BY_PRIMARY_EMAIL', - BY_PHONE_NUMBER: 'BY_PHONE_NUMBER', - BY_LAST_LOGIN: 'BY_LAST_LOGIN', - BY_LOCATION: 'BY_LOCATION' - }, - - GetAccountsFilterCriteria: { - PRIMARY_EMAIL: 'PRIMARY_EMAIL', - FIRST_NAME: 'FIRST_NAME', - LAST_NAME: 'LAST_NAME', - DISPLAY_NAME: 'DISPLAY_NAME', - STATUS: 'STATUS', - TAG: 'TAG' - }, - - SystemPermission: { - IGNORE: 'IGNORE', - CLIENT_LOGON: 'CLIENT_LOGON', - INVITE_USER: 'INVITE_USER', - ROLE_TELEPHONY_CONNECTOR: 'ROLE_TELEPHONY_CONNECTOR', - ROLE_VIRTUAL_TELEPHONY_CONNECTOR: 'ROLE_VIRTUAL_TELEPHONY_CONNECTOR', - ROLE_TECHNICAL: 'ROLE_TECHNICAL', - ROLE_SESSION_GUEST: 'ROLE_SESSION_GUEST', - ROLE_USER: 'ROLE_USER', - ROLE_TENANT_ADMIN: 'ROLE_TENANT_ADMIN', - ROLE_SUPPORT: 'ROLE_SUPPORT', - ROLE_SYSTEM_ADMIN: 'ROLE_SYSTEM_ADMIN', - MANAGE_TENANT: 'MANAGE_TENANT', - TELEPHONY: 'TELEPHONY', - RECORDING: 'RECORDING', - MODERATION: 'MODERATION', - GUEST_ACCESS: 'GUEST_ACCESS', - MAX_PACKAGE_ACCOUNTS: 'MAX_PACKAGE_ACCOUNTS', - FREE_TRIAL: 'FREE_TRIAL', - USER_STORAGE: 'USER_STORAGE', - INTEGRATIONS: 'INTEGRATIONS', - RTC_PARTICIPANTS: 'RTC_PARTICIPANTS', - ACCOUNTS: 'ACCOUNTS', - IE_PLUGIN_SUPPORT: 'IE_PLUGIN_SUPPORT', - SUPPORT_CONVERSATION: 'SUPPORT_CONVERSATION', - SUPPORT_FORUM: 'SUPPORT_FORUM', - OUTLOOK_PLUGIN: 'OUTLOOK_PLUGIN', - TENANT_STORAGE: 'TENANT_STORAGE', - PSTN_DIAL_IN: 'PSTN_DIAL_IN', - LDAP_AGENT: 'LDAP_AGENT', - INVITE_PARTNER: 'INVITE_PARTNER', // Permission to invite users in a partner domain - LARGE_CONFERENCE: 'LARGE_CONFERENCE', - CREATE_COMMUNITY: 'CREATE_COMMUNITY', - CONTENT_MODERATOR: 'CONTENT_MODERATOR', - ANALYTICS: 'ANALYTICS', - VIEW_OPEN_CONVERSATION: 'VIEW_OPEN_CONVERSATION', - SSO: 'SSO', - IE_PLUGIN_SUPPORT_LIMITATION: 'IE_PLUGIN_SUPPORT_LIMITATION', - ROLE_MEETING_POINT: 'ROLE_MEETING_POINT', - MOBILE_BREAKOUT: 'MOBILE_BREAKOUT', - UCAAS: 'UCAAS', - PARTNER_ADMIN: 'PARTNER_ADMIN', - DEVELOPER_CONSOLE_ACCESS: 'DEVELOPER_CONSOLE_ACCESS', - S4B_INTEGRATION: 'S4B_INTEGRATION' - }, - - PermissionType: { - TENANT_QUOTA: 'TENANT_QUOTA', - USER_QUOTA: 'USER_QUOTA', - FEATURE: 'FEATURE', - OVERALL_PACKAGE_QUOTA: 'OVERALL_PACKAGE_QUOTA', - OVERALL_PACKAGE_FEATURE: 'OVERALL_PACKAGE_FEATURE', - RESTRICTION: 'RESTRICTION' - }, - - MigrateMultipleStatusCode: { - RUNNING: 'RUNNING', - FINISHED: 'FINISHED' - }, - - MigrateResponseCode: { - NULL_PARAMETER: 'NULL_PARAMETER', - ACCOUNT_DOES_NOT_EXIST: 'ACCOUNT_DOES_NOT_EXIST', - TECHNICAL_ACCOUNT_NOT_ALLOWED: 'TECHNICAL_ACCOUNT_NOT_ALLOWED', - ACCOUNT_TEMPLATE_DOES_NOT_EXIST: 'ACCOUNT_TEMPLATE_DOES_NOT_EXIST', - ILLEGAL_ACCOUNT_TEMPLATE_ASSIGNMENT: 'ILLEGAL_ACCOUNT_TEMPLATE_ASSIGNMENT', - MAXIMUM_ACCOUNTS_REACHED: 'MAXIMUM_ACCOUNTS_REACHED', - ERROR: 'ERROR', - OK: 'OK' - }, - - /////////////////////////////////////////////////////////////////////////// - // ActivityStream Action - /////////////////////////////////////////////////////////////////////////// - ActivityStreamActionType: { - CREATE_MENTION: 'CREATE_MENTION', - GET_MENTIONS_BY_USER: 'GET_MENTIONS_BY_USER', - GET_ACTIVITIES_BY_USER: 'GET_ACTIVITIES_BY_USER', - UPDATE_ACTIVITY_READ: 'UPDATE_ACTIVITY_READ', - MARK_ALL_READ: 'MARK_ALL_READ' - }, - - /////////////////////////////////////////////////////////////////////////// - // ActivityStream Event - /////////////////////////////////////////////////////////////////////////// - ActivityStreamEventType: { - ACTIVITY_CREATED: 'ACTIVITY_CREATED', - ACTIVITY_MARKED_READ: 'ACTIVITY_MARKED_READ', - ACTIVITY_MARKED_UNREAD: 'ACTIVITY_MARKED_UNREAD', - ACTIVITY_DELETED: 'ACTIVITY_DELETED', - ACTIVITY_MARK_ALL_READ: 'ACTIVITY_MARK_ALL_READ' - }, - - /////////////////////////////////////////////////////////////////////////// - // ConversationUserData Event - /////////////////////////////////////////////////////////////////////////// - ConversationUserDataEventType: { - LABELS_ADDED: 'LABELS_ADDED', - LABELS_REMOVED: 'LABELS_REMOVED', - LABEL_EDITED: 'LABEL_EDITED', - FILTERS_REMOVED: 'FILTERS_REMOVED', - FILTER_ADDED: 'FILTER_ADDED', - FILTER_EDITED: 'FILTER_EDITED' - }, - - /////////////////////////////////////////////////////////////////////////// - // Private data definitions - /////////////////////////////////////////////////////////////////////////// - VotingType: { - ACCEPT_DECLINE: 'ACCEPT_DECLINE', - APPROVE_REJECT: 'APPROVE_REJECT', - YES_NO: 'YES_NO', - YES_NO_MAYBE: 'YES_NO_MAYBE', - A_B: 'A_B', - ONE_TWO_THREE_FOUR: 'ONE_TWO_THREE_FOUR' - }, - - /////////////////////////////////////////////////////////////////////////// - // Feature Names - /////////////////////////////////////////////////////////////////////////// - LabFeatureName: { - ADD_CMR_VIA_QR_CODE: 'ADD_CMR_VIA_QR_CODE', - AUTHENTICATION_SETTINGS: 'AUTHENTICATION_SETTINGS', - COLOR_CONTRAST: 'COLOR_CONTRAST', - DEVELOPER_CONSOLE: 'DEVELOPER_CONSOLE', - EXPORT_USER_DATA: 'EXPORT_USER_DATA', - FILTER_BY_LABEL: 'FILTER_BY_LABEL', - FIREBASE_PUSH_NOTIFICATIONS: 'FIREBASE_PUSH_NOTIFICATIONS', - GLOBAL_FUNCTIONS_REDESIGN: 'GLOBAL_FUNCTIONS_REDESIGN', - GROUP_PICKUP: 'GROUP_PICKUP', - INCOMING_CALL_ROUTING: 'INCOMING_CALL_ROUTING', - LANDSCAPE_MODE: 'LANDSCAPE_MODE', - LEAK_CANARY: 'LEAK_CANARY', - OPEN_XCHANGE: 'OPEN_XCHANGE', - ORGANISE_CONTENT: 'ORGANISE_CONTENT', - PARTICIPANT_DRAWING: 'PARTICIPANT_DRAWING', - PARTNER_ADMINISTRATION: 'PARTNER_ADMINISTRATION', - SCOPE_SEARCHES: 'SCOPE_SEARCHES', - TEXT_TO_SPEECH: 'TEXT_TO_SPEECH', - UCAAS_DEVICE: 'UCAAS_DEVICE', - VIDEO_PLAYER: 'VIDEO_PLAYER', - VOTING: 'VOTING', - WHITEBOARD: 'WHITEBOARD' - }, - - DraftType: { - PRIVATE: 'PRIVATE', - OPEN: 'OPEN', - BRIDGE: 'BRIDGE', - LARGE: 'LARGE' - }, - - AvatarBadgeType: { - COMMUNITY: 'COMMUNITY', - EVENT: 'EVENT', - SKYPE: 'SKYPE' - }, - - // Device categories for device selection - AudioVideoDeviceCategories: { - AUDIO_OUTPUT: 'AUDIO_OUTPUT', - RINGING_OUTPUT: 'RINGING_OUTPUT', - AUDIO_INPUT: 'AUDIO_INPUT', - VIDEO_INPUT: 'VIDEO_INPUT' - }, - - /////////////////////////////////////////////////////////////////////////// - // ATC capabilities - /////////////////////////////////////////////////////////////////////////// - AtcCapabilities: { - EXTENDED_ALERTING: 'EXTENDED_ALERTING', - CLEAR_CONNECTION_BUSY: 'CLEAR_CONNECTION_BUSY', - PBX_CALL_LOG: 'PBX_CALL_LOG' - } - }); - - var PrivateData = (function () { - var SUPPORTED_PRIVATE_DATA_FIELDS = ['voting']; - var PRIVATE_DATA_TEMPLATE = '||::$$privateData=%%1::||'; - var PRIVATE_DATA_REGEX = /^\|\|::\$\$privateData=({.*})::\|\|/; - - function convertPrivateDataToContent(item) { - if (!item) { - return ''; - } - - var privateData = {}; - SUPPORTED_PRIVATE_DATA_FIELDS.forEach(function (name) { - if (item[name] !== undefined && item[name] !== null) { - privateData[name] = item[name]; - } - }); - - if (Utils.isEmptyObject(privateData)) { - return ''; - } - var privateDataStr = JSON.stringify(privateData); - privateDataStr = Utils.textToHtmlEscaped(privateDataStr); - return PRIVATE_DATA_TEMPLATE.replace('%%1', privateDataStr); - } - - function retrievePrivateDataFromContent(item) { - if (!item || !item.text) { - return; - } - - try { - // Initialize all private data fields - SUPPORTED_PRIVATE_DATA_FIELDS.forEach(function (name) { - if (item[name] === undefined) { - item[name] = null; - } - }); - - if (!item.text.content) { - return; - } - // Parse the content to see if there is any private data - var match = PRIVATE_DATA_REGEX.exec(item.text.content); - if (match && match[0]) { - item.text.content = item.text.content.slice(match[0].length); - var privateDataStr = match[1]; - if (privateDataStr) { - privateDataStr = Utils.escapedHtmlToText(privateDataStr); - var privateData = JSON.parse(privateDataStr); - SUPPORTED_PRIVATE_DATA_FIELDS.forEach(function (name) { - if (privateData[name] !== undefined) { - item[name] = privateData[name]; - } - }); - } - } - } catch (e) { - logger.error('[Proto]: Failed to retrieve private data. ', e); - } - } - - return { - SUPPORTED_PRIVATE_DATA_FIELDS: SUPPORTED_PRIVATE_DATA_FIELDS, - convertPrivateDataToContent: convertPrivateDataToContent, - retrievePrivateDataFromContent: retrievePrivateDataFromContent - }; - })(); - - var Proto = { - requestNr: 0, - Request: function (contentType, content, tenantContext) { - var msg = { - msgType: Constants.WSMessageType.REQUEST, - request: { - requestId: ++Proto.requestNr, - type: contentType - } - }; - if (tenantContext) { - msg.request.tenantContext = tenantContext; - } - - switch (contentType) { - case Constants.ContentType.CONVERSATION: - msg.request.conversation = content; - break; - case Constants.ContentType.USER: - msg.request.user = content; - break; - case Constants.ContentType.RTC_CALL: - msg.request.rtcCall = content; - break; - case Constants.ContentType.RTC_SESSION: - msg.request.rtcSession = content; - break; - case Constants.ContentType.SEARCH: - msg.request.search = content; - break; - case Constants.ContentType.INSTRUMENTATION: - msg.request.instrumentation = content; - break; - case Constants.ContentType.ADMINISTRATION: - msg.request.administration = content; - break; - case Constants.ContentType.SYSTEM: - msg.request.system = content; - break; - case Constants.ContentType.THIRDPARTYCONNECTORS: - msg.request.thirdpartyconnectors = content; - break; - case Constants.ContentType.GUEST: - msg.request.guest = content; - break; - case Constants.ContentType.USER_TO_USER: - msg.request.userToUser = content; - break; - case Constants.ContentType.VERSION: - msg.request.version = content; - break; - case Constants.ContentType.ACCOUNT: - msg.request.account = content; - break; - case Constants.ContentType.ACTIVITYSTREAM: - msg.request.activityStream = content; - break; - case Constants.ContentType.USER_DATA: - msg.request.userData = content; - break; - } - return msg; - }, - // ParseEvent parses the event from the server to: - // a) build the event name the services subscribed to, and - // b) flatten the event object a bit to better suit the services - ParseEvent: function (event) { - var evtName; - var evtData = {}; - - switch (event.type) { - // CONVERSATION - case Constants.ContentType.CONVERSATION: - evtName = 'Conversation.' + event.conversation.type; - switch (event.conversation.type) { - case Constants.ConversationEventType.CREATE: - evtData = event.conversation.create; - break; - case Constants.ConversationEventType.UPDATE: - evtData = event.conversation.update; - break; - case Constants.ConversationEventType.ADD_ITEM: - evtData = event.conversation.addItem; - break; - case Constants.ConversationEventType.ADD_PARTICIPANT: - evtData = event.conversation.addParticipant; - break; - case Constants.ConversationEventType.UPDATE_ITEM: - evtData = event.conversation.updateItem; - break; - case Constants.ConversationEventType.READ_ITEMS: - evtData = event.conversation.readItems; - break; - case Constants.ConversationEventType.TYPING: - evtData = event.conversation.typing; - break; - case Constants.ConversationEventType.DRAFT_MESSAGE_SAVED: - evtData = event.conversation.draftMessage; - break; - case Constants.ConversationEventType.DRAFT_MESSAGE_DISCARDED: - evtData = event.conversation.draftMessage; - break; - case Constants.ConversationEventType.DRAFT_MESSAGE_PUBLISHED: - evtData = event.conversation.draftMessage; - break; - case Constants.ConversationEventType.FLAG_ITEM: - evtData = event.conversation.setFlagItem; - break; - case Constants.ConversationEventType.CLEAR_FLAGGED_ITEM: - evtData = event.conversation.clearFlaggedItem; - break; - case Constants.ConversationEventType.CONVERSATION_MARKED: - evtData = event.conversation.conversationMarked; - break; - case Constants.ConversationEventType.CONVERSATION_UNMARKED: - evtData = event.conversation.conversationUnmarked; - break; - case Constants.ConversationEventType.FAVORITE_POSITION_CHANGED: - evtData = event.conversation.favoritePositionChangedEvent; - break; - case Constants.ConversationEventType.USER_DATA_CHANGED: - evtData = event.conversation.userDataChangedEvent; - break; - } - evtData.convId = event.conversation.convId; - break; - - // USER - case Constants.ContentType.USER: - evtName = 'User.' + event.user.type; - switch (event.user.type) { - case Constants.UserEventType.USER_PRESENCE_CHANGE: - evtData = event.user.presenceChanged; - break; - case Constants.UserEventType.USER_UPDATED: - evtData = event.user.userUpdated; - break; - case Constants.UserEventType.SESSION_CLOSED: - evtData = event.user.sessionClosed; - break; - case Constants.UserEventType.PASSWORD_CHANGED: - evtData = event.user.passwordChanged; - break; - case Constants.UserEventType.USER_SETTING_UPDATED: - evtData = event.user.userSettingUpdated; - break; - case Constants.UserEventType.TELEPHONY_DATA: - evtData = event.user.telephonyData; - break; - case Constants.UserEventType.DEVICE_LIST_CHANGED: - evtData = event.user.deviceListChangedEvent; - break; - case Constants.UserEventType.NOTIFICATION_SUBSCRIPTION_CHANGE: - evtData = event.user.notificationSubscriptionChange; - break; - case Constants.UserEventType.TENANT_CONFIGURATION_UPDATED: - evtData = event.user.tenantConfigurationUpdatedEvent; - break; - case Constants.UserEventType.UCAAS_DEVICE_UPDATED: - // Event has no data - break; - } - evtData.userId = event.user.userId; - break; - - // RTC_CALL - case Constants.ContentType.RTC_CALL: - evtName = 'RTCCall.' + event.rtcCall.type; - switch (event.rtcCall.type) { - case Constants.RTCCallEventType.SDP_ANSWER: - evtData = event.rtcCall.sdpAnswer; - break; - case Constants.RTCCallEventType.SDP_FAILED: - evtData = event.rtcCall.sdpFailed; - break; - case Constants.RTCCallEventType.INVITE: - evtData = event.rtcCall.invite; - break; - case Constants.RTCCallEventType.INVITE_CANCEL: - evtData = event.rtcCall.inviteCancel; - break; - case Constants.RTCCallEventType.INVITE_FAILED: - evtData = event.rtcCall.inviteFailed; - break; - case Constants.RTCCallEventType.CHANGE_MEDIA_TYPE_REQUESTED: - evtData = event.rtcCall.changeMediaRequested; - break; - case Constants.RTCCallEventType.PROGRESS: - evtData = event.rtcCall.participantProgress; - break; - case Constants.RTCCallEventType.CHANGE_MEDIA_TYPE_FORCED: - evtData = event.rtcCall.changeMediaTypeForcedEvent; - break; - case Constants.RTCCallEventType.ICE_CANDIDATES: - evtData = event.rtcCall.iceCandidates; - break; - case Constants.RTCCallEventType.RTC_QUALITY_RATING_EVENT: - evtData = event.rtcCall.ratingEvent; - break; - } - evtData.sessionId = event.rtcCall.sessionId; - evtData.instanceId = event.rtcCall.instanceId; - break; - - // RTC_SESSION - case Constants.ContentType.RTC_SESSION: - evtName = 'RTCSession.' + event.rtcSession.type; - switch (event.rtcSession.type) { - case Constants.RTCSessionEventType.SESSION_STARTED: - evtData = event.rtcSession.sessionStarted; - break; - case Constants.RTCSessionEventType.SESSION_TERMINATED: - evtData = event.rtcSession.sessionTerminated; - break; - case Constants.RTCSessionEventType.SESSION_UPDATED: - evtData = event.rtcSession.sessionUpdated; - // Backwards compatibility for backends running SP75 - if (evtData.session.whiteBoardEnabled !== undefined) { - evtData.session.whiteboardEnabled = evtData.session.whiteBoardEnabled; - delete evtData.session.whiteBoardEnabled; - } - break; - case Constants.RTCSessionEventType.SESSION_MOVED: - evtData = event.rtcSession.sessionMoved; - break; - case Constants.RTCSessionEventType.PARTICIPANT_JOINED: - evtData = event.rtcSession.participantJoined; - break; - case Constants.RTCSessionEventType.PARTICIPANT_LEFT: - evtData = event.rtcSession.participantLeft; - break; - case Constants.RTCSessionEventType.PARTICIPANT_UPDATED: - evtData = event.rtcSession.participantUpdated; - break; - case Constants.RTCSessionEventType.ACTIVE_SPEAKER: - evtData = event.rtcSession.speaker; - break; - case Constants.RTCSessionEventType.VIDEO_ACTIVE_SPEAKER: - evtData = event.rtcSession.videoSpeaker; - break; - case Constants.RTCSessionEventType.SESSION_RECORDING_INFO: - evtData = event.rtcSession.sessionRecordingInfo; - break; - case Constants.RTCSessionEventType.SESSION_RECORDING_STARTED: - evtData = event.rtcSession.sessionRecordingStarted; - break; - case Constants.RTCSessionEventType.SESSION_RECORDING_STOPPED: - evtData = event.rtcSession.sessionRecordingStopped; - break; - case Constants.RTCSessionEventType.SESSION_RECORDING_FAILED: - evtData = event.rtcSession.sessionRecordingFailed; - break; - case Constants.RTCSessionEventType.SESSION_TERMINATION_TIMER_STARTED: - evtData = event.rtcSession.sessionTerminationTimerStarted; - break; - case Constants.RTCSessionEventType.SESSION_TERMINATION_TIMER_CANCELLED: - evtData = event.rtcSession.sessionTerminationTimerCancelled; - break; - case Constants.RTCSessionEventType.SESSION_ATTRIBUTES_CHANGED_EVENT: - evtData = event.rtcSession.sessionAttributesChangedEvent; - break; - case Constants.RTCSessionEventType.QUESTION_EVENT: - evtData = event.rtcSession.questionEvent; - break; - case Constants.RTCSessionEventType.INVITE_TO_STAGE_EVENT: - evtData = event.rtcSession.inviteToStageEvent; - break; - case Constants.RTCSessionEventType.INVITE_TO_STAGE_CANCEL_EVENT: - evtData = event.rtcSession.inviteToStageCancelEvent; - break; - case Constants.RTCSessionEventType.CURTAIN_EVENT: - evtData = event.rtcSession.curtainEvent; - break; - case Constants.RTCSessionEventType.WHITEBOARD_ENABLED: - evtData = event.rtcSession.whiteboardEnabled; - break; - case Constants.RTCSessionEventType.WHITEBOARD_DISABLED: - evtData = {}; - break; - case Constants.RTCSessionEventType.WHITEBOARD_ELEMENT_ADDED: - evtData = event.rtcSession.whiteboardElementAdded; - break; - case Constants.RTCSessionEventType.WHITEBOARD_ELEMENT_REMOVED: - evtData = event.rtcSession.whiteboardElementRemoved; - break; - case Constants.RTCSessionEventType.WHITEBOARD_ELEMENT_UPDATED: - evtData = event.rtcSession.whiteboardElementUpdated; - break; - case Constants.RTCSessionEventType.WHITEBOARD_CLEARED: - evtData = event.rtcSession.whiteboardCleared; - break; - case Constants.RTCSessionEventType.WHITEBOARD_BACKGROUND_SET: - evtData = event.rtcSession.whiteboardBackgroundSet; - break; - case Constants.RTCSessionEventType.WHITEBOARD_BACKGROUND_CLEARED: - evtData = {}; - break; - case Constants.RTCSessionEventType.WHITEBOARD_OVERLAY_TOGGLED: - evtData = {}; - break; - case Constants.RTCSessionEventType.WHITEBOARD_SYNC: - evtData = event.rtcSession.whiteboardSync; - break; - } - evtData.sessionId = event.rtcSession.sessionId; - break; - - // SEARCH - case Constants.ContentType.SEARCH: - evtName = 'Search.' + event.search.type; - switch (event.search.type) { - case Constants.SearchEventType.BASIC_SEARCH_RESULT: - evtData = event.search.basicSearchResult; - break; - case Constants.SearchEventType.DETAILED_SEARCH_RESULT: - evtData = event.search.detailSearchResult; - break; - case Constants.SearchEventType.SEARCH_STATUS: - evtData = event.search.searchStatus; - break; - case Constants.SearchEventType.RECENT_SEARCH_ADDED: - evtData = event.search.recentSearchAdded; - break; - } - evtData.searchId = event.search.searchId; - break; - - // SYSTEM - case Constants.ContentType.SYSTEM: - evtName = 'System.' + event.system.type; - switch (event.system.type) { - case Constants.SystemEventType.MAINTENANCE: - case Constants.SystemEventType.MAINTENANCE_ADDED: - case Constants.SystemEventType.MAINTENANCE_REMOVED: - evtData = event.system.maintenance; - break; - } - break; - - // ADMINISTRATION - case Constants.ContentType.ADMINISTRATION: - evtName = 'Administration.' + event.administration.type; - switch (event.administration.type) { - case Constants.AdministrationEventType.INVITE_MULTIPLE_USERS: - evtData = event.administration.inviteMultipleUsers; - break; - case Constants.AdministrationEventType.INVITE_MULTIPLE_PARTNER_USERS: - evtData = event.administration.inviteMultiplePartnerUsers; - break; - } - break; - - // THIRDPARTYCONECTORS - case Constants.ContentType.THIRDPARTYCONNECTORS: - evtName = 'ThirdParty.' + event.thirdParty.type; - switch (event.thirdParty.type) { - case Constants.ThirdPartyEventType.CONNECTED_TO_THIRDPARTY: - evtData = event.thirdParty; - break; - case Constants.ThirdPartyEventType.DISCONNECTED_FROM_THIRDPARTY: - evtData = event.thirdParty.provider; - break; - } - break; - - // USER_TO_USER - case Constants.ContentType.USER_TO_USER: - evtName = 'UserToUser.' + event.userToUser.userToUser.type; - // The event handling is the same for all USER_TO_USER events - evtData = event.userToUser.userToUser; - evtData.routing = event.userToUser.routing; - break; - - // ACTIVITYSTREAM - case Constants.ContentType.ACTIVITYSTREAM: - evtName = 'ActivityStream.' + event.activity.type; - switch (event.activity.type) { - case Constants.ActivityStreamEventType.ACTIVITY_CREATED: - evtData = event.activity.create.item; - break; - case Constants.ActivityStreamEventType.ACTIVITY_DELETED: - evtData = event.activity.delete.item; - break; - case Constants.ActivityStreamEventType.ACTIVITY_MARKED_READ: - evtData = event.activity.markRead.itemId; - break; - case Constants.ActivityStreamEventType.ACTIVITY_MARKED_UNREAD: - evtData = event.activity.markUnread.itemId; - break; - case Constants.ActivityStreamEventType.ACTIVITY_MARK_ALL_READ: - evtData = event.activity.markAllRead.status; - break; - } - break; - - // ACCOUNT - case Constants.ContentType.ACCOUNT: - evtName = 'Account.' + event.account.type; - switch (event.account.type) { - case Constants.AccountEventType.MIGRATE_MULTIPLE_USERS: - evtData = event.account.migrateMultipleUsers; - break; - case Constants.AccountEventType.TELEPHONY_CONFIGURATION_UPDATED: - evtData = event.account.telephonyConfigurationUpdated; - break; - } - break; - - // CONVERSATION_USER_DATA - case Constants.ContentType.CONVERSATION_USER_DATA: - evtName = 'ConversationUserData.' + event.conversationUserData.type; - switch (event.conversationUserData.type) { - case Constants.ConversationUserDataEventType.LABELS_ADDED: - evtData = event.conversationUserData.labelsAdded; - break; - case Constants.ConversationUserDataEventType.LABELS_REMOVED: - evtData = event.conversationUserData.labelsRemoved; - break; - case Constants.ConversationUserDataEventType.LABEL_EDITED: - evtData = event.conversationUserData.labelEdited; - break; - case Constants.ConversationUserDataEventType.FILTERS_REMOVED: - evtData = event.conversationUserData.filtersRemoved; - break; - case Constants.ConversationUserDataEventType.FILTER_ADDED: - evtData = event.conversationUserData.filterAdded; - break; - case Constants.ConversationUserDataEventType.FILTER_EDITED: - evtData = event.conversationUserData.filterEdited; - break; - } - break; - - default: - return null; - } - - return {name: evtName, data: evtData}; - }, - getMediaType: function (data) { - if (!data) { - return {audio: false, video: false, desktop: false}; - } - - // data is an array of RealtimeMediaType ('AUDIO', 'VIDEO', 'DESKTOP_SHARING') - var mediaType = {audio: false, video: false, desktop: false}; - data.forEach(function (elem) { - switch (elem) { - case Constants.RealtimeMediaType.AUDIO: - mediaType.audio = true; - break; - case Constants.RealtimeMediaType.VIDEO: - mediaType.video = true; - break; - case Constants.RealtimeMediaType.DESKTOP_SHARING: - mediaType.desktop = true; - break; - } - }); - return mediaType; - }, - isOfflineFailure: function (err) { - return err === Constants.ReturnCode.DISCONNECTED || - err === Constants.ReturnCode.FAILED_TO_SEND || - err === Constants.ReturnCode.REQUEST_TIMEOUT; - } - }; - - // Exports - circuit.Constants = Constants; - circuit.PrivateData = PrivateData; - circuit.Proto = Proto; - - return circuit; - -})(Circuit); - -// The code in this module is based on the sip.js module and has been -// modified for Ansible - -/* sip.js ********************************************************************* - -Copyright (c) 2010 Kirill Mikhailov (kirill.mikhailov@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -******************************************************************************/ - -var Circuit = (function (circuit) { - 'use strict'; - - var Utils = circuit.Utils; - - // Empirical priority ranges collected from logs. These values are subject to change. - var WebRtcRelayIcePriority = Object.freeze({ - TLS: {name: 'tls', start: 0, end: 11000000}, - TCP: {name: 'tcp', start: 12000000, end: 31000000}, - UDP: {name: 'udp', start: 35000000, end: 60000000} - }); - - /* - * IceCandidate Object - */ - function IceCandidate(candidate) { - /******************* - // candidate-attribute = "candidate" ":" foundation ' ' component-id ' ' - // transport ' ' - // priority ' ' - // connection-address ' ' ;from RFC 4566 - // port ;port from RFC 4566 - // ' ' cand-type - // [' ' rel-addr] - // [' ' rel-port] - // *(' ' extension-att-name ' ' - // extension-att-value) - // - // foundation = 1*32ice-char - // component-id = 1*5DIGIT - // transport = "UDP" / transport-extension - // transport-extension = token ; from RFC 3261 - // priority = 1*10DIGIT - // cand-type = "typ" ' ' candidate-types - // candidate-types = "host" / "srflx" / "prflx" / "relay" / token - // rel-addr = "raddr" ' ' connection-address - // rel-port = "rport" ' ' port - // extension-att-name = byte-string ;from RFC 4566 - // extension-att-value = byte-string - // ice-char = ALPHA / DIGIT / "+" / "/" - *******************/ - - if (candidate) { - candidate = candidate.replace(/(a=)*candidate:([^\r]+)(\r\n)*/i, '$2'); - var tmp = candidate.split(' '); - if (tmp.length < 7) { - throw new Error('Invalid candidate: ' + candidate); - } - this.foundation = parseInt(tmp[0], 10); - this.componentId = parseInt(tmp[1], 10); - this.transport = tmp[2].toLowerCase(); - this.priority = parseInt(tmp[3], 10); - this.address = tmp[4]; - this.port = parseInt(tmp[5], 10); - this.typ = tmp[7]; - - this.optional = []; - for (var idx = 8; idx < tmp.length; idx += 2) { - this.optional.push(tmp[idx]); - this[tmp[idx]] = tmp[idx + 1]; - } - } else { - this.foundation = 1; - this.componentId = 1; // 1 = RTP, 2 = RTCP - this.transport = 'udp'; - this.priority = 1; - this.address = '0.0.0.0'; - this.port = 1; - this.typ = 'host'; - this.generation = 0; - - this.optional = []; - } - } - - IceCandidate.prototype.equals = function (other) { - other = (!other || typeof other === 'string') ? new IceCandidate(other) : other; - return (this.foundation === other.foundation && this.componentId === other.componentId && - this.transport === other.transport && this.priority === other.priority && - this.address === other.address && this.port === other.port && this.typ === other.typ); - }; - - IceCandidate.prototype.isRtp = function () { - return (this.componentId === 1); - }; - - IceCandidate.prototype.isRtcp = function () { - return (this.componentId === 2); - }; - - IceCandidate.prototype.isTcpTlsRelay = function () { - // Check if this is a relay candidate with a TCP or TLS connection to the TURN server. - // For now, the only way of doing that is by checking in which range the priority value is. - return (this.typ === 'relay') && (this.priority <= WebRtcRelayIcePriority.TCP.end); - }; - - IceCandidate.prototype.getRelayClientTransportType = function () { - var type = ''; - if (this.typ === 'relay') { - var p = this.priority; - [WebRtcRelayIcePriority.TLS, WebRtcRelayIcePriority.TCP, WebRtcRelayIcePriority.UDP].some(function (t) { - if (p >= t.start && p <= t.end) { - type = t.name; - return true; - } - }); - } - return type; - }; - - IceCandidate.prototype.getValue = function () { - var str = this.foundation + ' ' + this.componentId + ' ' + this.transport + - ' ' + this.priority + ' ' + this.address + ' ' + this.port + ' ' + 'typ ' + this.typ; - - for (var idx = 0; idx < this.optional.length; idx++) { - var param = this.optional[idx]; - str += ' ' + param + ' ' + this[param]; - } - return str; - }; - - IceCandidate.prototype.getParsedAttribute = function () { - return { - field: 'candidate', - value: this.getValue() - }; - }; - - IceCandidate.prototype.toString = function () { - var str = 'candidate:' + this.getValue(); - return str; - }; - - - var sdpParser = (function () { - - // Imports - var logger = Circuit.logger; - - function parseO(o) { - var t = o.split(/\s+/); - return { username: t[0], id: t[1], version: t[2], nettype: t[3], addrtype: t[4], address: t[5] }; - } - - function parseC(c) { - var t = c.split(/\s+/); - return { nettype: t[0], addrtype: t[1], address: t[2] }; - } - - function parseA(a) { - var tmp = /^([^:]+)(?::(.*))?/.exec(a); - return { field: tmp[1], value: tmp[2] }; - } - - var reM = /^(\w+) +(\d+)(?:\/(\d))? +(\S+)(?: (\d+( +\d+)*))?/; - function parseM(m) { - var tmp = reM.exec(m); - - return { - media: tmp[1], - port: +tmp[2], - portnum: +(tmp[3] || 1), - proto: tmp[4], - fmt: tmp[5] ? tmp[5].split(/\s+/).map(function (x) { return +x; }) : [] - }; - } - - function push(o, i, v) { - switch (i) { - case 'v': - case 'o': - case 's': - case 'i': - case 'u': - case 'e': - case 'p': - case 'c': - o[i] = v; - break; - default: - if (o[i]) { - o[i].push(v); - } else { - o[i] = [v]; - } - break; - } - } - - var stringifiers = { - o: function (o) { - return [o.username || '-', o.id, o.version, o.nettype || 'IN', o.addrtype || 'IP4', o.address].join(' '); - }, - c: function (c) { - return [c.nettype || 'IN', c.addrtype || 'IP4', c.address].join(' '); - }, - m: function (m) { - return [m.media || 'audio', m.port || '0', m.proto || 'RTP/AVP', m.fmt.join(' ')].join(' '); - }, - a: function (a) { - return a.value ? [a.field, a.value].join(':') : a.field; - } - }; - - function stringifyParam(sdp, type, def) { - if (sdp[type] !== undefined) { - var stringifier = function (x) { return type + '=' + ((stringifiers[type] && stringifiers[type](x)) || x) + '\r\n'; }; - - if (Array.isArray(sdp[type])) { - return sdp[type].map(stringifier).join(''); - } - return stringifier(sdp[type]); - } - - if (def) { - return type + '=' + def + '\r\n'; - } - return ''; - } - - function filterSdpAttr(sdpAttr) { - return sdpAttr.filter(function (a) { - switch (a.field) { - case 'inactive': - case 'recvonly': - case 'sendonly': - case 'sendrecv': - return false; - } - return true; - }); - } - - function parseSdp(sdp) { - var tmpSdp = sdp.split(/\r\n/); - var i, tmp; - - var result = {}; - - // First parse the session level attributes - for (i = 0; i < tmpSdp.length; ++i) { - tmp = /^(\w)=(.*)/.exec(tmpSdp[i]); - - if (!tmp || tmp[1] === 'm') { - break; - } else { - if (tmp[1] === 'a' && tmp[2]) { - push(result, tmp[1], parseA(tmp[2])); - } else { - push(result, tmp[1], tmp[2]); - } - } - } - - result.m = []; - var m = null; - - // Now let's parse the media lines and their attributes - for (; i < tmpSdp.length; ++i) { - tmp = /(\w)=(.*)/.exec(tmpSdp[i]); - - if (!tmp) { - break; - } - - if (tmp[1] === 'm') { - m = parseM(tmp[2]); - result.m.push(m); - } else { - if (tmp[1] === 'a' && tmp[2]) { - push(m, tmp[1], parseA(tmp[2])); - } else { - push(m, tmp[1], tmp[2]); - } - } - } - - if (!result.s || result.s.trim() === '') { - result.s = '-'; - } - if (result.o) { - result.o = parseO(result.o); - } - if (result.c) { - result.c = parseC(result.c); - } - result.m.forEach(function (m) { - if (m.c) { - m.c = parseC(m.c); - } - }); - - return result; - } - - function getVideoConnectionModes(sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - var connModes = []; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media !== 'video') { - return; - } - m.a.some(function (a) { - switch (a.field) { - case 'sendrecv': - case 'sendonly': - case 'recvonly': - case 'inactive': - connModes.push(a.field); - return true; - } - }); - }); - } - return connModes; - } - - function getConnectionMode(sdp, option/*{mediaType, mLineIndex}*/) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - - option = option || {}; - // Default is sendrecv - var connMode = 'sendrecv'; - - if (option.mLineIndex !== undefined) { - parsedSdp.m[option.mLineIndex].a.forEach(function (a) { - switch (a.field) { - case 'sendrecv': - case 'sendonly': - case 'recvonly': - case 'inactive': - connMode = a.field; - break; - } - }); - return connMode; - } - - if (!option.mediaType || option.mediaType === 'both') { - option.mediaType = 'audio'; - } - - if (parsedSdp.a) { - parsedSdp.a.forEach(function (a) { - switch (a.field) { - case 'sendonly': - case 'recvonly': - case 'inactive': - connMode = a.field; - break; - } - }); - } - - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media === option.mediaType) { - m.a.forEach(function (a) { - switch (a.field) { - case 'sendrecv': - case 'sendonly': - case 'recvonly': - case 'inactive': - connMode = a.field; - break; - } - }); - } - }); - } - - return connMode; - } - - function isBandWidthASSet(b) { - if (!Array.isArray(b)) { - return false; - } - return b.some(function (v) { - return v.includes('AS:'); - }); - } - - function isBandWidthTIASSet(b) { - if (!Array.isArray(b)) { - return false; - } - return b.some(function (v) { - return v.includes('TIAS:'); - }); - } - - function validateMLine(m, index, removeUdpVideoIce, trickleIce) { - if (m.port === 0) { - // m-line is disabled. No validation is needed - return true; - } - - var numCandidates = 0; - var removedValidCandidates = false; - var relayRtpCandidate = null; - var relayRtcpCandidate = null; - - // Remove ICE candidates as needed - var attr = m.a.filter(function (a) { - if (a.field !== 'candidate') { - return true; - } - var candidate = new IceCandidate(a.value); - // Remove any tcp candidates. It only returns UDP Ice Candidates - if (candidate.transport !== 'udp') { - return false; - } - - if (removeUdpVideoIce && m.media === 'video') { - if (candidate.typ === 'relay') { - // For relay candidates, select only the ones that use TCP/TLS to the TURN server. - if (candidate.isTcpTlsRelay()) { - if (!relayRtpCandidate && candidate.isRtp()) { - relayRtpCandidate = candidate; - } - if (!relayRtcpCandidate && candidate.isRtcp()) { - relayRtcpCandidate = candidate; - } - } else { - logger.debug('[SdpParser]: Removed UDP relay candidate: ', a.value); - removedValidCandidates = true; - return false; - } - } else { - // We need to completely remove host and srflx candidates - removedValidCandidates = true; - return false; - } - } - numCandidates++; - return true; - }); - - if (!relayRtpCandidate && removedValidCandidates && !trickleIce) { - // The SDP doesn't have TLS/TCP TURN candidates for RTP but has other valid candidates. - // Try validating again with the removeUdpVideoIce flag turned off. - logger.info('[SdpParser]: Media description does not have TCP/TLS relay candidates. Do not filter UDP candidates!'); - return validateMLine(m, index, false); - } - - if (numCandidates === 0 && !trickleIce) { - // The m-line does not have any valid candidates - return false; - } - - m.a = attr; - if (relayRtcpCandidate) { - // Update rtcp attribute with info from first relay candidate - m.a.some(function (a) { - if (a.field === 'rtcp') { - // a=rtcp:1234 IN IP4 1.1.1.1 - a.value = relayRtcpCandidate.port + ' IN IP4 ' + relayRtcpCandidate.address; - return true; - } - }); - } - if (relayRtpCandidate) { - // Update media description port and connection address with info from first relay candidate - m.port = relayRtpCandidate.port; - if (m.c) { - m.c.address = relayRtpCandidate.address; - } - } - return true; - } - - function findIdInMediaAttrs(parsedSdp, id) { - var mediaIndex = -1; - parsedSdp.m.some(function (m, i) { - if (m.a.some(function (a) { - return a.field === 'mid' && a.value === id; - })) { - mediaIndex = i; - return true; // Break out of parsedSdp.m.some - } - }); - return mediaIndex; - } - - return { - parse: parseSdp, - - stringify: function (sdp) { - var s = ''; - - s += stringifyParam(sdp, 'v', 0); - s += stringifyParam(sdp, 'o'); - s += stringifyParam(sdp, 's', '-'); - s += stringifyParam(sdp, 'i'); - s += stringifyParam(sdp, 'u'); - s += stringifyParam(sdp, 'e'); - s += stringifyParam(sdp, 'p'); - s += stringifyParam(sdp, 'c'); - s += stringifyParam(sdp, 'b'); - s += stringifyParam(sdp, 't', '0 0'); - s += stringifyParam(sdp, 'r'); - s += stringifyParam(sdp, 'z'); - s += stringifyParam(sdp, 'k'); - s += stringifyParam(sdp, 'a'); - sdp.m.forEach(function (m) { - s += stringifyParam({m: m}, 'm'); - s += stringifyParam(m, 'i'); - s += stringifyParam(m, 'c'); - s += stringifyParam(m, 'b'); - s += stringifyParam(m, 'k'); - s += stringifyParam(m, 'a'); - }); - - return s; - }, - - // mediaType: 'audio', 'video' or 'both' - getConnectionMode: getConnectionMode, - getVideoConnectionModes: getVideoConnectionModes, - - // mediaType: 'audio', 'video' or 'both' - // connectionMode: 'inactive', 'recvonly','sendonly' or 'sendrecv' - setConnectionMode: function (sdp, mediaType, connectionMode) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - var media = (!mediaType || mediaType === 'both') ? ['audio', 'video'] : [mediaType]; - - if (mediaType === 'both' && parsedSdp.a) { - parsedSdp.a = filterSdpAttr(parsedSdp.a); - parsedSdp.a.push({field: connectionMode}); - } - - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (media.includes(m.media)) { - m.a = filterSdpAttr(m.a); - m.a.push({field: connectionMode}); - } - }); - } - return parsedSdp; - }, - - isHold: function (sdp) { - var connMode = getConnectionMode(sdp); - return (connMode === 'inactive' || connMode === 'sendonly'); - }, - - disableMultiplex: function (sdp) { - // Disable the multiplex of audio and video RTP packets by removing the 'a=group:BUNDLE audio video' line. - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.a) { - parsedSdp.a = parsedSdp.a.filter(function (a) { - return (a.field !== 'group'); - }); - } - return parsedSdp; - }, - - hasVideo: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - - if (parsedSdp.m) { - return parsedSdp.m.some(function (m, index) { - if ((m.media !== 'video') || (m.port === 0) || (m.fmt.length === 0)) { - return false; - } - - if (getConnectionMode(parsedSdp, {mLineIndex: index}) === 'inactive') { - return false; - } - - // This is a valid video m-line. Make sure that it contains VP8 or VP9. - return m.a.some(function (a) { - return ((a.field === 'rtpmap') && /VP[89]/.test(a.value)); - }); - }); - } - return false; - }, - - addEmptyVideoLine: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - var newMediaLine = { - media: 'video', - port: 0, - proto: 'RTP/SAVPF', - fmt: [100], - c: { - nettype: 'IN', - addrtype: 'IP4', - address: '0.0.0.0' - }, - a: [{field: 'inactive'}] - }; - - parsedSdp.m.push(newMediaLine); - return parsedSdp; - }, - - removeVideo: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media === 'video') { - m.port = 0; - m.c = { - nettype: 'IN', - addrtype: 'IP4', - address: '0.0.0.0' - }; - // Remove all attributes, except for a=mid - m.a = m.a.filter(function (a) { return a.field === 'mid'; }); - m.a.push({field: 'inactive'}); - } - }); - } - return parsedSdp; - }, - - setOpusParameters: function (sdp, parameters) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (!parsedSdp.m || !parameters) { - return parsedSdp; - } - // Create a shallow copy of parameters - parameters = Utils.shallowCopy(parameters); - - parsedSdp.m.forEach(function (m) { - if (m.media !== 'audio' || !m.a) { - return; - } - var pt; - m.a.some(function (a) { - if (a.field === 'rtpmap' && a.value.includes('opus')) { - pt = a.value.split(' ')[0]; - return true; - } - }); - pt && m.a.some(function (a) { - if (a.field === 'fmtp' && a.value.includes(pt)) { - var currentParams = a.value.substring(pt.length + 1).split(';'); - currentParams.forEach(function (param) { - var s = param.split('='); - var name = s[0].trim(); - var value = s[1].trim(); - if (!parameters.hasOwnProperty(name)) { - parameters[name] = value; - } - }); - var attr = Object.keys(parameters).map(function (name) { - return (name + '=' + parameters[name]); - }); - a.value = pt + ' ' + attr.join(';'); - return true; - } - }); - - }); - return parsedSdp; - }, - - setVideoBandwidth: function (sdp, bw, overwrite) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media === 'video') { - if (overwrite) { - if (bw && bw.valueOf() > 0) { - m.b = ['AS:' + bw, 'TIAS:' + (bw * 1000)]; - } else if (isBandWidthASSet(m.b) || isBandWidthTIASSet(m.b)) { - delete m.b; - } - } else { - if (!isBandWidthASSet(m.b)) { - push(m, 'b', 'AS:' + bw); - } - if (!isBandWidthTIASSet(m.b)) { - push(m, 'b', 'TIAS:' + (bw * 1000)); - } - } - } - }); - } - return parsedSdp; - }, - - setXGoogle: function (sdp, x) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m && x) { - parsedSdp.m.forEach(function (m) { - if (m.media !== 'video' || !m.a) { - return; - } - var pts = []; - // Remove the existing parameters, regardless if we're setting it locally or not - m.a = m.a.filter(function (a, i) { - if ((a.field === 'rtpmap') && (/VP[89]/.test(a.value))) { - // Found the VP8 or VP9 codec. Get the payload type - pts.push({idx: i + pts.length, pt: a.value.split(' ')[0]}); - } - return ((a.field !== 'fmtp' || (a.value && !a.value.includes('x-google'))) && - a.field !== 'x-google-buffer-latency'); - }); - - pts.forEach(function (element) { - var xAttr = []; - if (x.minBitRate && x.minBitRate.valueOf() > 0) { - xAttr.push('x-google-min-bitrate=' + x.minBitRate); - } - if (x.maxBitRate && x.maxBitRate.valueOf() > 0) { - xAttr.push('x-google-max-bitrate=' + x.maxBitRate); - } - if (x.startBitRate && x.startBitRate.valueOf() > 0) { - xAttr.push('x-google-start-bitrate=' + x.startBitRate); - } - if (x.maxQuantization && x.maxQuantization.valueOf() > 0) { - xAttr.push('x-google-max-quantization=' + x.maxQuantization); - } - if (xAttr.length > 0) { - xAttr = element.pt + ' ' + xAttr.join('; '); - m.a.splice(element.idx + 1, 0, {field: 'fmtp', value: xAttr}); - } - }); - }); - } - return parsedSdp; - }, - - preferVideoVp9: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m, index) { - if (m.media === 'video') { - var vp9 = m.a.find(function (a) { - return a.field === 'rtpmap' && a.value.includes('VP9'); - }); - if (vp9) { - var pt = parseInt(vp9.value.split(' ')[0], 10); - var idx = m.fmt.indexOf(pt); - if (idx > 0) { - m.fmt.splice(idx, 1); - m.fmt.unshift(pt); - logger.debug('[SdpParser]: Prefer VP9 format for video m-line #:', index); - } - } - } - }); - } - return parsedSdp; - }, - - removeVideoBandwidth: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media === 'video') { - m.b = m.b && m.b.filter(function (v) { - return !v.startsWith('AS:') && !v.startsWith('TIAS:'); - }); - } - }); - } - return parsedSdp; - }, - - /** - * This method removes the ssrc-audio-level attribute (security issue). - * At least until Chrome removes it. - * See https://code.google.com/p/webrtc/issues/detail?id=3411 - */ - removeAudioLevelAttr: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - if (m.media === 'audio') { - m.a = m.a && m.a.filter(function (a) { - if (a.field === 'extmap' && a.value && a.value.includes('ssrc-audio-level')) { - return false; - } - return true; - }); - } - }); - } - return parsedSdp; - }, - - /** - * Checks for Trickle ICE indication in provided SDP. - * @param {type} sdp The raw SDP string or the parsed SDP. - * @returns {Boolean} true if "ice-options" attribute contains "trickle" value, otherwise false. - */ - isTrickleIceOption: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.a = parsedSdp.a || []; - var found = parsedSdp.a.some(function (a) { - if (a.field === 'ice-options') { - a.value = a.value || ''; - return a.value.includes('trickle'); - } - return false; - }); - return found; - }, - - /** - * Adds "trickle" to ice-options if not generated by peer connection and Trickle ICE - * is enabled. Also remove "trickle" from ice-options if generated by peer connection - * and Trickle ICE is disabled. - * @param {string|Object} sdp The raw SDP string of the parsed SDP. - * @param {boolean} enabled true if Trickle ICE is enabled, otherwise false - * @returns {Object} The modified SDP object with correct trickle ICE option. - */ - fixTrickleIceOption: function (sdp, enabled) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.a = parsedSdp.a || []; - - var found = parsedSdp.a.some(function (a, idx) { - if (a.field === 'ice-options') { - a.value = a.value || ''; - if (enabled) { - if (!a.value.includes('trickle')) { - a.value += ' trickle'; - } - } else { - a.value = a.value.replace(/trickle\s*/gi, '').trim(); - if (!a.value) { - // There are no other options. Remove the attribute - parsedSdp.a.splice(idx, 1); - } - } - return true; - } - }); - - if (enabled && !found) { - parsedSdp.a.push({field: 'ice-options', value: 'trickle'}); - } - return parsedSdp; - }, - - /** - * Adds end-of-candidates attribute to all RTP streams (m-lines). - * @param {string|Object} sdp The raw SDP string of the parsed SDP . - * @returns {Object} The modified SDP object with the end-of-candidates attributes. - */ - addEndOfCandidatesLine: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - m.a = m.a || []; - var eoc = m.a.some(function (a) { - return (a.field === 'end-of-candidates'); - }); - if (!eoc) { - m.a.push({field: 'end-of-candidates'}); - } - }); - } - return parsedSdp; - }, - - /** - * This method removes the end-of-candidates attribute - * @param {string|Object} sdp The raw SDP string of the parsed SDP - * @returns {Object} The modified SDP object without the end-of-candidate attributes. - */ - removeEndOfCandidatesLine: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - parsedSdp.m.forEach(function (m) { - m.a && m.a.some(function (a, idx) { - if (a.field === 'end-of-candidates') { - m.a.splice(idx, 1); - return true; - } - }); - }); - } - return parsedSdp; - }, - - /** - * This method removes empty attribute lines. E.g.: ice-ufrag, which can - * cause problems on the remote peer. - */ - removeEmptyAttributeLines: function (sdp, attrFields) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (parsedSdp.m) { - var attrFieldsMap = {}; - if (!Array.isArray(attrFields)) { - attrFieldsMap[attrFields] = true; - } else { - attrFields.forEach(function (field) { attrFieldsMap[field] = true; }); - } - parsedSdp.m.forEach(function (m) { - m.a = m.a && m.a.filter(function (a) { - return a.value || !attrFieldsMap[a.field]; - }); - }); - } - return parsedSdp; - }, - - /** - * This method validate if all m lines contains at least one UDP IceCandidate - * Any TCP IceCandidates is removed from the result - * if SDP is not valid the method returns null otherwise - * the method return the changed SDP - */ - validateIceCandidates: function (sdp, removeUdpVideoIce, trickleIce) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - return !parsedSdp.m.some(function (m, index) { - if (!validateMLine(m, index, removeUdpVideoIce, trickleIce)) { - return true; - } - }); - }, - - isNoOfferSdp: function (rtcSdp) { - return !!rtcSdp && rtcSdp.sdp === '' && rtcSdp.type === 'nooffer'; - }, - - iceTotalCandidatesCount: function (sdp) { - var count = 0; - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.m.forEach(function (m) { - m.a.forEach(function (a) { - if (a.field === 'candidate') { - count++; - } - }); - }); - return count; - }, - - getIceCandidates: function (sdp) { - var candidates = []; - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.m.forEach(function (m) { - m.a.forEach(function (a) { - if (a.field === 'candidate') { - candidates.push(new IceCandidate(a.value)); - } - }); - }); - return candidates; - }, - - /** - * Removes any ssrc from m-lines that are 'recvonly'. - */ - removeNotNeededSsrc: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.m.forEach(function (m) { - if (m.a.some(function (a) { - return (a.field === 'recvonly'); - })) { - m.a = m.a.filter(function (a) { - return a.field !== 'ssrc'; - }); - } - }); - return parsedSdp; - }, - - removeAudioMediaProtocols: function (sdp, protocols) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - if (!protocols || !protocols.length) { - return parsedSdp; - } - parsedSdp.m.some(function (m) { - if (m.media === 'audio' && m.proto) { - var pl = m.proto.split('/'); - pl = pl.filter(function (p) { - return !protocols.includes(p); - }); - m.proto = pl.join('/'); - return true; - } - }); - return parsedSdp; - }, - - verifyFingerprintInMLines: function (sdp) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - parsedSdp.m && parsedSdp.m.forEach(function (mline, index) { - if (!mline.a.some(function (a) { - return (a.field === 'fingerprint'); - })) { - // Firefox (as of v39): no fingerprint is inside the m-line, so get it from the session - if (!parsedSdp.a.some(function (a) { - if (a.field === 'fingerprint') { - mline.a.push(a); - return true; - } - })) { - logger.debug('[SdpParser]: verifyFingerprintInMLines(): No session fingerprint found for video m-line #:', index); - } - } - }); - return parsedSdp; - }, - - findMediaLineIndex: function (sdp, id) { - if (!id) { - return; - } - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - return findIdInMediaAttrs(parsedSdp, id); - }, - - setMediaIdAttribute: function (sdp, mLineIndex, value, overwrite) { - var parsedSdp = (typeof sdp === 'string') ? parseSdp(sdp) : sdp; - var m = parsedSdp.m[mLineIndex]; - if (m) { - if (!m.a.some(function (a) { - if (a.field === 'mid') { - if (overwrite) { - a.value = value; - } - return true; - } - })) { - m.a.push({field: 'mid', value: value}); - } - } - }, - - getOrigin: function (sdp) { - if (typeof sdp === 'string') { - var origin = /(o=[^\r]+)\r\n/i.exec(sdp); - return (origin && origin[1]) || ''; - } - return stringifyParam(sdp, 'o'); - } - }; - - }()); - - // Exports - circuit.IceCandidate = IceCandidate; - circuit.sdpParser = sdpParser; - - return circuit; - -})(Circuit || {}); - - - -// Define external globals for JSHint -/*global WebSocket, window*/ - -/////////////////////////////////////////////////////////////////////////////// -//--- WebSocket Connection Controller --- -/////////////////////////////////////////////////////////////////////////////// - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Utils = circuit.Utils; - - // Connection states used by ConnectionHandler - var ConnectionState = Object.freeze({ - Disconnected: 'Disconnected', - Connecting: 'Connecting', - Reconnecting: 'Reconnecting', - Connected: 'Connected' - }); - - function ConnectionController(config) { - var logger = circuit.logger; - config = config || {}; - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var RETRY_DELAY, RETRY_INTERVAL, MAX_SHORT_ATTEMPTS, MAX_INTERVAL, MAX_ATTEMPTS; - if (Utils.isMobile()) { - RETRY_DELAY = 0; - RETRY_INTERVAL = 0; - MAX_INTERVAL = 0; - MAX_ATTEMPTS = 0; - MAX_SHORT_ATTEMPTS = 0; - } else { - RETRY_DELAY = 1000; // Wait for 1 second before attempting to reconnect - RETRY_INTERVAL = 8000; // Start with 8 seconds then double after MAX_SHORT_ATTEMPTS until it reaches MAX_INTERVAL - MAX_INTERVAL = 64000; // Max retry interval (64 seconds) - MAX_ATTEMPTS = 1350; // Approximately ~1 day, then it gives up. -1 means forever. - MAX_SHORT_ATTEMPTS = 1; // Limit of short interval attempts. After this, we start doubling the intervals - } - - var OFFLINE_DELAY = 4000; // Small Delay to handle Network Glitch - var THROTTLE_MAX_MESSAGES = 20; // 20 messages each 2 seconds - var THROTTLE_INTERVAL = 2000; - var MIN_THROTTLE_DELAY = 200; - var MAX_QUEUED_MESSAGES = 200; - var MAX_DEQUEUED_MESSAGES = 5; // Dequeue up to 5 messages at a time - - var _that = this; - var _socket = null; - var _errorCb = null; - var _successCb = null; - var _state = ConnectionState.Disconnected; - var _retryInterval = null; - var _retryAttempts = 0; - var _retryTimeout = null; - var _reconnectTimeout = null; - var _offlineTimeout = null; - var _config = config || {}; - - var _target = null; - var _targetObfuscated = null; - - var _sentTimestamps = []; - var _queuedMessages = []; - var _throttleTimeout = null; - - ///////////////////////////////////////////////////////////////////////////// - // Event Senders - ///////////////////////////////////////////////////////////////////////////// - function raiseEvent(eventHandler, event) { - if (typeof eventHandler === 'function') { - try { - eventHandler(event); - } catch (e) { - logger.error(e); - } - } - } - - function raiseStateChange(newState, oldState) { - raiseEvent(_that.onStateChange, { - newState: newState, - oldState: oldState - }); - } - - function raiseReconnectFailed() { - raiseEvent(_that.onReconnectFailed); - } - - function raiseMessage(msg) { - raiseEvent(_that.onMessage, msg); - } - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function handleOnlineEvent() { - logger.debug('[CONN]: online event received'); - - if (_offlineTimeout) { - logger.debug('[CONN]: Clear offline timeout'); - window.clearTimeout(_offlineTimeout); - _offlineTimeout = null; - } - - if (_state === ConnectionState.Reconnecting) { - // Reset retry interval and try to reconnect - _retryInterval = RETRY_INTERVAL; - _that.reconnect(null, 1000); - } - } - - function handleOfflineEvent() { - logger.debug('[CONN]: offline event received'); - - if (_state !== ConnectionState.Connected) { - return; - } - - // Make sure we are handling the online event - window.addEventListener('online', handleOnlineEvent); - - if (!_offlineTimeout) { - _offlineTimeout = window.setTimeout(function () { - _offlineTimeout = null; - if (!window.navigator.onLine) { - logger.debug('[CONN]: The client is offline. Change state to Reconnecting.'); - _that.reconnect(); - } - }, OFFLINE_DELAY); - } - } - - function clearSocket() { - if (_socket) { - // Unregister events - _socket.onopen = null; - _socket.onclose = null; - _socket.onmessage = null; - _socket.onerror = null; - - _socket = null; - } - } - - function setState(newState) { - if (newState !== _state) { - logger.debug('[CONN]: Changed connection state from ' + _state + ' to ' + newState); - logger.debug('[CONN]: Raising connectionStateChange event'); - - var oldState = _state; - _state = newState; - - // Dispatch the event asynchronously - window.setTimeout(function () { - raiseStateChange(newState, oldState); - }, 0); - } - } - - function onError(error) { - try { - _errorCb && _errorCb(error); - } catch (e) { - logger.error('[CONN]: Exception on errorCallback. ', e); - } finally { - _errorCb = null; - _successCb = null; - } - } - - function onSuccess() { - try { - logger.debug('[CONN]: Registered Successfully'); - _successCb && _successCb(); - } catch (e) { - logger.error('[CONN]: Exception on successCallback. ', e); - } finally { - _errorCb = null; - _successCb = null; - } - } - - function clearThrottlingData() { - if (_throttleTimeout) { - window.clearTimeout(_throttleTimeout); - _throttleTimeout = null; - } - _queuedMessages = []; - _sentTimestamps = []; - } - - function disconnect(suppressStateChange) { - if (_retryTimeout) { - window.clearTimeout(_retryTimeout); - _retryTimeout = null; - } - if (_reconnectTimeout) { - window.clearTimeout(_reconnectTimeout); - _reconnectTimeout = null; - } - clearThrottlingData(); - - if (_socket) { - logger.debug('[CONN]: Disconnecting from server'); - var activeSocket = _socket; - clearSocket(); - activeSocket.close(); - } - - if (!suppressStateChange) { - if (window.removeEventListener) { - window.removeEventListener('online', handleOnlineEvent); - window.removeEventListener('offline', handleOfflineEvent); - } - setState(ConnectionState.Disconnected); - } - } - - function retryConnection() { - if (Utils.isMobile()) { - // For mobile clients we don't try to reconnect from javascript - logger.warn('[CONN]: Mobile Client - Do not attempt to reconnect from JS'); - return; - } - - if (MAX_ATTEMPTS > 0 && _retryAttempts >= MAX_ATTEMPTS) { - logger.warn('[CONN]: Reached max number of attempts reconnecting to WebSocket'); - disconnect(); - return; - } - - if (window.navigator.onLine === false) { - // Only for browser clients that support window.navigator.onLine, - // non-browser clients (e.g. mobile, node) will have window.navigator.onLine as 'undefind' - logger.warn('[CONN]: Client is offline. Set retry timer to max value.'); - _retryInterval = MAX_INTERVAL; - } - - logger.info('[CONN]: Retry to open socket in ' + Math.floor(_retryInterval / 1000) + ' seconds'); - _retryTimeout = window.setTimeout(function () { - _retryTimeout = null; - openSocket(); - }, _retryInterval); - - if (_retryInterval < MAX_INTERVAL) { - // After the first attempt (random), set the interval to RETRY_INTERVAL - // for MAX_SHORT_ATTEMPTS, then start doubling it up to MAX_INTERVAL - _retryInterval = ++_retryAttempts > MAX_SHORT_ATTEMPTS ? _retryInterval * 2 : RETRY_INTERVAL; - _retryInterval = Math.min(_retryInterval, MAX_INTERVAL); - } - } - - function onSocketOpen(socket) { - if (socket !== _socket) { - return; - } - logger.info('[CONN]: WebSocket is opened'); - // Randonmly distribute the first reconnection across RETRY_INTERVAL seconds - // So the clients won't swamp the Access Server all at once - _retryInterval = RETRY_DELAY + Utils.randomNumber(0, RETRY_INTERVAL); - _retryAttempts = 0; - - if (_state !== ConnectionState.Disconnected) { - setState(ConnectionState.Connected); - if (window.addEventListener) { - window.addEventListener('offline', handleOfflineEvent); - window.removeEventListener('online', handleOnlineEvent); - } - onSuccess(); - } - } - - function onSocketClose(socket, evt) { - if (socket !== _socket) { - return; - } - clearThrottlingData(); - evt = evt || {}; - logger.info('[CONN]: WebSocket is closed. Code: ' + evt.code + '. Reason:', evt.reason); - - switch (_state) { - case ConnectionState.Connected: - case ConnectionState.Reconnecting: - if (_state === ConnectionState.Reconnecting) { - raiseReconnectFailed(); - } - - setState(ConnectionState.Reconnecting); - retryConnection(); - break; - - case ConnectionState.Connecting: - disconnect(); - logger.error('[CONN]: Failed to open WebSocket connection'); - onError('Failed to open WebSocket connection'); - break; - - default: - // Socket is already Disconnected - break; - } - } - - function onSocketMessage(socket, message) { - if (socket !== _socket) { - return; - } - try { - if (!message.data || message.data === 'PING') { - // Ignore PING request - return; - } - - var msg = message.data; - if (!_config.doNotParseMessages) { - try { - msg = JSON.parse(msg); - } catch (err) { - logger.error('[CONN]: Message discarded (Cannot Parse): ', message.data); - return; - } - } - - // Do not log message here since we don't know which fields must be suppressed - - raiseMessage(msg); - - } catch (e) { - logger.error(e); - } - } - - function onSocketError(socket, error) { - if (socket !== _socket) { - return; - } - clearThrottlingData(); - if (_state !== ConnectionState.Reconnecting && - _state !== ConnectionState.Connected) { - logger.error('[CONN]: WebSocket Connection Error'); - setState(ConnectionState.Disconnected); - clearSocket(); - onError('Failed to open WebSocket connection'); - } else if (window.navigator.platform === 'node' || window.navigator.platform === 'iOS' || window.navigator.platform === 'Android') { - // we may not have received a _socket.onclose from the node websocket module - logger.error('[CONN]: WebSocket connection error. Force _socket.onclose. ', error); - _socket.onclose(); - } - } - - function openSocket() { - if (_state !== ConnectionState.Connecting && _state !== ConnectionState.Reconnecting) { - // We are neither connecting nor reconnecting. Stop here. - return; - } - - logger.info('[CONN]: Opening WebSocket to ', _targetObfuscated); - - try { - switch (window.navigator.platform) { - case 'iOS': - _socket = WebSocket.createWebSocket(_target); - break; - case 'node': - _socket = new WebSocket(_target, _config.cookie); - break; - default: - _socket = new WebSocket(_target); - } - } catch (e) { - logger.error('[CONN]: Failed to connect WebSocket. ', e); - onError(e); - return; - } - - _socket.onopen = onSocketOpen.bind(null, _socket); - _socket.onclose = onSocketClose.bind(null, _socket); - _socket.onmessage = onSocketMessage.bind(null, _socket); - _socket.onerror = onSocketError.bind(null, _socket); - } - - function clearSentTimestamps() { - var now = Date.now(); - var lastIdx = _sentTimestamps.length - 1; - _sentTimestamps.some(function (timestamp, idx) { - if (now - timestamp < THROTTLE_INTERVAL) { - _sentTimestamps.splice(0, idx); - return true; - } - if (idx === lastIdx) { - // Empty the array - _sentTimestamps = []; - } - }); - } - - function canSendMessage() { - if (_throttleTimeout) { - return false; - } - if (_sentTimestamps.length >= THROTTLE_MAX_MESSAGES) { - clearSentTimestamps(); - } - return _sentTimestamps.length < THROTTLE_MAX_MESSAGES; - } - - function startThrottleTimeout() { - if (!_throttleTimeout) { - var delay = MIN_THROTTLE_DELAY; - if (_sentTimestamps.length >= THROTTLE_MAX_MESSAGES) { - delay += THROTTLE_INTERVAL - (Date.now() - _sentTimestamps[0]); - } - logger.info('[CONN]: Start throttle timeout with delay = ', delay); - _throttleTimeout = window.setTimeout(function () { - _throttleTimeout = null; - dequeueMessages(); - }, delay); - } - } - - function sendMessage(data) { - try { - var reqId = (data.request && data.request.requestId) || 'N/A'; - _socket.send(JSON.stringify(data)); - _sentTimestamps.push(Date.now()); - logger.debug('[CONN]: Sent message with requestId:', reqId); - } catch (e) { - logger.error('[CONN]: Failed to send message. ', e); - } - } - - function dequeueMessages() { - logger.info('[CONN]: Dequeue throttled messages. Number of queued messages = ', _queuedMessages.length); - clearSentTimestamps(); - - var count = 0; - while (_queuedMessages.length > 0 && _sentTimestamps.length < THROTTLE_MAX_MESSAGES && count < MAX_DEQUEUED_MESSAGES) { - sendMessage(_queuedMessages.shift()); - count++; - } - if (_queuedMessages.length > 0) { - logger.warn('[CONN]: Number of remaining queued messages = ', _queuedMessages.length); - startThrottleTimeout(); - } else { - logger.info('[CONN]: Dequeued all throttled messages'); - } - } - - function queueMessage(data) { - logger.warn('[CONN]: Throttling limit exceeded. Queue the message.'); - if (_queuedMessages.length >= MAX_QUEUED_MESSAGES) { - logger.error('[CONN]: Throttling queue is full. Drop the message.'); - } else { - // Queue the new message - _queuedMessages.push(data); - logger.info('[CONN]: Queued message #', _queuedMessages.length); - } - } - - ///////////////////////////////////////////////////////////////////////////// - // Public Event Handlers - ///////////////////////////////////////////////////////////////////////////// - this.onStateChange = null; - this.onReconnectFailed = null; - this.onMessage = null; - - ///////////////////////////////////////////////////////////////////////////// - // Public interfaces - ///////////////////////////////////////////////////////////////////////////// - this.getState = function () { return _state; }; - - this.isConnected = function () { - return _state === ConnectionState.Connected; - }; - - this.setTarget = function (url) { - _target = url; - _targetObfuscated = _target.split('?')[0]; - }; - - this.connect = function (url, successCallback, errorCallback) { - logger.debug('[CONN]: Connect WebSocket to ', url); - if (_state !== ConnectionState.Disconnected) { - // Disconnect the socket but do not raise an event - disconnect(true); - } - - _that.setTarget(url); - - _successCb = successCallback || function () {}; - _errorCb = errorCallback || function () {}; - - setState(ConnectionState.Connecting); - // Use timeout to open socket after the connecting state is set. - window.setTimeout(openSocket, 0); - }; - - this.disconnect = function () { - logger.debug('[CONN]: Disconnect WebSocket from ', _targetObfuscated); - disconnect(); - }; - - this.reconnect = function (url, delay) { - logger.debug('[CONN]: Reconnect WebSocket to ', _targetObfuscated); - // First disconnect the socket (if opened). - disconnect(true); - - // Update the url since the token uri parameter may have changed - url && _that.setTarget(url); - - // Now start a small timeout to wait for the socket to close (if it was opened) - // and then open it again. - setState(ConnectionState.Reconnecting); - _reconnectTimeout = window.setTimeout(function () { - _reconnectTimeout = null; - openSocket(); - }, delay || 50); - }; - - this.sendMessage = function (data, suppressLog) { - if (_state !== ConnectionState.Connected) { - logger.warn('[CONN]: WebSocket is not connected. Cannot send message. ', data); - return false; - } - - if (!data) { - logger.error('[CONN]: Data is missing. Cannot send message.'); - return false; - } - - if (!suppressLog) { - logger.msgSend('[CONN]: ', data); - } - - if (typeof data === 'string') { - if (data === 'PING' && Utils.isMobile()) { - _socket.ping(); // Mobile client's implementation - } else { - _socket.send(data); - } - } else { - delete data.keysToOmitFromLogging; - if (canSendMessage()) { - sendMessage(data); - } else { - queueMessage(data); - startThrottleTimeout(); - } - } - return true; - }; - } - - // Exports - circuit.ConnectionController = ConnectionController; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.ConnectionState = ConnectionState; - - return circuit; -})(Circuit); - -// Define global variables for JSHint -/*global HTMLMediaElement, MediaStream, MediaStreamTrack, mozRTCIceCandidate, mozRTCPeerConnection, -mozRTCSessionDescription, navigator, RTCIceCandidate, RTCPeerConnection, RTCSessionDescription, -URL, webkitMediaStream, webkitRTCPeerConnection, window */ - - -///////////////////////////////////////////////////////////////////////////////////////// -// -// The following table shows the different names used by Chrome and Firefox for the -// standard Media Capture and WebRTC APIs. -// -// W3C Standard Chrome Firefox Mobile -// -------------------------------------------------------------------------------------- -// getUserMedia webkitGetUserMedia getUserMedia getUserMedia -// RTCPeerConnection webkitRTCPeerConnection RTCPeerConnection RTCPeerConnection -// RTCSessionDescription RTCSessionDescription RTCSessionDescription RTCPeerConnection -// RTCIceCandidate RTCIceCandidate RTCIceCandidate RTCIceCandidate -// -// -// The WebRTCAdapter object helps insulate our apps from these differences. -// -///////////////////////////////////////////////////////////////////////////////////////// - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = circuit.logger; - var TemasysAdapter = circuit.TemasysAdapter; - - var _browser = circuit.Utils.getBrowserInfo(); - - try { - ///////////////////////////////////////////////////////////////////////////////// - // Helper functions - ///////////////////////////////////////////////////////////////////////////////// - var stopMediaStream = function (stream) { - if (stream) { - try { - stream.oninactive = null; - // In the latest W3C definition the stop function belongs in the - // MediaStreamTrack, not the MediaStream. But if MediaStreamTrack.stop() - // is not present, fallback to MediaStream.stop(). - var tracks = stream.getAudioTracks().concat(stream.getVideoTracks()); - if (tracks.length && tracks[0].stop) { - tracks.forEach(function (t) { - t.stop(); - }); - } else if (stream.stop) { - stream.stop(); - } - logger.info('[WebRTCAdapter]: Media stream has been stopped'); - } catch (e) { - logger.error('[WebRTCAdapter]: Failed to stop media stream. ', e); - } - } - }; - - var stopLocalVideoTrack = function (stream) { - if (stream) { - try { - var tracks = stream.getVideoTracks(); - if (tracks) { - tracks.forEach(function (track) { - if (track.stop && track.kind === 'video' && !track.remote) { - track.stop(); - } - }); - } else { - logger.info('[WebRTCAdapter]: No video tracks to be changed'); - } - } catch (e) { - logger.error('[WebRTCAdapter]: Failed to disable local video tracks. ', e); - } - } - }; - - var closePc = function (pc) { - try { - pc.close(); - logger.debug('[WebRTCAdapter]: RTCPeerConnection has been closed'); - } catch (e) { - logger.error(e); - } - }; - - var supportsAudioOutputSelection = function () { - // Check whether MediaDevices' enumerateDevices and HTMLMediaElement's setSinkId APIs are available. - // These are experimental APIs supported in Chrome 45.0.2441.x or later. - // You can enable it by selecting "Enable experimental Web Platform features" in chrome://flags - - return !!navigator.mediaDevices && - (typeof navigator.mediaDevices.enumerateDevices === 'function') && - (typeof HTMLMediaElement !== 'undefined') && - (typeof HTMLMediaElement.prototype.setSinkId === 'function'); - }; - - var pendingEnumerateDevicesRequests = []; - var cachedDeviceInfos = null; - var CACHED_DEVICE_INFOS_TIMEOUT = 5000; // Keep cached devices for 5 seconds - - var splitDeviceInfos = function (cb) { - if (typeof cb !== 'function') { - return; - } - var audioSources, audioOutputs, videoSources; - - logger.debug('[WebRTCAdapter]: Get audio output, microphone and camera devices'); - - if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { - audioSources = [{kind: 'audio', id: 'default', label: '', facing: ''}]; - videoSources = [{kind: 'video', id: 'default', label: '', facing: ''}]; - window.setTimeout(function () { - cb(audioSources, videoSources, null); - }, 0); - return; - } - - if (cachedDeviceInfos && (cachedDeviceInfos.validTimestamp > Date.now())) { - logger.debug('[WebRTCAdapter]: Return cached devices'); - audioSources = cachedDeviceInfos.audioSources; - audioOutputs = cachedDeviceInfos.audioOutputs; - videoSources = cachedDeviceInfos.videoSources; - - window.setTimeout(function () { - cb(audioSources, videoSources, audioOutputs); - }, 0); - return; - } - - pendingEnumerateDevicesRequests.push(cb); - - if (pendingEnumerateDevicesRequests.length > 1) { - logger.debug('[WebRTCAdapter]: There is a pending MediaDevices.enumerateDevices() request'); - return; - } - - logger.info('[WebRTCAdapter]: Invoke MediaDevices.enumerateDevices()'); - - var invokePendingCallbacks = function (audioSources, videoSources, audioOutputs) { - pendingEnumerateDevicesRequests.forEach(function (cb) { - try { - cb(audioSources, videoSources, audioOutputs); - } catch (err) {} - }); - pendingEnumerateDevicesRequests = []; - }; - - cachedDeviceInfos = null; - navigator.mediaDevices.enumerateDevices() - .then(function (deviceInfos) { - deviceInfos = deviceInfos || []; - audioSources = []; - audioOutputs = []; - videoSources = []; - - logger.info('[WebRTCAdapter]: MediaDevices.enumerateDevices returned ' + deviceInfos.length + ' device(s)'); - - deviceInfos.forEach(function (info) { - var item = { - id: info.deviceId, - groupId: info.groupId, - kind: info.kind, - label: info.label - }; - - // Fallback for electron issue: https://github.com/atom/electron/issues/4931 - if (Circuit.isElectron) { - if (item.id === 'default' && !item.label) { - item.label = 'res_DefaultDeviceLabel'; - } - } - - switch (item.kind) { - case 'audioinput': - audioSources.push(item); - break; - case 'audiooutput': - audioOutputs.push(item); - break; - case 'videoinput': - videoSources.push(item); - break; - } - }); - - // Only return audioOutputs if there is at least one device. - // Also make sure the browser supports the HTMLMediaElement setSinkId API. - if (audioOutputs.length === 0 || !supportsAudioOutputSelection()) { - audioOutputs = null; - } - - cachedDeviceInfos = { - validTimestamp: Date.now() + CACHED_DEVICE_INFOS_TIMEOUT, - audioSources: audioSources, - audioOutputs: audioOutputs, - videoSources: videoSources - }; - - invokePendingCallbacks(audioSources, videoSources, audioOutputs); - }) - .catch(function (e) { - logger.error('[WebRTCAdapter]: Failed to enumerate devices. ', e); - invokePendingCallbacks(); - }); - }; - - var clearDeviceInfoCache = function () { - logger.debug('[WebRTCAdapter]: Cleared device info cache'); - cachedDeviceInfos = null; - }; - - var separateIceServers = function (origConfig) { - if (!origConfig || !origConfig.iceServers) { - return origConfig; - } - var iceServers = []; - origConfig.iceServers.forEach(function (iceServer) { - if (iceServer.url) { - iceServers.push(iceServer); - } else { - iceServer.urls && iceServer.urls.forEach(function (url) { - iceServers.push({ - url: url, - credential: iceServer.credential, - username: iceServer.username - }); - }); - } - }); - - return { - iceServers: iceServers - }; - }; - - var getDefaultAudioOptions = function (config) { - config = config || {}; - var audio = { - optional: [ - // Echo cancellation constraints - {echoCancellation: true}, - {googEchoCancellation: true}, - {googEchoCancellation2: true}, - {autoGainControl: !!config.enableAudioAGC}, - {googAutoGainControl: !!config.enableAudioAGC}, - {googAutoGainControl2: !!config.enableAudioAGC}, - {noiseSuppression: true}, - {googNoiseSuppression: true}, - {googNoiseSuppression2: true}, - {googHighpassFilter: true}, - // Disable audio ducking - {googDucking: false}, - // By default, use the output device associated to the microphone (useful for headsets) - {chromeRenderToAssociatedSink: true} - ] - }; - return audio; - }; - - var getDefaultVideoOptions = function (config) { - if (config && config.hdVideo) { - var options = { - mandatory: { - // 16:9 Video - minAspectRatio: 1.76, - maxAspectRatio: 1.78, - // FrameRate - minFrameRate: 30 - }, - optional: [ - {googNoiseReduction: true} - ] - }; - switch (config.hdVideo) { - case '1080p': - // Full HD - options.mandatory.minWidth = 1920; - options.mandatory.minHeight = 1080; - break; - case '720p': - // HD - options.mandatory.minWidth = 1280; - options.mandatory.minHeight = 720; - break; - } - return options; - } - // Non HD - return { - mandatory: { - // 4:3 Video - minAspectRatio: 1.32, - maxAspectRatio: 1.34 - }, - optional: [ - {googNoiseReduction: true} - ] - }; - }; - - var toggleAudio = function (stream, enable) { - var audio = stream && stream.getAudioTracks(); - if (audio && audio[0]) { - audio[0].enabled = !!enable; - return true; - } - return false; - }; - - var attachSinkIdToAudioElement = function (audioElement, deviceList, cb) { - cb = cb || function () {}; - - logger.debug('[WebRTCAdapter]: Attach sinkId to audio element'); - - if (!audioElement) { - cb('Invalid parameters'); - return; - } - if (!deviceList || !deviceList.length) { - logger.debug('[WebRTCAdapter]: There is no audio output device selected'); - cb(); - return; - } - if (typeof audioElement.sinkId !== 'undefined' && circuit.WebRTCAdapter) { - // In order to find out which device in deviceList can be used, we need to get the latest list of - // devices that are plugged in - logger.debug('[WebRTCAdapter]: Get media sources to find available device'); - circuit.WebRTCAdapter.getMediaSources(function (audioSources, videoSources, audioOutputDevices) { - var foundMatch = circuit.Utils.selectMediaDevice(audioOutputDevices, deviceList); - var deviceId = (foundMatch && foundMatch.id) || ''; - - logger.debug('[WebRTCAdapter]: Found audio output device to attach as sinkId. deviceId = ', deviceId || 'NONE'); - - if (audioElement.sinkId === deviceId) { - // Sink ID already set - logger.debug('[WebRTCAdapter]: Audio output device is already attached to element'); - cb(); - return; - } - - if (!deviceId) { - // We had another device configured as the sink ID and we are falling back to the default device - logger.warn('[WebRTCAdapter]: A previously selected output device could not be found. Fallback to browser\'s default. Device not found: ', audioElement.sinkId); - } - - audioElement.setSinkId(deviceId) - .then(function () { - logger.debug('[WebRTCAdapter]: Success, audio output device attached: ', deviceId || '<default>'); - cb(); - }) - .catch(function (error) { - error = error || {}; - var errorMessage = error.message || 'unknown error'; - if (error.name === 'SecurityError') { - errorMessage = 'You need to use HTTPS for selecting audio output device.'; - } - logger.error('[WebRTCAdapter]: Failed to attach output device. Device Id:' + deviceId + ' - ', errorMessage); - cb(errorMessage); - }); - }); - } else { - cb('setSinkId not available'); - } - }; - - var convertDeprecatedSdpConstraints = function (deprecatedConstraints) { - var offerOptions = { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 - }; - if (deprecatedConstraints) { - if (deprecatedConstraints.mandatory) { - if (deprecatedConstraints.mandatory.hasOwnProperty('OfferToReceiveAudio')) { - offerOptions.offerToReceiveAudio = deprecatedConstraints.mandatory.OfferToReceiveAudio ? 1 : 0; - } - if (deprecatedConstraints.mandatory.hasOwnProperty('OfferToReceiveVideo')) { - offerOptions.offerToReceiveVideo = deprecatedConstraints.mandatory.OfferToReceiveVideo ? 1 : 0; - } - } - if (deprecatedConstraints.optional) { - deprecatedConstraints.optional.some(function (constraint) { - if (constraint.hasOwnProperty('VoiceActivityDetection')) { - offerOptions.voiceActivityDetection = !!constraint.VoiceActivityDetection; - return true; - } - }); - } - } - return offerOptions; - }; - - var timedGetUserMedia = function (constraints, successCallback, errorCallback) { - // Firefox has some bugs regarding 'getUserMedia' popup: - // Bug 947266 - Navigator.getUserMedia should either remove or let webpages detect 'Not now' case - // Bug 846584 - WebRTC getUserMedia doorhanger has 3 ways of rejecting the request, and there's no clear distinction between them - // - // Chrome also has some problems with very lengthy or unresponsive getUserMedia's - // Desktop App doesn't need to request permission to the user, so the timeout is shorter (10s) - // - // Workaround by implementing timeout for recalling/reshowing 'getUserMedia' request - - var getUserMediaTimeout = null; - var getUserMediaLimit = Circuit.isElectron ? 1 : 3; - var getUserMediaCounter = 0; - - function onSuccess(stream) { - logger.info('[WebRTCAdapter]: Get user media succeeded'); - if (successCallback) { - successCallback(stream); - } else { - // Stop the media stream since it won't be used anymore - stopMediaStream(stream); - } - successCallback = null; - errorCallback = null; - } - - function onError(error) { - logger.error('[WebRTCAdapter]: Get user media failed: ', error); - errorCallback && errorCallback(error); - successCallback = null; - errorCallback = null; - } - - function getUserMediaHandler() { - logger.info('[WebRTCAdapter]: Get user media attempt #:', getUserMediaCounter); - getUserMediaTimeout = null; - if (getUserMediaCounter === getUserMediaLimit) { - onError('Timeout'); - return; - } - ++getUserMediaCounter; - navigator.mediaDevices.getUserMedia(constraints) - .then(onSuccess) - .catch(onError) - .then(function () { - getUserMediaTimeout && window.clearTimeout(getUserMediaTimeout); - getUserMediaTimeout = null; - }); - - getUserMediaTimeout = window.setTimeout(getUserMediaHandler, 10000); - } - - getUserMediaHandler(); - }; - - ///////////////////////////////////////////////////////////////////////////////// - // Chrome - ///////////////////////////////////////////////////////////////////////////////// - if (_browser.chrome) { - logger.info('Setting WebRTC APIs for Chrome'); - - // Temporary workaround to solve Chrome issue #467490 - // https://code.google.com/p/chromium/issues/detail?id=467490 - RTCSessionDescription.prototype.toJSON = function () { - return { - type: this.type, - sdp: this.sdp - }; - }; - - RTCIceCandidate.prototype.toJSON = function () { - return { - sdpMLineIndex: this.sdpMLineIndex, - sdpMid: this.sdpMid, - candidate: this.candidate - }; - }; - - circuit.WebRTCAdapter = { - enabled: true, - browser: 'chrome', - MediaStream: webkitMediaStream, - PeerConnection: function (config, constraints) { - var pc = new webkitRTCPeerConnection(config, constraints); - pc.origCreateOffer = pc.createOffer; - pc.createOffer = function (successCb, errorCb, offerConstraints) { - var offerOptions = convertDeprecatedSdpConstraints(offerConstraints); - pc.origCreateOffer(offerOptions) - .then(function (sdp) { - successCb && successCb(sdp); - }) - .catch(function (err) { - errorCb && errorCb(err); - }); - }; - pc.origCreateAnswer = pc.createAnswer; - pc.createAnswer = function (successCb, errorCb, options) { - var answerOptions = convertDeprecatedSdpConstraints(options); - pc.origCreateAnswer(answerOptions) - .then(function (sdp) { - successCb && successCb(sdp); - }) - .catch(function (err) { - errorCb && errorCb(err); - }); - }; - return pc; - }, - SessionDescription: RTCSessionDescription, - IceCandidate: RTCIceCandidate, - getUserMedia: timedGetUserMedia, - createObjectURL: URL.createObjectURL.bind(URL), - getAudioStreamTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? stream.id + ' ' + audioTrack.id : ''; - }, - getVideoStreamTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? stream.id + ' ' + videoTrack.id : ''; - }, - getAudioTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.id : ''; - }, - getVideoTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.id : ''; - }, - hasDeviceNameOnTrackLabel: true, - getAudioTrackLabel: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.label : ''; - }, - getVideoTrackLabel: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: closePc, - getMediaSources: function (cb) { - cb && splitDeviceInfos(cb); - }, - clearMediaSourcesCache: function () { - clearDeviceInfoCache(); - }, - getAudioOptions: function (config) { - var audio = getDefaultAudioOptions(config); - if (config && config.sourceId) { - audio.optional.push({sourceId: config.sourceId}); - } - return audio; - }, - getVideoOptions: function (config) { - var video = getDefaultVideoOptions(config); - if (config && config.sourceId) { - video.optional.push({sourceId: config.sourceId}); - } - return video; - }, - getDesktopOptions: function (streamId, screenConstraint) { - screenConstraint = screenConstraint || {}; - var fr = screenConstraint.frameRate || {}; - return { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: streamId, - maxWidth: 2560, - maxHeight: 1600 - }, - optional: [ - {googNoiseReduction: true}, - {maxFrameRate: fr.max || 10}, - {minFrameRate: fr.min || 5} - ] - }; - }, - audioOutputSelectionSupported: supportsAudioOutputSelection(), - groupPeerConnectionsSupported: true, - getMediaSourcesSupported: true, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - toggleAudio: toggleAudio - }; - return circuit; - } - - ///////////////////////////////////////////////////////////////////////////////// - // Firefox - ///////////////////////////////////////////////////////////////////////////////// - if (_browser.firefox) { - logger.info('Setting WebRTC APIs for Firefox'); - - var convertStats = function (stats) { - var items = []; - stats.forEach(function (item) { - items.push(new Stat(item)); - }); - return items; - }; - - // Convert Firefox stats to Chrome's format - var Stat = function (stats) { - this.stats = stats; - this.id = stats.id; - switch (stats.type) { - case 'inboundrtp': - case 'inbound-rtp': - if (!stats.isRemote) { - this.type = 'ssrc'; - if (stats.mediaType === 'audio') { - this.stats.audioOutputLevel = true; - this.stats.googJitterReceived = stats.jitter; - } else { - this.stats.googFrameHeightReceived = true; - } - } - break; - case 'outboundrtp': - case 'outbound-rtp': - if (!stats.isRemote) { - this.type = 'ssrc'; - if (stats.mediaType === 'audio') { - this.stats.audioInputLevel = true; - } else { - this.stats.googFrameHeightSent = true; - } - } - break; - case 'candidatepair': - case 'candidate-pair': - // Candidatepair info is now available in FF, but there's no indication of what - // kind of media they belong to yet. Still waiting for more getStats improvements: - // https://bugzilla.mozilla.org/showdependencytree.cgi?id=964161&hide_resolved=1 - this.type = 'googCandidatePair'; - if (typeof stats.selected === 'boolean') { - this.googActiveConnection = stats.selected ? 'true' : 'false'; - } - break; - default: - this.type = stats.type; - break; - } - }; - Stat.prototype.names = function () { - return Object.keys(this.stats); - }; - Stat.prototype.stat = function (type) { - return this.stats[type]; - }; - - // Firefox has deprecated the 'moz' prefix, but we still need with older versions - var PeerConnection = typeof RTCPeerConnection === 'undefined' ? mozRTCPeerConnection : RTCPeerConnection; - var SessionDescription = typeof RTCSessionDescription === 'undefined' ? mozRTCSessionDescription : RTCSessionDescription; - var IceCandidate = typeof RTCIceCandidate === 'undefined' ? mozRTCIceCandidate : RTCIceCandidate; - - circuit.WebRTCAdapter = { - enabled: true, - browser: 'firefox', - MediaStream: MediaStream, - PeerConnection: function (configuration) { - var pc = new PeerConnection(configuration); - - pc.origCreateOffer = pc.createOffer; - pc.createOffer = function (successCb, errorCb, offerConstraints) { - var offerOptions = convertDeprecatedSdpConstraints(offerConstraints); - pc.origCreateOffer(offerOptions) - .then(function (sdp) { - successCb && successCb(sdp); - }) - .catch(function (err) { - errorCb && errorCb(err); - }); - }; - pc.origCreateAnswer = pc.createAnswer; - pc.createAnswer = function (successCb, errorCb, options) { - var answerOptions = convertDeprecatedSdpConstraints(options); - pc.origCreateAnswer(answerOptions) - .then(function (sdp) { - successCb && successCb(sdp); - }) - .catch(function (err) { - errorCb && errorCb(err); - }); - }; - - pc.origGetStats = pc.getStats; - pc.getStats = function (successCallback, errorCallback) { - if (typeof successCallback !== 'function') { - return; - } - pc.origGetStats().then(function (stats) { - successCallback(convertStats(stats)); - }, errorCallback); - }; - pc.removeStream = function () {}; - pc.createDTMFSender = function (audioTrack) { - if (typeof pc.getSenders !== 'function') { - return null; - } - var audioSender = pc.getSenders().find(function (sender) { - return sender.track === audioTrack; - }); - var dtmfSender = audioSender && audioSender.dtmf; - if (!dtmfSender) { - return null; - } - dtmfSender.canInsertDTMF = true; // set obsolete property - return dtmfSender; - }; - - if (parseInt(_browser.version, 10) >= 46) { - // Override deprecated onaddstream property for Firefox v46 and newer - Object.defineProperty(pc, 'onaddstream', { - enumerable: true, - get: function () { - return pc.onaddstreamCb; - }, - set: function (cb) { - pc.onaddstreamCb = cb; - if (cb) { - // Use ontrack instead of onaddstream - pc.ontrack = function (event) { - cb({stream: event && event.streams && event.streams[0]}); - }; - } else { - pc.ontrack = null; - } - }.bind(pc) - }); - } - - return pc; - }, - SessionDescription: SessionDescription, - IceCandidate: IceCandidate, - getUserMedia: timedGetUserMedia, - createObjectURL: function (object) { - return URL.createObjectURL(object); - }, - getAudioStreamTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? stream.id + ' ' + audioTrack.id : ''; - }, - getVideoStreamTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? stream.id + ' ' + videoTrack.id : ''; - }, - getAudioTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.id : ''; - }, - getVideoTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.id : ''; - }, - hasDeviceNameOnTrackLabel: false, - getAudioTrackLabel: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.label : ''; - }, - getVideoTrackLabel: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: function (pc) { - // Wait 500ms before closing the peer connection to give time for the stats to be collected - window.setTimeout(function () { - closePc(pc); - }, 500); - }, - getMediaSources: function (cb) { - cb && splitDeviceInfos(cb); - }, - clearMediaSourcesCache: function () { - clearDeviceInfoCache(); - }, - getAudioOptions: function (config) { - var audio = getDefaultAudioOptions(config); - if (config && config.sourceId) { - audio.deviceId = config.sourceId; - } - audio.advanced = audio.optional; - delete audio.optional; - return audio; - }, - getVideoOptions: function (config) { - var video = getDefaultVideoOptions(config); - if (config && config.sourceId) { - video.deviceId = config.sourceId; - } - video.advanced = video.optional; - delete video.optional; - return video; - }, - getDesktopOptions: function (streamId, screenConstraint) { - screenConstraint = screenConstraint || {}; - var fr = screenConstraint.frameRate || {}; - fr.min = fr.min || 5; - fr.max = fr.max || 10; - - return { - mozMediaSource: streamId || 'screen', // Deprecated - mediaSource: streamId || 'screen', - width: {max: 2560}, - height: {max: 1600}, - advanced: [{frameRate: fr}] - }; - }, - audioOutputSelectionSupported: supportsAudioOutputSelection(), - groupPeerConnectionsSupported: true, - getMediaSourcesSupported: true, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - iceTimeoutSafetyFactor: 0.5, // Wait 50% more when there are multiple peer connections - toggleAudio: toggleAudio - }; - - return circuit; - } - - - ///////////////////////////////////////////////////////////////////////////////// - // iOS - ///////////////////////////////////////////////////////////////////////////////// - if (_browser.ios) { - logger.info('Setting WebRTC APIs for iOS client'); - - var RTCStatsReport = function (iosReport) { - this.id = iosReport.id; - this.type = iosReport.type; - this.timestamp = new Date(iosReport.timestamp); - - var _stats = {}; - Object.getOwnPropertyNames(iosReport.values).forEach(function (name) { - _stats[name] = iosReport.values[name]; - }); - - this.names = function () { - return Object.getOwnPropertyNames(_stats); - }; - - this.stat = function (name) { - return _stats[name]; - }; - - iosReport = null; - }; - - var RTCStatsResponse = function (iosReports) { - var _reports = iosReports.map(function (iosReport) { - return new RTCStatsReport(iosReport); - }); - - this.result = function () { - return _reports; - }; - - iosReports = null; - }; - - circuit.WebRTCAdapter = { - enabled: true, - browser: 'ios', - PeerConnection: function (configuration, constraints) { - configuration = separateIceServers(configuration); - var pc = RTCPeerConnection.createRTCPeerConnection(configuration, constraints); - pc.origGetStats = pc.getStats; - pc.getStats = function (cb) { - if (typeof cb !== 'function') { - return; - } - pc.origGetStats(function (iosStats) { - // Convert stats to the same format as the webclient - cb(new RTCStatsResponse(iosStats)); - }); - }; - - // Renaming the folowing methods / attributes due to WebRTC ObJ-C interface name conflicts. - pc.addIceCandidate = pc.addIceCandidateJS; - - Object.defineProperties(pc, { - localDescription: { - get: function () { - return pc.getLocalDescriptionJS(); - }, - enumerable: true, - configurable: false - }, - remoteDescription: { - get: function () { - return pc.getRemoteDescriptionJS(); - }, - enumerable: true, - configurable: false - }, - signalingState: { - get: function () { - return pc.getSignalingStateJS(); - }, - enumerable: true, - configurable: false - }, - iceConnectionState: { - get: function () { - return pc.getIceConnectionStateJS(); - }, - enumerable: true, - configurable: false - }, - iceGatheringState: { - get: function () { - return pc.getIceGatheringStateJS(); - }, - enumerable: true, - configurable: false - } - }); - - return pc; - }, - SessionDescription: function (descriptionInitDict) { - var sdp = RTCSessionDescription.createRTCSessionDescription(descriptionInitDict); - return sdp; - }, - IceCandidate: function (candidateInitDict) { - return { - sdpMLineIndex: candidateInitDict.sdpMLineIndex, - sdpMid: candidateInitDict.sdpMid, - candidate: candidateInitDict.candidate - }; - }, - getUserMedia: navigator.getUserMedia.bind(navigator), - createObjectURL: URL.createObjectURL.bind(URL), - getAudioStreamTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? stream.streamId + ' ' + audioTrack.trackId : ''; - }, - getVideoStreamTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? stream.streamId + ' ' + videoTrack.trackId : ''; - }, - getAudioTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.trackId : ''; - }, - getVideoTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.trackId : ''; - }, - hasDeviceNameOnTrackLabel: false, - getAudioTrackLabel: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.label : ''; - }, - getVideoTrackLabel: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: closePc, - getMediaSources: function (cb) { - cb && cb([], []); - }, - clearMediaSourcesCache: function () {}, - getAudioOptions: getDefaultAudioOptions, - getVideoOptions: getDefaultVideoOptions, - getDesktopOptions: function () { - return { - mandatory: { - iOSMediaSource: 'desktop' - } - }; - }, - audioOutputSelectionSupported: false, - groupPeerConnectionsSupported: false, - getMediaSourcesSupported: false, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - toggleAudio: toggleAudio - }; - return circuit; - } - - - ///////////////////////////////////////////////////////////////////////////////// - // Android - ///////////////////////////////////////////////////////////////////////////////// - if (_browser.android) { - logger.info('Setting WebRTC APIs for Android client'); - - var AndroidRTCStatsReport = function (androidReport) { - this.id = androidReport.id; - this.type = androidReport.type; - this.timestamp = new Date(androidReport.timestamp); - - var _stats = {}; - Object.getOwnPropertyNames(androidReport.values).forEach(function (name) { - var statValue = androidReport.values[name]; - if (statValue.name) { - _stats[statValue.name] = statValue.value; - } - }); - - this.names = function () { - return Object.getOwnPropertyNames(_stats); - }; - - this.stat = function (name) { - return _stats[name]; - }; - - androidReport = null; - }; - - var AndroidRTCStatsResponse = function (androidReports) { - var _reports = androidReports.map(function (androidReport) { - return new AndroidRTCStatsReport(androidReport); - }); - - this.result = function () { - return _reports; - }; - - androidReports = null; - }; - - // RTCPeerConnection is not a class for Android - var rtcPeerConnectionConstructor = RTCPeerConnection.bind(RTCPeerConnection); - - circuit.WebRTCAdapter = { - enabled: true, - browser: 'android', - PeerConnection: function (configuration, constraints) { - configuration = separateIceServers(configuration); - var pc = rtcPeerConnectionConstructor(configuration, constraints); - pc.getStats = function (cb) { - if (typeof cb !== 'function') { - return; - } - pc.origGetStats(function (androidStats) { - cb(new AndroidRTCStatsResponse(androidStats.data)); - }); - }; - return pc; - }, - SessionDescription: function (descriptionInitDict) { - return { - type: descriptionInitDict.type, - sdp: descriptionInitDict.sdp - }; - }, - IceCandidate: function (candidateInitDict) { - return { - sdpMLineIndex: candidateInitDict.sdpMLineIndex, - sdpMid: candidateInitDict.sdpMid, - candidate: candidateInitDict.candidate - }; - }, - getUserMedia: navigator.getUserMedia.bind(navigator), - createObjectURL: function () { return 'dummy'; }, - getAudioStreamTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? stream.id + ' ' + audioTrack.id : ''; - }, - getVideoStreamTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? stream.id + ' ' + videoTrack.id : ''; - }, - getAudioTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.id : ''; - }, - getVideoTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.id : ''; - }, - hasDeviceNameOnTrackLabel: false, - getAudioTrackLabel: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.label : ''; - }, - getVideoTrackLabel: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: closePc, - getMediaSources: function (cb) { - cb && cb([], []); - }, - clearMediaSourcesCache: function () {}, - getAudioOptions: getDefaultAudioOptions, - getVideoOptions: getDefaultVideoOptions, - getDesktopOptions: function () { - return { - mandatory: { - androidMediaSource: 'screen' - } - }; - }, - audioOutputSelectionSupported: false, - groupPeerConnectionsSupported: false, - getMediaSourcesSupported: false, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - toggleAudio: toggleAudio - }; - return circuit; - } - - ///////////////////////////////////////////////////////////////////////////////// - // .NET - ///////////////////////////////////////////////////////////////////////////////// - if (_browser.dotnet) { - logger.info('Setting WebRTC APIs for .NET client'); - circuit.WebRTCAdapter = { - enabled: true, - browser: 'dotnet', - PeerConnection: function (configuration, constraints) { - configuration = separateIceServers(configuration); - var pc = new RTCPeerConnection(configuration, constraints); - pc.origGetStats = pc.getStats; - pc.getStats = function (cb) { - if (typeof cb !== 'function') { - return; - } - cb(); - }; - - Object.defineProperties(pc, { - localDescription: { - get: function () { - return pc.getLocalDescription(); - }, - enumerable: true, - configurable: false - }, - remoteDescription: { - get: function () { - return pc.getRemoteDescription(); - }, - enumerable: true, - configurable: false - }, - signalingState: { - get: function () { - return pc.getSignalingState(); - }, - enumerable: true, - configurable: false - }, - iceConnectionState: { - get: function () { - return pc.getIceConnectionState(); - }, - enumerable: true, - configurable: false - }, - iceGatheringState: { - get: function () { - return pc.getIceGatheringState(); - }, - enumerable: true, - configurable: false - } - }); - - return pc; - }, - SessionDescription: function (desc) { - return { - type: desc.type, - sdp: desc.sdp - }; - }, - IceCandidate: function (candidateInitDict) { - return { - sdpMLineIndex: candidateInitDict.sdpMLineIndex, - sdpMid: candidateInitDict.sdpMid, - candidate: candidateInitDict.candidate - }; - }, - getUserMedia: function (constraints, successCb, errorCb) { - var newContraints = { - audioEnabled: !!(constraints && constraints.audio), - videoEnabled: !!(constraints && constraints.video) - }; - return navigator.getUserMedia(newContraints, successCb, errorCb); - }, - createObjectURL: function (stream) { - return stream.id; - }, - getAudioStreamTrackId: function (stream) { - var tracks = stream && stream.getAudioTracks(); - return tracks && tracks.length ? stream.id + ' ' + tracks[0].id : ''; - }, - getVideoStreamTrackId: function (stream) { - var tracks = stream && stream.getVideoTracks(); - return tracks && tracks.length ? stream.id + ' ' + tracks[0].id : ''; - }, - getAudioTrackId: function (stream) { - var tracks = stream && stream.getAudioTracks(); - return tracks && tracks.length ? tracks[0].id : ''; - }, - getVideoTrackId: function (stream) { - var tracks = stream && stream.getVideoTracks(); - return tracks && tracks.length ? tracks[0].id : ''; - }, - hasDeviceNameOnTrackLabel: true, - getAudioTrackLabel: function (stream) { - var tracks = stream && stream.getAudioTracks(); - return tracks && tracks.length ? tracks[0].label : ''; - }, - getVideoTrackLabel: function (stream) { - var tracks = stream && stream.getVideoTracks(); - return tracks && tracks.length ? tracks[0].label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: closePc, - getMediaSources: function (cb) { - cb && cb([], []); - }, - clearMediaSourcesCache: function () {}, - getAudioOptions: function (config) { - var audio = getDefaultAudioOptions(config); - if (config && config.sourceId) { - audio.optional.push({ sourceId: config.sourceId }); - } - return audio; - }, - getVideoOptions: function (config) { - var video = getDefaultVideoOptions(config); - if (config && config.sourceId) { - video.optional.push({ sourceId: config.sourceId }); - } - return video; - }, - getDesktopOptions: function (streamId, screenConstraint) { - screenConstraint = screenConstraint || {}; - var fr = screenConstraint.frameRate || {}; - return { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: streamId, - maxWidth: 2560, - maxHeight: 1600 - }, - optional: [ - { googNoiseReduction: true }, - { maxFrameRate: fr.max || 10 }, - { minFrameRate: fr.min || 5 } - ] - }; - }, - audioOutputSelectionSupported: supportsAudioOutputSelection(), - groupPeerConnectionsSupported: false, - getMediaSourcesSupported: false, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - toggleAudio: toggleAudio - }; - - return circuit; - } - - ///////////////////////////////////////////////////////////////////////////////// - // Internet Explorer with Temasys plugin - ///////////////////////////////////////////////////////////////////////////////// - if (TemasysAdapter) { - logger.info('Setting WebRTC APIs for IE with Temasys plugin'); - - circuit.WebRTCAdapter = { - enabled: true, - browser: 'msie', - PeerConnection: function (configuration, constraints) { - configuration = separateIceServers(configuration); - return TemasysAdapter.PeerConnection(configuration, constraints); - }, - SessionDescription: TemasysAdapter.SessionDescription, - IceCandidate: TemasysAdapter.IceCandidate, - getUserMedia: TemasysAdapter.getUserMedia, - createObjectURL: function (object, options, isRemote) { - return TemasysAdapter.createObjectURL(object, isRemote); - }, - attachMediaStream: TemasysAdapter.attachMediaStream, - getAudioStreamTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? stream.label + ' ' + audioTrack.id : ''; - }, - getVideoStreamTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? stream.label + ' ' + videoTrack.id : ''; - }, - getAudioTrackId: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.id : ''; - }, - getVideoTrackId: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.id : ''; - }, - hasDeviceNameOnTrackLabel: false, - getAudioTrackLabel: function (stream) { - var audioTrack = stream && stream.getAudioTracks()[0]; - return audioTrack ? audioTrack.label : ''; - }, - getVideoTrackLabel: function (stream) { - var videoTrack = stream && stream.getVideoTracks()[0]; - return videoTrack ? videoTrack.label : ''; - }, - stopMediaStream: stopMediaStream, - stopLocalVideoTrack: stopLocalVideoTrack, - closePc: closePc, - getMediaSources: function (cb) { - if (typeof cb !== 'function') { - return; - } - - TemasysAdapter.getMediaSources(function (sources) { - var audioSources = []; - var videoSources = []; - sources && sources.forEach(function (item) { - if (item.kind === 'audio') { - audioSources.push(item); - } else if (item.kind === 'video') { - videoSources.push(item); - } - }); - cb(audioSources, videoSources, null); - }); - }, - clearMediaSourcesCache: function () {}, - getAudioOptions: function (config) { - return (config && config.sourceId) ? {optional: [{sourceId: config.sourceId}]} : true; - }, - getVideoOptions: function (config) { - return (config && config.sourceId) ? {optional: [{sourceId: config.sourceId}]} : true; - }, - getDesktopOptions: function () { - return {optional: [{sourceId: TemasysAdapter.screensharingKey()}]}; - }, - audioOutputSelectionSupported: false, - groupPeerConnectionsSupported: true, - getMediaSourcesSupported: false, - attachSinkIdToAudioElement: attachSinkIdToAudioElement, - toggleAudio: toggleAudio - }; - return circuit; - } - - logger.info('WebRTC is not supported'); - - } catch (e) { - logger.error('Failed to initialize WebRTCAdapter. ', e); - } - - // Initialize with dummy object - circuit.WebRTCAdapter = { - enabled: false, - browser: '', - PeerConnection: function () {}, - SessionDescription: function () {}, - IceCandidate: function () {}, - getUserMedia: function (constraints, successCallback, errorCallback) { - errorCallback && errorCallback('Not supported'); - }, - createObjectURL: function () { return ''; }, - getAudioStreamTrackId: function () { return ''; }, - getVideoStreamTrackId: function () { return ''; }, - getAudioTrackId: function () { return ''; }, - getVideoTrackId: function () { return ''; }, - hasDeviceNameOnTrackLabel: false, - getAudioTrackLabel: function () { return ''; }, - getVideoTrackLabel: function () { return ''; }, - stopMediaStream: function () {}, - stopLocalVideoTrack: function () {}, - closePc: function () {}, - getMediaSources: function (cb) { - cb && cb([], []); - }, - clearMediaSourcesCache: function () {}, - getAudioOptions: function () { return false; }, - getVideoOptions: function () { return false; }, - getDesktopOptions: function () { return false; }, - audioOutputSelectionSupported: false, - groupPeerConnectionsSupported: false, - getMediaSourcesSupported: false, - attachSinkIdToAudioElement: function (audioElement, sinkId, cb) { - cb && cb(); - }, - toggleAudio: function () { return false; } - }; - return circuit; - -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = circuit.logger; - var sdpParser = circuit.sdpParser; - var Utils = circuit.Utils; - var WebRTCAdapter = circuit.WebRTCAdapter; - - function RtcPeerConnections(pcConfig, pcConstraints, options) { - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var _that = this; - - var _pcConfig = Utils.shallowCopy(pcConfig); - var _pcConstraints = pcConstraints; - - // Main peer connection - var _pc = null; - - // Local variables for additional peer connections to receive videos - var _pcGroupCount = (options && options.extraVideoChannels) || 0; - var _pcGroup = []; - - var _offerConstraintsGroup = { - mandatory: { - OfferToReceiveAudio: false, - OfferToReceiveVideo: true - } - }; - - var _iceCandidatesEndCount = 0; - var _iceConnectionState = 'new'; - - ///////////////////////////////////////////////////////////////////////////// - // WebRTC 1.0: Real-time Communication Between Browsers - ///////////////////////////////////////////////////////////////////////////// - function createGroupPeerConnections() { - for (var i = 0; i < _pcGroupCount; i++) { - try { - _pcConfig.iceCandidatePoolSize = 1; // Pre-allocate ICE candidates for 1 media type (video) - var pc = new WebRTCAdapter.PeerConnection(_pcConfig, _pcConstraints); - _pcGroup.push(pc); - registerEvtHandlers(pc); - logger.debug('[RTCPeerConnections]: Created aux video PeerConnection'); - } catch (e) { - logger.error('[RTCPeerConnections]: Failed to create aux video PeerConnection.', e); - } - } - } - - function init() { - _pcConfig.iceCandidatePoolSize = 2; // Pre-allocate ICE candidates for 2 media types (audio and video) - _pc = new WebRTCAdapter.PeerConnection(_pcConfig, _pcConstraints); - registerEvtHandlers(_pc); - - logger.debug('[RTCPeerConnections]: Created main audio/video RTCPeerConnection'); - // Do not create the group peer connections for mobile clients or .NET SDK - WebRTCAdapter.groupPeerConnectionsSupported && createGroupPeerConnections(); - } - - function registerEvtHandlers(pc) { - pc.onnegotiationneeded = handleNegotiationNeeded.bind(null, pc); - pc.onicecandidate = handleIceCandidate.bind(null, pc); - pc.onsignalingstatechange = handleSignalingStateChange.bind(null, pc); - pc.onaddstream = handleAddStream.bind(null, pc); - pc.onremovestream = handleRemoveStream.bind(null, pc); - pc.oniceconnectionstatechange = handleIceConnectionStateChange.bind(null, pc); - } - - function unregisterEvtHandlers(pc) { - pc.onnegotiationneeded = null; - pc.onicecandidate = null; - pc.onsignalingstatechange = null; - pc.onaddstream = null; - pc.onremovestream = null; - pc.oniceconnectionstatechange = null; - } - - function isPcCurrent(pc) { - if (pc === _pc) { - return true; - } - var found = _pcGroup.some(function (p) { - if (pc === p) { - return true; - } - }); - return found; - } - - function handleNegotiationNeeded(pc, event) { - logger.debug('[RTCPeerConnections]: onnegotiationneeded'); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection'); - return; - } - sendNegotiationNeeded(event); - } - - function handleIceCandidate(pc, event) { - logger.debug('[RTCPeerConnections]: onicecandidate: iceGatheringState = ', pc.iceGatheringState); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection'); - return; - } - if (!event.candidate) { - _iceCandidatesEndCount++; - if (_iceCandidatesEndCount < (_pcGroup.length + 1)) { - return; - } - logger.debug('[RTCPeerConnections]: End of candidates - all peer connections'); - _iceCandidatesEndCount = 0; - } else if (pc !== _pc) { - var groupPcIndex = _pcGroup.indexOf(pc); - if (groupPcIndex >= 0) { - var iceCandidate = new WebRTCAdapter.IceCandidate({ - sdpMLineIndex: event.candidate.sdpMLineIndex + groupPcIndex + 2, - sdpMid: event.candidate.sdpMid, - candidate: event.candidate.candidate - }); - event = { - candidate: iceCandidate - }; - } - } - sendIceCandidate(event); - } - - function handleSignalingStateChange(pc, event) { - logger.debug('[RTCPeerConnections]: onsignalingstatechange: signalingState = ', pc.signalingState); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection'); - return; - } - sendSignalingStateChange(event); - } - - function handleAddStream(pc, event) { - logger.debug('[RTCPeerConnections]: onaddstream'); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection'); - return; - } - sendAddStream(event); - } - - function handleRemoveStream(pc, event) { - logger.debug('[RTCPeerConnections]: onremovestream'); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection'); - return; - } - sendRemoveStream(event); - } - - function handleIceConnectionStateChange(pc, event) { - var id = (_pcGroupCount > 0 ? (' - ' + (pc === _pc ? 1 : _pcGroup.indexOf(pc) + 2)) : ''); - var newState = pc.iceConnectionState; - logger.debug('[RTCPeerConnections]: oniceconnectionstatechange' + id + ': iceConnectionState = ', newState); - if (!isPcCurrent(pc)) { - logger.debug('[RTCPeerConnections]: Event is from Old Peer Connection.'); - return; - } - if (pc !== _pc || _iceConnectionState === newState) { - return; // It's not the main peer connection or already in this state - } - _iceConnectionState = newState; - sendIceConnectionStateChange(event); - } - - ///////////////////////////////////////////////////////////////////////////// - // Internal functions - ///////////////////////////////////////////////////////////////////////////// - function setVideoBandwidth(sdp, overwrite) { - if (RtcPeerConnections.maxVideoBandwidth > 0) { - sdpParser.setVideoBandwidth(sdp, RtcPeerConnections.maxVideoBandwidth, overwrite); - } - } - - function assembleDescriptions(descriptionField) { - if (_pcGroupCount === 0) { - // We are just using the main RTCPeerConnection - return _pc[descriptionField]; - } - - var parsedMainSdp = sdpParser.parse(_pc[descriptionField].sdp); - if (parsedMainSdp.m.length === 1 && parsedMainSdp.m[0].media === 'audio') { - // This is an audio only stream - if (descriptionField !== 'remoteDescription') { - sdpParser.addEmptyVideoLine(parsedMainSdp); - } - } - - _pcGroup.every(function (p) { - if (!p[descriptionField]) { - return false; - } - var parsedAuxSdp = sdpParser.parse(p[descriptionField].sdp); - parsedAuxSdp = sdpParser.verifyFingerprintInMLines(parsedAuxSdp); - var videoFound = parsedAuxSdp.m.some(function (mline) { - if (mline.media === 'video') { - parsedMainSdp.m.push(mline); - return true; - } - }); - if (!videoFound) { - return false; //break - } - return true; - }); - - var newSdp = new WebRTCAdapter.SessionDescription({ - type: _pc[descriptionField].type, - sdp: sdpParser.stringify(parsedMainSdp) - }); - return newSdp; - } - - function clearLocalCreatedSdp() { - _pc.createdLocalSdp = undefined; - _pcGroup.forEach(function (p) { - p.createdLocalSdp = undefined; - }); - } - - function updateLocalOffer(rtcSdp, counter) { - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - setVideoBandwidth(parsedSdp, true); - - if (_pcGroupCount) { - // For group calls, create an unique mid attribute for all video m-lines (video_0, video_1...) - parsedSdp.m.forEach(function (m) { - if (m.media === 'video') { - for (var i = 0, length = m.a.length; i < length; i++) { - if (m.a[i].field === 'mid') { - m.a[i].value = 'video_' + (counter - 1); - break; - } - } - } - }); - } - - var newSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - return newSdp; - } - - function updateLocalAnswer(rtcSdp, localMedia) { - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - var hasVideo = sdpParser.hasVideo(parsedSdp); - if (!hasVideo) { - return rtcSdp; - } - - // Get the video connection mode for the local description - var videoMode = sdpParser.getConnectionMode(parsedSdp, {mediaType: 'video'}); - logger.info('[RTCPeerConnections]: Local description has video with a=' + videoMode); - - if (videoMode === 'sendonly' && !localMedia.video) { - // This is a bug in Chrome (tested in v31 and v46) - // When Chrome receives an SDP Offer with a=recvonly for video, it - // always creates an SDP Answer with a=sendonly even if there is - // no local video. To correct that we must override the connection mode - // to inactive. - parsedSdp = sdpParser.setConnectionMode(parsedSdp, 'video', 'inactive'); - logger.warning('[RTCPeerConnections]: Set video connection mode to inactive'); - } else { - // Set video bandwidth - setVideoBandwidth(parsedSdp, true); - } - - var newSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - return newSdp; - } - - function updateRemoteAnswer(rtcSdp) { - if (rtcSdp.type === 'pranswer') { - // WebRTC doesn't support pranswer, so just convert it to answer - rtcSdp.type = 'answer'; - } - return rtcSdp; - } - - ///////////////////////////////////////////////////////////////////////////// - // Event Senders - ///////////////////////////////////////////////////////////////////////////// - function sendEvent(eventHandler, event) { - if (typeof eventHandler === 'function') { - try { - eventHandler(event); - } catch (e) { - logger.error(e); - } - } - } - - function sendNegotiationNeeded(event) { - sendEvent(_that.onnegotiationneeded, event); - } - - function sendIceCandidate(event) { - sendEvent(_that.onicecandidate, event); - } - - function sendSignalingStateChange(event) { - sendEvent(_that.onsignalingstatechange, event); - } - - function sendAddStream(event) { - sendEvent(_that.onaddstream, event); - } - - function sendRemoveStream(event) { - sendEvent(_that.onremovestream, event); - } - - function sendIceConnectionStateChange(event) { - sendEvent(_that.oniceconnectionstatechange, event); - } - - ///////////////////////////////////////////////////////////////////////////// - // Event Handlers - same interfaces as peer connection - ///////////////////////////////////////////////////////////////////////////// - this.onnegotiationneeded = null; - this.onicecandidate = null; - this.onsignalingstatechange = null; - this.onaddstream = null; - this.onremovestream = null; - this.oniceconnectionstatechange = null; - - this.createOffer = function (successCb, errorCb, offerConstraints) { - var cbCount = 0; - var cbErr = null; - - function onOfferCreate(pc, rtcSdp) { - cbCount++; - logger.debug('[RTCPeerConnections]: createOffer, count: ', cbCount); - // The following trace statement should only be enabled for debugging - //logger.debug('[RTCPeerConnections]: Created SDP: ', rtcSdp); - pc.createdLocalSdp = updateLocalOffer(rtcSdp, cbCount); - if (cbCount >= (_pcGroup.length + 1)) { - if (cbErr) { - errorCb(cbErr); - } else { - var combinedSdp = assembleDescriptions('createdLocalSdp'); - successCb(combinedSdp); - } - clearLocalCreatedSdp(); - } - } - function onError(error) { - cbCount++; - if (!cbErr) { - cbErr = error; - } - if (cbCount >= (_pcGroup.length + 1)) { - errorCb(cbErr); - } - } - - // Create offer for main pc - _pc.createOffer(onOfferCreate.bind(null, _pc), onError, offerConstraints); - - // Create offer for extra video receiving peer connections (if any) - _pcGroup.forEach(function (p) { - p.createOffer(onOfferCreate.bind(null, p), onError, _offerConstraintsGroup); - }); - }; - - - this.createAnswer = function (successCb, errorCb, sdpConstraints, localMedia) { - var cbCount = 0; - var cbErr = null; - function onAnswerCreate(pc, rtcSdp) { - cbCount++; - logger.debug('[RTCPeerConnections]: createAnswer, count: ', cbCount); - // The following trace statement should only be enabled for debugging - //logger.debug('[RTCPeerConnections]: Created SDP: ', rtcSdp); - if (pc === _pc) { - pc.createdLocalSdp = updateLocalAnswer(rtcSdp, localMedia); - } else { - pc.createdLocalSdp = updateLocalAnswer(rtcSdp, {video: false}); - } - - if (cbCount >= (_pcGroup.length + 1)) { - if (cbErr) { - errorCb(cbErr); - } else { - var combinedSdp = assembleDescriptions('createdLocalSdp'); - successCb(combinedSdp); - } - clearLocalCreatedSdp(); - } - } - function onError(error) { - cbCount++; - if (!cbErr) { - cbErr = error; - } - if (cbCount >= (_pcGroup.length + 1)) { - errorCb(cbErr); - } - } - //create answer for main pc - _pc.createAnswer(onAnswerCreate.bind(null, _pc), onError); - //create offer for group video receiving peer connections - _pcGroup.forEach(function (p) { - if (!p.remoteDescription) { - cbCount++; - return; - } - p.createAnswer(onAnswerCreate.bind(null, p), onError, sdpConstraints); - }); - - }; - - this.setLocalDescription = function (rtcSdp, successCb, errorCb) { - //distribute sdp to each peer connection - //when all done, re-assemble sdp from each pc.localDesritpion and call back - var cbCount = 0; - var cbErr = null; - function onLocalSdpApplied() { - cbCount++; - logger.debug('[RTCPeerConnections]: setLocalDescription onLocalSdpApplied, count: ', cbCount); - if (cbCount >= (_pcGroup.length + 1)) { - if (cbErr) { - errorCb(cbErr); - } else { - successCb(); - } - } - } - function onError(error) { - cbCount++; - logger.error('[RTCPeerConnections]: setLocalDescription error, count: ' + cbCount + '\n', error); - - if (!cbErr) { - cbErr = error; - } - if (cbCount >= (_pcGroup.length + 1)) { - errorCb(cbErr); - } - } - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - var parsedMedia = parsedSdp.m; - parsedSdp.m = parsedMedia.slice(0, 2); - - if (rtcSdp.type === 'answer') { - var remoteParsedSdp = sdpParser.parse(_pc.remoteDescription.sdp); - if (remoteParsedSdp.m.length === 1) { - //This is an audio only stream - logger.info('[RTCPeerConnections]: only 1 mline in remote sdp offer'); - parsedSdp.m = parsedSdp.m.slice(0, 1); - } - } - - var disassembledSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - _pc.setLocalDescription(disassembledSdp, onLocalSdpApplied, onError); - _pcGroup.forEach(function (p, index) { - if (!parsedMedia[index + 2]) { - cbCount++; - logger.warning('[RTCPeerConnections]: missing sdp mline for peer connection: ' + cbCount); - return; - } - parsedSdp.m = [parsedMedia[index + 2]]; - disassembledSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - p.setLocalDescription(disassembledSdp, onLocalSdpApplied, onError); - }); - }; - - this.setRemoteDescription = function (rtcSdp, successCb, errorCb) { - //distribute sdp to each pc - //call back when all done - var cbCount = 0; - var cbErr = null; - function onRemoteSdpApplied() { - cbCount++; - logger.debug('[RTCPeerConnections]: setRemoteDescription onRemoteSdpApplied, count: ', cbCount); - if (cbCount >= (_pcGroup.length + 1)) { - if (cbErr) { - errorCb(cbErr); - } else { - successCb(); - } - } - } - function onError(error) { - cbCount++; - if (!cbErr) { - cbErr = error; - } - if (cbCount >= (_pcGroup.length + 1)) { - errorCb(cbErr); - } - } - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - var parsedMedia = parsedSdp.m; - parsedSdp.m = parsedMedia.slice(0, 2); - var disassembledSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - disassembledSdp = updateRemoteAnswer(disassembledSdp); - - _pc.setRemoteDescription(disassembledSdp, onRemoteSdpApplied.bind(null, _pc), onError); - _pcGroup.forEach(function (p, index) { - if (!parsedMedia[index + 2]) { - cbCount++; - logger.warning('[RTCPeerConnections]: missing sdp mline for peer connection: ' + cbCount); - return; - } - parsedSdp.m = [parsedMedia[index + 2]]; - disassembledSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - disassembledSdp = updateRemoteAnswer(disassembledSdp); - p.setRemoteDescription(disassembledSdp, onRemoteSdpApplied.bind(null, p), onError); - }); - }; - - this.removeStream = function (localStream) { - return _pc.removeStream(localStream); - }; - - this.addStream = function (localStream) { - return _pc.addStream(localStream); - }; - - this.getLocalStreams = function () { - return _pc.getLocalStreams(); - }; - - this.getRemoteStreams = function () { - var remoteStreams = _pc.getRemoteStreams().slice(0); - _pcGroup.forEach(function (pc) { - remoteStreams.push.apply(remoteStreams, pc.getRemoteStreams()); - }); - return remoteStreams; - }; - - this.addIceCandidate = function (iceCandidate) { - if (!iceCandidate) { - _pc.addIceCandidate(); - _pcGroup.forEach(function (p) { - p.addIceCandidate(); - }); - return; - } - // TODO Check for sdpMid if sdpMLineIndex is null... - if (iceCandidate.sdpMLineIndex <= 1) { - _pc.addIceCandidate(new WebRTCAdapter.IceCandidate(iceCandidate)); - return; - } - var pcGroupIndex = iceCandidate.sdpMLineIndex - 2; - if (_pcGroup[pcGroupIndex]) { - iceCandidate.sdpMLineIndex = 0; - _pcGroup[pcGroupIndex].addIceCandidate(new WebRTCAdapter.IceCandidate(iceCandidate)); - } - }; - - this.close = function () { - _pc.close(); - unregisterEvtHandlers(_pc); - _pcGroup.forEach(function (p) { - p.close(); - unregisterEvtHandlers(p); - }); - }; - - this.createDTMFSender = function (audioTrack) { - return _pc.createDTMFSender(audioTrack); - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - // Define the read-only properties to access the internal variables - Object.defineProperties(this, { - localDescription: { - get: function () { - //subject to ICE candidates update, must put together dynamically - return assembleDescriptions('localDescription'); - }, - enumerable: true, - configurable: false - } - }); - - Object.defineProperties(this, { - remoteDescription: { - get: function () { - return assembleDescriptions('remoteDescription'); - }, - enumerable: true, - configurable: false - } - }); - - Object.defineProperties(this, { - signalingState: { - get: function () { return _pc.signalingState; }, - enumerable: true, - configurable: false - }, - iceConnectionState: { - get: function () { return _pcGroupCount > 0 ? _iceConnectionState : _pc.iceConnectionState; }, - enumerable: true, - configurable: false - } - }); - - Object.defineProperties(this, { - mainPeerConnection: { - get: function () { return _pc; }, - enumerable: false, - configurable: false - }, - groupPeerConnections: { - get: function () { return _pcGroup; }, - enumerable: true, - configurable: false - } - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialize the RTCPeerConnections instances - /////////////////////////////////////////////////////////////////////////////////////// - init(); - } - - // Max video bandwidth for each video stream (Default is 512 Kbps) - RtcPeerConnections.maxVideoBandwidth = 512; - - // Exports - circuit.RtcPeerConnections = RtcPeerConnections; - - return circuit; - -})(Circuit || {}); - - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Constants = circuit.Constants; - - // Participant States for RTC Sessions - var ParticipantState = { - Active: {name: 'Active', ui: '', established: true, css: 'active-call'}, - Busy: {name: 'Busy', ui: 'res_ParticipantState_busy', established: false, css: 'busy'}, - Calling: {name: 'Calling', ui: '', established: false, css: 'calling'}, - Connecting: {name: 'Connecting', ui: '', established: false, css: 'connecting'}, // outgoing call at caller side: state just before being active - ConnectionLost: {name: 'ConnectionLost', ui: 'res_ParticipantState_connectionLost', established: false, css: 'connection-lost'}, - Declined: {name: 'Declined', ui: 'res_ParticipantState_declined', established: false, css: 'declined'}, - Delivered: {name: 'Delivered', ui: '', established: false, css: 'outgoing'}, - Idle: {name: 'Idle', ui: '', established: false, css: 'idle'}, - Inactive: {name: 'Inactive', ui: '', established: false, css: 'inactive'}, // participant hasn't answered yet, but at least one did - Initiated: {name: 'Initiated', ui: '', established: false, css: 'outgoing'}, - Joined: {name: 'Joined', ui: 'res_ParticipantState_joined', established: true, css: 'joined-call'}, - Muted: {name: 'Muted', ui: 'res_ParticipantState_muted', established: true, css: 'muted'}, - Offline: {name: 'Offline', ui: 'res_ParticipantState_offline', established: false, css: 'offline'}, - OffStage: {name: 'OffStage', ui: 'res_ParticipantState_leftStage', established: false, css: 'declined'}, - OnStage: {name: 'OnStage', ui: 'res_ParticipantState_enteredStage', established: true, css: 'joined-call'}, - Removed: {name: 'Removed', ui: 'res_ParticipantState_removed', established: false, css: 'declined'}, - Ringing: {name: 'Ringing', ui: '', established: false, css: 'incoming'}, - Terminated: {name: 'Terminated', ui: 'res_ParticipantState_left', established: false, css: 'declined'}, - Timeout: {name: 'Timeout', ui: 'res_ParticipantState_missed', established: false, css: 'busy'} - }; - - - var ParticipantAction = { - Drop: {name: 'Drop', type: 'hangup', icon: '', localize: 'res_Hangup'}, - Mute: {name: 'Mute', type: 'microphone', icon: 'unmuted', localize: 'res_Mute'}, - Unmute: {name: 'Unmute', type: 'microphone', icon: 'muted', localize: 'res_Unmute'}, - StartVideo: {name: 'StartVideo', type: 'video', icon: 'inactive', localize: 'res_StartVideo'}, - StopVideo: {name: 'StopVideo', type: 'video', icon: 'active', localize: 'res_StopVideo'}, - ToggleVideoInProgress: {name: 'ToggleVideoInProgress', type: 'video', icon: 'progress-icon', localize: ''}, - RemoveFromStage: {name: 'DropFromStage', type: 'hangup', icon: 'drop-from-stage', localize: ''} - }; - - // Participant object for RTC Sessions - var RtcParticipant = {}; - - // Create participant object from a given UserProfile - RtcParticipant.createFromUser = function (user, pcState) { - if (!user) { - return null; - } - - var participant = Object.create(user); - participant.pcState = pcState || ParticipantState.Idle; - participant.streamId = null; - participant.screenStreamId = null; - participant.videoUrl = ''; - participant.mediaType = {audio: false, video: false, desktop: false}; - participant.muted = false; - participant.activeSpeaker = false; - participant.isExternal = !!user.isExternal; // External PSTN dial-in participants - participant.isActive = RtcParticipant.isActive.bind(null, participant); - participant.actions = []; - participant.screenSharePointerSupported = false; - - participant.hasSameMediaType = RtcParticipant.hasSameMediaType.bind(null, participant); - participant.hasVideoStream = RtcParticipant.hasVideoStream.bind(null, participant); - participant.setActions = RtcParticipant.setActions.bind(null, participant); - - // The extended User object overrides the toJSON function to flatten the object - // Lets's override it again here so we don't do it for RTC participants. - participant.toJSON = function () { - return this; - }; - - // JSON.stringify does not include protototype's properties by default, so copy - // the userId field to the inherithed object. Do not copy other fields since they - // may become out of synch. - participant.userId = user.userId; - // Also copy first and last names since they are needed for VDI - participant.firstName = user.firstName; - participant.lastName = user.lastName; - - return participant; - }; - - RtcParticipant.isActive = function (participant) { - switch (participant.pcState) { - case ParticipantState.Active: - case ParticipantState.Joined: - case ParticipantState.Muted: - case ParticipantState.OnStage: - return true; - } - return false; - }; - - RtcParticipant.hasSameMediaType = function (p1, p2) { - return !!(p1 && p2 && p1.mediaType && p2.mediaType && - p1.mediaType.audio === p2.mediaType.audio && - p1.mediaType.video === p2.mediaType.video && - p1.mediaType.desktop === p2.mediaType.desktop); - }; - - RtcParticipant.hasVideoStream = function (participant) { - return !!(participant.videoUrl && (participant.streamId || participant.screenStreamId) && (participant.mediaType.video || participant.mediaType.desktop)); - }; - - RtcParticipant.setActions = function (participant, conversation, localUser) { - participant.actions = []; - - if (conversation && conversation.isTemporary) { - // Invited guests are not allowed any actions - return; - } - - if (!participant.pcState.established) { - if (participant.pcState === ParticipantState.Initiated) { - // Ringing participant added to call. - participant.actions.push(ParticipantAction.Drop); - } - return; - } - - if (participant.isMeetingPointInvitee) { - if (participant.toggleVideoInProgress) { - participant.actions.push(ParticipantAction.ToggleVideoInProgress); - } else { - participant.actions.push(participant.mediaType.video ? ParticipantAction.StopVideo : ParticipantAction.StartVideo); - } - - participant.actions.push(participant.muted ? ParticipantAction.Unmute : ParticipantAction.Mute); - participant.actions.push(ParticipantAction.Drop); - - } else { - var isConvModerated = !conversation || conversation.isModerated; - var amIModerator = conversation && conversation.userIsModerator(localUser); - var isModerator = conversation && conversation.userIsModerator(participant); - - if (!participant.muted && (!isConvModerated || !isModerator || amIModerator)) { - // In a moderated conversation, non-moderators can only mute non-moderators - participant.actions.push(ParticipantAction.Mute); - } - if (!isConvModerated || amIModerator) { - if (conversation.type === Constants.ConversationType.LARGE && participant.isSessionGuest && amIModerator) { - participant.actions.push(ParticipantAction.RemoveFromStage); - } else { - // In a moderated conversation, only moderators can drop other participants - participant.actions.push(ParticipantAction.Drop); - } - } - } - }; - - // Exports - circuit.RtcParticipant = RtcParticipant; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.ParticipantState = ParticipantState; - circuit.Enums.ParticipantAction = ParticipantAction; - - return circuit; - -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = circuit.logger; - var Utils = circuit.Utils; - - // Define common targets for all call types. - // This object may be extended with additional targets as required by the application. - var Targets = { - WebRTC: {name: 'WebRTC', ui: 'res_CircuitClient', css: 'circuit'}, - Cell: {name: 'Cell', ui: 'res_BackupNumber', css: 'mobile'}, - Desk: {name: 'Desk', ui: 'res_Deskphone', css: 'desk'}, - VM: {name: 'VM', ui: 'res_Voicemail', css: 'vm'}, - Other: {name: 'Other', ui: 'res_CallAtOther', css: ''} - }; - - var CstaCallState = Object.freeze({ - Idle: {name: 'Idle', ui: '', established: false}, - - // Outgoing call states - Initiated: {name: 'Initiated', established: false}, - Connecting: {name: 'Connecting', established: false}, - Delivered: {name: 'Delivered', established: false}, - Busy: {name: 'Busy', established: false}, - Offered: {name: 'Offered', established: false}, - - // Failed call states - Failed: {name: 'Failed', established: false}, - TransferFailed: {name: 'TransferFailed', established: false}, - - // Incoming call states - Ringing: {name: 'Ringing', established: false}, - ExtendedRinging: {name: 'ExtendedRinging', established: false}, - - // Established call states - Active: {name: 'Active', established: true}, - Held: {name: 'Held', established: true}, - Holding: {name: 'Holding', established: true}, - HoldOnHold: {name: 'HoldOnHold', established: true}, - Parked: {name: 'Parked', established: true}, - Conference: {name: 'Conference', established: true}, - ConferenceHolding: {name: 'ConferenceHolding', established: true}, - - // Call has been terminated - Terminated: {name: 'Terminated', established: false} - }); - - var BusyHandlingOptions = Object.freeze({ - DefaultRouting: {name: 'DefaultRouting', ui: 'res_BusyHandling_DefaultRouting'}, - BusySignal: {name: 'BusySignal', ui: 'res_BusyHandling_BusySignal'}, - SendToAlternativeNumber: {name: 'SendToAlternativeNumber', ui: 'res_BusyHandling_AlternativeNumber'}, - SendToVM: {name: 'SendToVM', ui: 'res_BusyHandling_Voicemail'} - }); - - var RoutingOptions = Object.freeze({ - DefaultRouting: {name: 'DefaultRouting', ui: 'res_BusyHandling_DefaultRouting', desc: 'res_DefaultRoutingDescription', newDesc: 'res_DefaultRoutingDescriptionTimersPBXConfigurable'}, - DeskPhone: {name: 'DeskPhone', ui: 'res_Deskphone', desc: 'res_DeskphoneRoutingDescription'}, - AlternativeNumber: {name: 'AlternativeNumber', ui: 'res_BusyHandling_AlternativeNumber', desc: 'res_AlternativeNumberRoutingDescription'}, - VM: {name: 'VoiceMail', ui: 'res_Voicemail', desc: 'res_VoicemailRoutingDescription'}, - Other: {name: 'Other', ui: 'res_Other', desc: ''} - }); - - var JournalEntryTypes = Object.freeze({ - REGULAR: 'REGULAR', - MISSED: 'MISSED' - }); - - var MissedReasonTypes = Object.freeze({ - DEFAULT: 'DEFAULT', - DEST_OUT_OF_ORDER: 'DEST_OUT_OF_ORDER', - REORDER_TONE: 'REORDER_TONE', - BUSY: 'BUSY', - CANCELLED: 'CANCELLED', - DECLINED: 'DECLINED', - TRANSFERRED: 'TRANSFERRED' - }); - - var TransferCallFailedCauses = Object.freeze({ - Busy: {name: 'Busy', ui: 'res_TransferCallFailedCauseBusy'}, - Unreachable: {name: 'Unreachable', ui: 'res_TransferCallFailedCauseUnreachable'}, - DND: {name: 'Dnd', ui: 'res_TransferCallFailedCauseDND'} - }); - - var RedirectionTypes = Object.freeze({ - CallForward: {name: 'callForward', ui: 'res_ForwardedFrom'}, - CallPickupNotification: {name: 'callPickupNotification', ui: 'res_CallPickupNotification'}, - CallPickedUp: {name: 'callPickedUp', ui: 'res_CallPickedUp'} - }); - - var AgentState = Object.freeze({ - Ready: 'ready', - NotReady: 'notReady' - }); - - function AtcCallInfo() { - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - // - // These variables can only be accessed via getters/setters. - // Critical variables that cannot be changed directly must be defined here. - /////////////////////////////////////////////////////////////////////////////////////// - var _transferCallFailedCause = null; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - this.cstaConn = null; - this.cstaState = CstaCallState.Idle; - this.position = Targets.Other; - this.servicesPermitted = {}; - this.peerFQN = ''; - this.peerDn = ''; - this.peerName = null; - this.missedReason = ''; - this.ignoreCall = false; - this.originalPartnerDisplay = {}; - this.handoverInProgress = false; - this.holdInProgress = false; - this.retrieveInProgress = false; - this.redirectingUser = {}; - - this.setPeerName = function (name) { - this.peerName = name || ''; - }; - - this.setPartnerDisplay = function (display) { - if (display) { - this.peerFQN = display.fqn || ''; - this.peerDn = display.dn || display.fqn || ''; - this.peerName = display.name || this.peerDn; - - if (!this.peerFQN && this.peerDn.startsWith('+')) { - this.peerFQN = this.peerDn; - } - } - }; - - this.setOriginalPartnerDisplay = function (display) { - if (display && Utils.isEmptyObject(this.originalPartnerDisplay)) { - this.originalPartnerDisplay = display; - } - }; - - // CSTA Methods - this.setCstaConnection = function (conn) { - this.cstaConn = conn; - if (this.isRemote) { - // Update the Call ID as well - this.callId = conn.cID; - } - }; - - this.getCstaConnection = function () { - return this.cstaConn; - }; - - this.getCstaCallId = function () { - return this.cstaConn && this.cstaConn.cID; - }; - - this.getCstaDeviceId = function () { - return this.cstaConn && this.cstaConn.dID; - }; - - this.setServicesPermitted = function (svcsPermitted) { - // If no new SP are reported in event then client must maintain old ones - if (!svcsPermitted || Utils.isEmptyObject(svcsPermitted)) { - logger.info('[AtcCallInfo]: Empty services permitted, keeping last values'); - return; - } - if (svcsPermitted.rSP && svcsPermitted.rSP.cCSs !== undefined) { - this.servicesPermitted.rSP = svcsPermitted.rSP; - } - if (svcsPermitted.eSP !== undefined) { - this.servicesPermitted.eSP = svcsPermitted.eSP; - } - logger.info('[AtcCallInfo]: Updated services permitted: ', this.servicesPermitted); - }; - - this.clearServicesPermitted = function () { - this.servicesPermitted = {}; - }; - - this.getServicesPermitted = function () { return this.servicesPermitted; }; - - this.isHandoverAllowed = function () { - return (this.isSilentHandoverAllowed() || this.isSeamlessHandoverAllowed()) && !this.isHandoverInProgress(); - }; - - this.isSeamlessHandoverAllowed = function () { - return !!(this.servicesPermitted.eSP && this.servicesPermitted.eSP.zseHo); - }; - - this.isSilentHandoverAllowed = function () { - return !!(this.servicesPermitted.eSP && this.servicesPermitted.eSP.zsiHo); - }; - - this.isTransferAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.sST && !this.isHandoverInProgress()); - }; - - this.isAlternateAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.alC); - }; - - this.isAnswerAllowed = function () { - if (this.servicesPermitted.rSP) { - return !!this.servicesPermitted.rSP.cCSs.anC; - } - return this.cstaState === CstaCallState.Ringing; - }; - - this.isCallBackAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.cB); - }; - - this.isConsultAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.csC); - }; - - this.isDeflectAllowed = function () { - if (this.cstaState === CstaCallState.Parked || this.cstaState === CstaCallState.Ringing) { - return true; - } - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.dCl); - }; - - this.isGenerateDigitsAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cAS.gD); - }; - - this.isHoldAllowed = function () { - if (this.isHandoverInProgress()) { - return false; - } - if (this.servicesPermitted.rSP) { - return this.servicesPermitted.rSP.cCSs.hCl; - } - switch (this.cstaState) { - case CstaCallState.Active: - case CstaCallState.Conference: - case CstaCallState.Held: - return true; - - default: - return false; - } - }; - - this.isConferenceCall = function () { - return (this.cstaState === CstaCallState.Conference || this.cstaState === CstaCallState.ConferenceHolding); - }; - - this.isMakeCallAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.mCl); - }; - - this.isReconnectAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.rC); - }; - - this.isRetrieveAllowed = function () { - if (this.isHandoverInProgress()) { - return false; - } - if (this.servicesPermitted.rSP) { - return this.servicesPermitted.rSP.cCSs.reC; - } - switch (this.cstaState) { - case CstaCallState.ConferenceHolding: - case CstaCallState.Holding: - case CstaCallState.HoldOnHold: - return true; - - default: - return false; - } - }; - - this.isTransferCallAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.tCl && !this.isHandoverInProgress()); - }; - - this.isConferenceCallAllowed = function () { - return !!(this.servicesPermitted.rSP && this.servicesPermitted.rSP.cCSs.coC); - }; - - this.getPosition = function () { return this.position; }; - - this.setPosition = function (p) { - if (!p || p === this.position) { - return; - } - this.position = p; - logger.debug('[AtcCallInfo]: Set call position to ', p.name); - }; - - this.getMissedReason = function () { return this.missedReason; }; - - this.setMissedReason = function (missedReason) { - if (!missedReason || missedReason === this.missedReason) { - return; - } - this.missedReason = missedReason; - logger.debug('[AtcCallInfo]: Set missedReason to ', missedReason); - }; - - this.getIgnoreCall = function () { return this.ignoreCall || this.cstaState === CstaCallState.Offered; }; - - this.setIgnoreCall = function (flag) { - this.ignoreCall = flag; - }; - - this.isHandoverInProgress = function () { - return this.handoverInProgress; - }; - - this.setHandoverInProgress = function () { - this.handoverInProgress = true; - }; - - this.clearHandoverInProgress = function () { - this.handoverInProgress = false; - }; - - this.isHoldInProgress = function () { - return this.holdInProgress; - }; - - this.setHoldInProgress = function () { - this.holdInProgress = true; - }; - - this.clearHoldInProgress = function () { - this.holdInProgress = false; - }; - - this.isRetrieveInProgress = function () { - return this.retrieveInProgress; - }; - - this.setRetrieveInProgress = function () { - this.retrieveInProgress = true; - }; - - this.clearRetrieveInProgress = function () { - this.retrieveInProgress = false; - }; - - this.setTransferCallFailedCause = function (cause) { - if (this.cstaState === CstaCallState.TransferFailed) { - switch (cause) { - case 'busy': - _transferCallFailedCause = TransferCallFailedCauses.Busy; - break; - case 'doNotDisturb': - _transferCallFailedCause = TransferCallFailedCauses.DND; - break; - default: - _transferCallFailedCause = TransferCallFailedCauses.Unreachable; - break; - } - } - }; - - this.getTransferCallFailedCause = function () { - if (this.cstaState === CstaCallState.TransferFailed) { - return _transferCallFailedCause || TransferCallFailedCauses.Unreachable; - } - return null; - }; - - this.setRedirectingUser = function (phoneNumber, fqNumber, displayName, userId, redirectionType) { - // Used for telephony calls - redirectionType = redirectionType || this.redirectingUser.redirectionType; - if (!this.redirectingUser || !this.redirectingUser.fqNumber || this.redirectingUser.fqNumber === fqNumber) { - this.redirectingUser = { - userId: userId || '', - displayName: displayName, - phoneNumber: phoneNumber, - fqNumber: fqNumber, - redirectionType: redirectionType - }; - } - }; - - this.getRedirectingUser = function () { - return this.redirectingUser; - }; - - this.setRedirectionType = function (type) { - this.redirectingUser.redirectionType = type; - }; - - this.getRedirectionType = function () { - return this.redirectingUser && this.redirectingUser.redirectionType; - }; - - this.isForwarded = function () { - return !!this.redirectingUser && this.redirectingUser.redirectionType === RedirectionTypes.CallForward; - }; - - this.isPickupNotification = function () { - return !!this.redirectingUser && this.redirectingUser.redirectionType === RedirectionTypes.CallPickupNotification; - }; - - this.isPickedUp = function () { - return !!this.redirectingUser && this.redirectingUser.redirectionType === RedirectionTypes.CallPickedUp; - }; - } - - // Exports - circuit.AtcCallInfo = AtcCallInfo; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.CstaCallState = CstaCallState; - circuit.Enums.Targets = Targets; - circuit.Enums.JournalEntryTypes = JournalEntryTypes; - circuit.Enums.MissedReasonTypes = MissedReasonTypes; - circuit.BusyHandlingOptions = BusyHandlingOptions; - circuit.RoutingOptions = RoutingOptions; - circuit.Enums.TransferCallFailedCauses = TransferCallFailedCauses; - circuit.Enums.RedirectionTypes = RedirectionTypes; - circuit.Enums.AgentState = AgentState; - - return circuit; - -})(Circuit || {}); - - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Constants = circuit.Constants; - var CstaCallState = circuit.Enums.CstaCallState; - var DefaultAvatars = circuit.DefaultAvatars; - var logger = circuit.logger; - var ParticipantState = circuit.Enums.ParticipantState; - var RtcParticipant = circuit.RtcParticipant; - var UserProfile = circuit.UserProfile; - var Utils = circuit.Utils; - - /* - * Enum for call direction - * @readonly - * @enum {String} - * @property INCOMING - * @property OUTGOING - * @property NONE - */ - var CallDirection = Object.freeze({ - INCOMING: 'incoming', - OUTGOING: 'outgoing', - NONE: '' - }); - - /* - * Enum for call state - * @readonly - * @enum {String} - * @property Idle - * @property Initiated Outgoing call initiated - * @property Connecting Outgoing call connecting - * @property Delivered Outgoing call delivered - * @property Busy Outgoing call with peer busy - * @property Failed Failed call - * @property Missed Missed call - * @property Declined Declined call - * @property NotAnswered Call not answered - * @property Ringing Incoming call is ringing - * @property Answering Incoming call being answered - * @property Active Established active call - * @property Held Established call being held - * @property Holding Established call holding - * @property HoldOnHold Established call hold on hold - * @property Waiting Established call waiting - * @property Started Conference call started - * @property ActiveRemote Active call on one of user's other devices - * @property Terminated Call has been terminated - */ - var CallState = Object.freeze({ - Idle: {name: 'Idle', ui: '', established: false}, - - // Outgoing call states - Initiated: {name: 'Initiated', ui: 'res_Calling', established: false, css: 'outgoing'}, - Connecting: {name: 'Connecting', ui: '', established: false, css: 'connecting'}, - Delivered: {name: 'Delivered', ui: 'res_Calling', established: false, css: 'outgoing'}, - Busy: {name: 'Busy', ui: 'res_BusyCall', established: false, css: 'busy'}, - - // Failed call states - Failed: {name: 'Failed', ui: 'res_FailedCall', established: false, css: 'failed'}, - Missed: {name: 'Missed', ui: 'res_MissedCall', established: false, css: 'missed'}, - Declined: {name: 'Declined', ui: 'res_CallDeclined', established: false, css: 'declined'}, - NotAnswered: {name: 'NotAnswered', ui: 'res_CallNotAnswered', established: false, css: 'not-answered'}, - - // Incoming call states - Ringing: {name: 'Ringing', ui: 'res_IncomingCall', established: false, css: 'incoming'}, - Answering: {name: 'Answering', ui: 'res_IncomingCall', established: false, css: 'answering'}, // Call answered but not Active yet - - // Established call states - Active: {name: 'Active', ui: '', established: true, css: 'active-call'}, - Held: {name: 'Held', ui: 'res_OnHold', established: true, css: 'active-call'}, - Holding: {name: 'Holding', ui: 'res_Holding', established: true, css: 'holding-call'}, - HoldOnHold: {name: 'HoldOnHold', ui: 'res_Holding', established: true, css: 'holding-call'}, - Waiting: {name: 'Waiting', ui: 'res_Waiting', established: true, css: 'waiting'}, - - // Remote calls - Started: {name: 'Started', ui: 'res_InProgress', established: false, css: 'started'}, - ActiveRemote: {name: 'ActiveRemote', ui: 'res_RemoteCall', established: true, css: 'active-call-remote'}, - - // Call has been terminated - Terminated: {name: 'Terminated', ui: 'res_Terminated', established: false, css: 'declined'} - }); - - // Define an abstract BaseCall class - function BaseCall(conversation, isRemote) { - - if (!conversation) { - throw new Error('Cannot create BaseCall object without a conversation'); - } - - // Make sure that isRemote is true or fase - isRemote = !!isRemote; - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - // - // These variables can only be accessed via getters/setters. - // Critical variables that cannot be changed directly must be defined here. - /////////////////////////////////////////////////////////////////////////////////////// - var _that = this; - - var _callId = null; - var _instanceId = null; - var _convId = null; - var _convType = null; - var _convTitle = null; - var _convTitleEscaped = null; - var _ownerId = null; - var _transactionId = null; - var _isDirect = false; - var _isLarge = false; - var _isTelephonyCall = false; - var _isTestCall = false; - var _state = CallState.Idle; - var _disconnectCause = null; - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - - function setConversationData(conv) { - _callId = conv.rtcSessionId; - _convId = conv.convId; - _convType = conv.type; - _convTitle = conv.topic || conv.participantFirstNames || null; - _convTitleEscaped = conv.topicEscaped || conv.participantFirstNamesEscaped || null; - _ownerId = conv.creatorId; - _isDirect = conv.type === Constants.ConversationType.DIRECT; - _isLarge = conv.type === Constants.ConversationType.LARGE; - _isTelephonyCall = conv.isTelephonyConv; - _isTestCall = conv.testCall; - - if (_isDirect) { - _that.peerUser = conv.peerUser; - } else { - delete _that.peerUser; - } - - _that.peerUsers = conv.peerUsers; - _that.numConvParticipants = conv.participants.length; - _that.avatar = conv.avatar; - _that.isGuestInvite = !!conv.isTemporary; - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - // Define the read-only properties to access the internal variables - Object.defineProperties(this, { - isRemote: { - value: isRemote, - writable: false, - enumerable: true, - configurable: false - }, - callId: { - get: function () { return _callId; }, - enumerable: true, - configurable: false - }, - instanceId: { - get: function () { return _instanceId; }, - enumerable: true, - configurable: false - }, - transactionId: { - get: function () { return _transactionId; }, - enumerable: true, - configurable: false - }, - state: { - get: function () { return _state; }, - enumerable: true, - configurable: false - }, - convId: { - get: function () { return _convId; }, - enumerable: true, - configurable: false - }, - convType: { - get: function () { return _convType; }, - enumerable: true, - configurable: false - }, - convTitle: { - get: function () { - // Don't set the title for direct call in setConversationData, because peerUser can have no data in that moment. - return _convTitle || (_isDirect ? this.peerUser.displayName : ''); - }, - enumerable: true, - configurable: false - }, - convTitleEscaped: { - get: function () { - return _convTitleEscaped || (_isDirect ? this.peerUser.displayNameEscaped : ''); - }, - enumerable: true, - configurable: false - }, - containsExternals: { - get: function () { - return this.participants.some(function (p) { - return p.participantType === Constants.RTCParticipantType.SESSION_GUEST || - p.participantType === Constants.RTCParticipantType.EXTERNAL; - }); - }, - enumerable: false, - configurable: false - }, - ownerId: { - get: function () { return _ownerId; }, - enumerable: true, - configurable: false - }, - isDirect: { - get: function () { return _isDirect; }, - enumerable: true, - configurable: false - }, - isLarge: { - get: function () { return _isLarge; }, - enumerable: true, - configurable: false - }, - conferenceCall: { - get: function () { return !_isDirect; }, // All group calls are conference calls. - enumerable: true, - configurable: false - }, - isGroupCallStarted: { - get: function () { - return !!(!this.isDirect && this.state === CallState.Started); - }, - enumerable: true, - configurable: false - }, - isGroupCallInitiated: { - get: function () { - return !!(!this.isDirect && this.state === CallState.Initiated); - }, - enumerable: true, - configurable: false - }, - isGroupCallActive: { - get: function () { - return !!(!this.isDirect && this.state === CallState.Active); - }, - enumerable: true, - configurable: false - }, - isTelephonyCall: { - get: function () { return _isTelephonyCall; }, - enumerable: true, - configurable: false - }, - isTestCall: { - get: function () { return _isTestCall; }, - enumerable: true, - configurable: false - }, - conversationFeedView: { - get: function () { return true; }, - enumerable: true, - configurable: true // Let LocalCall object redefine this property - }, - isPullAllowed: { - value: false, - enumerable: true, - configurable: true // Let atcRemoteCall object redefine this property - }, - isHandoverAllowed: { - get: function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isHandoverAllowed(); - }, - enumerable: true, - configurable: true - }, - isHandoverInProgress: { - get: function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isHandoverInProgress(); - }, - enumerable: true, - configurable: true - }, - redirectingUser: { - get: function () { - return this.getRedirectingUser(); - }, - enumerable: true, - configurable: true - }, - forwarded: { - get: function () { - return this.isForwarded(); - }, - enumerable: true, - configurable: true - }, - pickupNotification: { - get: function () { - return this.isPickupNotification(); - }, - enumerable: true, - configurable: true - }, - pickedUp: { - get: function () { - return this.isPickedUp(); - }, - enumerable: true, - configurable: true - }, - disconnectCause: { - get: function () { return _disconnectCause; }, - set: function (cause) { _disconnectCause = cause; }, - enumerable: true, - configurable: false - }, - transferCallFailedCause: { - get: function () { - if (!this.atcCallInfo) { - return null; - } - return this.atcCallInfo.getTransferCallFailedCause(); - }, - enumerable: true, - configurable: false - }, - isATCCall: { - get: function () { - return !!this.atcCallInfo; - }, - enumerable: true, - configurable: false - } - }); - - // The media types for the local user - this.localMediaType = {audio: false, video: false, desktop: false}; - - // The media types for the call - this.mediaType = {audio: false, video: false, desktop: false}; - - // Remember the last media type before the call is terminated - this.lastMediaType = this.mediaType; - - this.direction = CallDirection.NONE; - this.secureCall = false; - - this.establishedTime = 0; // The timestamp when the call is established - this.creationTime = Date.now(); // Client side creation time. Used for sorting. - - this.peerUsers = null; - this.peerUser = null; - this.numConvParticipants = 0; - this.avatar = null; - - this.activeNotification = null; - - this.activeRTPStatsWarning = null; - - this.participants = []; // Array of RtcParticipants instances - this.participantsHashTable = {}; - this.attendeeCount = 0; // Number of all guests in a large conference (non moderators) - - this.remotelyMuted = false; // true if local user is muted remotely - this.sessionMuted = false; - this.sessionLocked = false; - - this.whiteboardEnabled = false; - - this.recording = { - state: Constants.RecordingInfoState.INITIAL, - notifyByCurtain: false, - notifyByUser: false, - duration: 0, - starter: {userId: ''}, - reason: Constants.RecordingInfoReason.NONE, - resumeTime: 0, // internal value - wasStarted: function () { - // was started at some point - return this.state !== Constants.RecordingInfoState.INITIAL; - }, - isActive: function () { - return this.state === Constants.RecordingInfoState.STARTED || this.state === Constants.RecordingInfoState.START_PENDING; - }, - isPaused: function () { // by curtain - return this.state === Constants.RecordingInfoState.START_PENDING; - }, - isFailed: function () { - return this.reason !== Constants.RecordingInfoReason.NONE && - this.reason !== Constants.RecordingInfoReason.STOPPED_MANUALLY && - this.reason !== Constants.RecordingInfoReason.STOPPED_AUTOMATICALLY && - (this.state === Constants.RecordingInfoState.INITIAL || this.state === Constants.RecordingInfoState.STOPPED); - } - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Members - // (Only includes members which need to access private data) - /////////////////////////////////////////////////////////////////////////////////////// - this.setState = function (newState, bypassTerminate) { - if (!newState || newState === _state) { - return; - } - if (_state === CallState.Terminated) { - logger.error('[BaseCall]: The call has already been terminated. Cannot set state to ', newState.name); - return; - } - logger.debug('[BaseCall]: Changing call state from ' + _state.name + ' to ' + newState.name); - - // If it's an ATC call and the CSTA Established has not yet been received, don't set the established time - if (!this.establishedTime && newState.established && (!this.isATCCall || this.getCstaState().established)) { - this.establishedTime = Date.now(); - } - - _state = newState; - - if (_state === CallState.Terminated && !bypassTerminate) { - this.terminate(); - } - }; - - this.checkState = function (states) { - if (!Array.isArray(states)) { - return states === _state; - } - return states.some(function (state) { - return state === _state; - }); - }; - - this.updateCall = function (newConversation) { - if (!newConversation) { - return false; - } - - setConversationData(newConversation); - return true; - }; - - this.setPeerUser = function (phoneNumber, displayName, userId) { - // Used for telephony calls - if (this.peerUser.hasTelephonyRole || !this.peerUser.userId || (userId && userId !== this.peerUser.userId) || - Utils.cleanPhoneNumber(phoneNumber) !== Utils.cleanPhoneNumber(this.peerUser.phoneNumber)) { - this.peerUser = { - userId: userId || '', - firstName: null, - lastName: null, - displayName: displayName, - phoneNumber: phoneNumber - }; - if (UserProfile && UserProfile.extend) { - this.peerUser = UserProfile.extend(this.peerUser); - } - _convTitle = displayName || phoneNumber || null; - _convTitleEscaped = _convTitle ? Utils.textToHtmlEscaped(_convTitle) : null; - } - }; - - this.setTransactionId = function (transactionId) { - _transactionId = transactionId || Utils.createTransactionId(); - }; - - this.clearTransactionId = function () { - _transactionId = null; - }; - - this.setInstanceId = function (instanceId) { - _instanceId = instanceId; - }; - - this.setCallIdForTelephony = function (callId) { - if (_that.isTelephonyCall && callId && callId !== _callId) { - logger.debug('[BaseCall]: Changing callId from ' + _callId + ' to ' + callId); - _callId = callId; - } - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////////////////// - setConversationData(conversation); - conversation = null; // Remove reference to conversation object - } - - BaseCall.prototype.sameAs = function (call) { - return (!!call && this.callId === call.callId && this.isRemote === call.isRemote); - }; - - BaseCall.prototype.terminate = function () { - logger.debug('[BaseCall]: Terminating call with callId = ', this.callId); - this.lastMediaType = this.mediaType; - this.mediaType = {audio: false, video: false, desktop: false}; - this.setState(CallState.Terminated, true); - }; - - BaseCall.prototype.isEstablished = function () { return this.state.established; }; - - BaseCall.prototype.isOutgoingState = function () { - return this.state === CallState.Initiated || this.state === CallState.Connecting || - this.state === CallState.Delivered || this.state === CallState.Busy; - }; - - BaseCall.prototype.isPresent = function () { - return (this.state !== CallState.Idle && this.state !== CallState.Terminated); - }; - - BaseCall.prototype.isHeld = function () { return this.state === CallState.Held; }; - - BaseCall.prototype.hasRemoteMedia = function () { - return false; - }; - - BaseCall.prototype.isCallingOut = function () { - return this.state === CallState.Waiting && this.participants.length > 0 && this.participants.every(function (p) { - return p.pcState === ParticipantState.Initiated; - }); - }; - - BaseCall.prototype.hasVideo = function () { - // Screen share is currently handled as a video stream, so return true - // if the media type includes either video or desktop (i.e. screen share) - return !!(this.mediaType.video || this.mediaType.desktop); - }; - - BaseCall.prototype.updateSecurityStatus = function (status) { - // Only update secureCall if status is explicitly set to true or false. - // Other truthy or falsy values (e.g. undefined or null) must be ignored. - if (status === true || status === false) { - this.secureCall = status; - } - }; - - // RTC methods - BaseCall.prototype.canToggleVideo = function () { - return !this.isRemote && this.checkState([CallState.Active, CallState.Waiting]); - }; - - BaseCall.prototype.hasRemoteVideo = function () { - return false; - }; - - BaseCall.prototype.hasLocalVideo = function () { - // Screen share is currently handled as a video stream, so return true - // if the client is streaming either video or desktop (i.e. screen share). - return this.localMediaType.video || this.localMediaType.desktop; - }; - - BaseCall.prototype.hasLocalScreenShare = function () { - return this.localMediaType.desktop; - }; - - BaseCall.prototype.hasRemoteScreenShare = function () { - return this.mediaType.desktop && !this.localMediaType.desktop; - }; - - BaseCall.prototype.isMuted = function () { return false; }; - - BaseCall.prototype.mute = function () { - logger.warning('[BaseCall]: Mute is not implemented in base object'); - return false; - }; - - BaseCall.prototype.unmute = function () { - logger.warning('[BaseCall]: Unmute is not implemented in base object'); - return false; - }; - - BaseCall.prototype.toggleMute = function () { - logger.warning('[BaseCall]: toggleMute is not implemented in base object'); - return false; - }; - - BaseCall.prototype.outgoingFailed = function () { - return !this.isRemote && - this.direction === CallDirection.OUTGOING && - this.checkState([CallState.Declined, CallState.Busy, CallState.NotAnswered, CallState.Failed]); - }; - - BaseCall.prototype.setPeerUsersAsParticipants = function () { - // Populate participants for outgoing Group Calls (RTC.JOIN) - if (this.direction !== CallDirection.OUTGOING || !this.peerUsers) { - return; - } - - this.participants = this.peerUsers.map(function (user) { - // Create a new object using the member's UserProfile object as prototype - return RtcParticipant.createFromUser(user, ParticipantState.Initiated); - }); - for (var i = 0; i < this.participants.length; i++) { - this.participantsHashTable[this.participants[i].userId] = this.participants[i]; - } - - logger.debug('[BaseCall]: Initialized participants: ', this.participants); - }; - - BaseCall.prototype.hasParticipant = function (userId) { - return this.participants.some(function (p) { - return (p.userId === userId); - }); - }; - - BaseCall.prototype.getParticipant = function (userId) { - for (var idx = 0; idx < this.participants.length; idx++) { - if (this.participants[idx].userId === userId) { - return this.participants[idx]; - } - } - return null; - }; - - BaseCall.prototype.addParticipant = function (participant, pcState, update) { - if (!participant || !participant.userId) { - logger.warning('[BaseCall]: addParticipant called with invalid participant'); - return null; - } - - logger.debug('[BaseCall]: Add participant with userId =', participant.userId); - if (this.hasParticipant(participant.userId)) { - if (update) { - return this.updateParticipant(participant, pcState); - } - logger.warning('[BaseCall]: addParticipant called with existing participant. userId =', participant.userId); - return null; - } - - if (pcState) { - participant.pcState = pcState; - } - this.participants.push(participant); - this.participantsHashTable[participant.userId] = participant; - - logger.debug('[BaseCall]: Added participant to call: ', participant.userId); - return participant; - }; - - BaseCall.prototype.updateParticipant = function (participant, pcState) { - if (!participant || !participant.userId) { - logger.warning('[BaseCall]: updateParticipant called with invalid participant'); - return null; - } - - logger.debug('[BaseCall]: Update participant with userId =', participant.userId); - var curr = this.getParticipant(participant.userId); - if (!curr) { - logger.warning('[BaseCall]: The given participant is not part of the call'); - return null; - } - curr.pcState = pcState || participant.pcState; - curr.streamId = participant.streamId; - curr.screenStreamId = participant.screenStreamId; - curr.muted = participant.muted; - curr.mediaType = participant.mediaType; - curr.screenSharePointerSupported = participant.screenSharePointerSupported; - - logger.debug('[BaseCall]: Updated participant data for ', curr.userId); - return curr; - }; - - BaseCall.prototype.removeParticipant = function (userId) { - delete this.participantsHashTable[userId]; - for (var idx = 0; idx < this.participants.length; idx++) { - if (this.participants[idx].userId === userId) { - var p = this.participants.splice(idx, 1)[0]; - // Reset the participant's video URL and media type - p.videoUrl = ''; - p.mediaType = {audio: false, video: false, desktop: false}; - - logger.debug('[BaseCall]: Removed participant from call: ', p.userId); - return p; - } - } - return null; - }; - - BaseCall.prototype.setParticipantState = function (userId, state) { - if (!state) { - return; - } - this.participants.some(function (p) { - if (p.userId === userId) { - p.pcState = state; - logger.debug('[BaseCall]: Set participant state to ' + state.name + ', userId =', p.userId); - return true; - } - }); - }; - - BaseCall.prototype.hasOtherParticipants = function () { - // For large conference there might be a single moderator and multiple attendees not included in participants field. - // For direct or regular group call all other participants are available in the participants field. - return this.participants.length > 0 || this.attendeeCount > 0; - }; - - BaseCall.prototype.updateMediaType = function () { - var mediaType = { - audio: this.localMediaType.audio, - video: this.localMediaType.video, - desktop: this.localMediaType.desktop - }; - - this.participants.forEach(function (p) { - if (p.mediaType) { - if (p.mediaType.audio) { - mediaType.audio = true; - } - if (p.mediaType.video) { - mediaType.video = true; - } - if (p.mediaType.desktop) { - mediaType.desktop = true; - } - } - }); - - logger.debug('[BaseCall]: Updating call media type to ', mediaType); - this.mediaType = mediaType; - }; - - BaseCall.prototype.callTypeCss = function () { - if (this.isDirect) { - return this.hasVideo() ? 'webrtc-video' : 'webrtc-audio'; - } - if (this.isRemote) { - return 'remote'; - } - // Group call - switch (this.state.css) { - case 'outgoing': - return 'ongoing'; - case 'incoming': - return 'incoming'; - case 'answering': - return 'answering'; - case 'active-call': - return 'active'; - default: - return ''; - } - }; - - BaseCall.prototype.callHeaderType = function () { - return this.isDirect ? 'Direct' : (this.isGuestInvite ? 'Guest' : (this.isLarge ? 'Large' : 'Group')); - }; - - BaseCall.prototype.toString = function () { - return JSON.stringify(this, null, 3); - }; - - BaseCall.prototype.getCstaState = function () { - return this.atcCallInfo ? this.atcCallInfo.cstaState : null; - }; - - BaseCall.prototype.setCstaState = function (state) { - if (this.atcCallInfo) { - this.atcCallInfo.cstaState = state; - this.avatar = this.isHolding() ? DefaultAvatars.TELEPHONY_HOLD : DefaultAvatars.TELEPHONY; - this.consultation = this.consultation && !this.isHolding(); - } - }; - - BaseCall.prototype.checkCstaState = function (states) { - if (!this.atcCallInfo) { - return false; - } - if (!Array.isArray(states)) { - return states === this.atcCallInfo.cstaState; - } - var that = this; - return states.some(function (state) { - return state === that.atcCallInfo.cstaState; - }); - }; - - BaseCall.prototype.isHolding = function () { - if (!this.atcCallInfo) { - return false; - } - return this.checkCstaState([CstaCallState.Holding, CstaCallState.HoldOnHold, CstaCallState.ConferenceHolding]); - }; - - BaseCall.prototype.isHoldAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isHoldAllowed(); - }; - - BaseCall.prototype.isConsultAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isConsultAllowed(); - }; - - BaseCall.prototype.isConferenceCallAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isConferenceCallAllowed(); - }; - - BaseCall.prototype.isRetrieveAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isRetrieveAllowed(); - }; - - BaseCall.prototype.isTransferCallAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isTransferCallAllowed(); - }; - - BaseCall.prototype.isTransferAllowed = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isTransferAllowed(); - }; - - BaseCall.prototype.getPosition = function () { - if (!this.atcCallInfo) { - return null; - } - return this.atcCallInfo.getPosition(); - }; - - BaseCall.prototype.setAtcHandoverInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.setHandoverInProgress(); - } - }; - - BaseCall.prototype.clearAtcHandoverInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.clearHandoverInProgress(); - } - }; - - BaseCall.prototype.isHoldInProgress = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isHoldInProgress(); - }; - - BaseCall.prototype.setHoldInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.setHoldInProgress(); - } - }; - - BaseCall.prototype.clearHoldInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.clearHoldInProgress(); - } - }; - - BaseCall.prototype.isRetrieveInProgress = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isRetrieveInProgress(); - }; - - BaseCall.prototype.setRetrieveInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.setRetrieveInProgress(); - } - }; - - BaseCall.prototype.clearRetrieveInProgress = function () { - if (this.atcCallInfo) { - this.atcCallInfo.clearRetrieveInProgress(); - } - }; - - BaseCall.prototype.isDtmfAllowed = function () { - return false; - }; - - BaseCall.prototype.isAtcConferenceCall = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isConferenceCall(); - }; - - BaseCall.prototype.setRedirectingUser = function (phoneNumber, fqNumber, displayName, userId, redirectionType) { - if (this.atcCallInfo) { - this.atcCallInfo.setRedirectingUser(phoneNumber, fqNumber, displayName, userId, redirectionType); - } - }; - - BaseCall.prototype.getRedirectingUser = function () { - if (!this.atcCallInfo) { - return null; - } - return this.atcCallInfo.getRedirectingUser(); - }; - - BaseCall.prototype.setRedirectionType = function (type) { - if (!this.atcCallInfo) { - return; - } - return this.atcCallInfo.setRedirectionType(type); - }; - - BaseCall.prototype.getRedirectionType = function () { - if (this.atcCallInfo) { - return this.atcCallInfo.getRedirectionType(); - } - return null; - }; - - BaseCall.prototype.isForwarded = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isForwarded(); - }; - - BaseCall.prototype.isPickupNotification = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isPickupNotification(); - }; - - BaseCall.prototype.isPickedUp = function () { - if (!this.atcCallInfo) { - return false; - } - return this.atcCallInfo.isPickedUp(); - }; - - BaseCall.prototype.setDisconnectCause = function (cause, reason) { - if (!this._disconnectCause) { - this.disconnectCause = { - cause: cause, - reason: reason - }; - } - }; - - // Exports - circuit.BaseCall = BaseCall; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.CallDirection = CallDirection; - circuit.Enums.CallState = CallState; - - return circuit; - -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var BaseCall = circuit.BaseCall; - var CallState = circuit.Enums.CallState; - var Proto = circuit.Proto; - var Utils = circuit.Utils; - - function RemoteCall(conversation) { - // Imports (change to injected Circuit) - var logger = Circuit.logger; - - if (!conversation || !conversation.rtcSessionId) { - throw new Error('Cannot create RemoteCall object without a valid conversation'); - } - - // Call the base constructor - RemoteCall.parent.constructor.call(this, conversation, true); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - // Define the read-only properties to access the internal variables - Object.defineProperties(this, { - isPullAllowed: { - get: function () { return !this.pullNotAllowed && this.state === CallState.ActiveRemote; }, - enumerable: true, - configurable: false - } - }); - // Remote client on which the call is established - this.activeClient = null; - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////////////////// - conversation = null; // Remove reference to conversation object - - logger.info('[RemoteCall]: Created new remote call with callID = ', this.callId); - } - - Utils.inherit(RemoteCall, BaseCall); - - RemoteCall.prototype.setActiveClient = function (apiParticipant) { - if (!apiParticipant) { - return; - } - if (apiParticipant.userId) { - this.activeClient = { - clientId: apiParticipant.clientId, - clientDisplayName: apiParticipant.clientDisplayName, - mediaType: Proto.getMediaType(apiParticipant.mediaTypes) - }; - // Check call permissions: - // We need to explicitly check if the backend set the pullCall flag to false (for backwards compatibility) - this.pullNotAllowed = (apiParticipant.pullCall === false); - } else { - // Set data only. This is the case for remote calls where the user has been invited as guest. - this.activeClient = apiParticipant; - } - }; - - // Exports - circuit.RemoteCall = RemoteCall; - - return circuit; - -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - /* - * ATC registration state - * @readonly - * @enum {String} - * @property Disconnected - Client is not associated with ATC - * @property Unregistered - Client is not registered with ATC - * @property Registering - Client is registering with ATC - * @property Registered - Client is registered with ATC - */ - var AtcRegistrationState = Object.freeze({ - Disconnected: 'Disconnected', - Unregistered: 'Unregistered', - Registering: 'Registering', - Registered: 'Registered' - }); - - // Exports - circuit.Enums = circuit.Enums || {}; - circuit.Enums.AtcRegistrationState = AtcRegistrationState; - - return circuit; - -})(Circuit || {}); - - -// Define globals for JSHint -/*global Audio, window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = circuit.logger; - var BaseCall = circuit.BaseCall; - var CallState = circuit.Enums.CallState; - var ParticipantState = circuit.Enums.ParticipantState; - var Utils = circuit.Utils; - var Constants = circuit.Constants; - var WebRTCAdapter = circuit.WebRTCAdapter; - - var SERVER_ENDED_PRFX = 'SERVER_ENDED:'; - var CallServerTerminatedReason = Object.freeze({ - SERVER_ENDED_PRFX: SERVER_ENDED_PRFX - }); - - var CLIENT_ENDED_PRFX = 'CLIENT_ENDED:'; - var CallClientTerminatedReason = Object.freeze({ - CLIENT_ENDED_PRFX: CLIENT_ENDED_PRFX, - ANOTHER_CLIENT_ANSWERED: CLIENT_ENDED_PRFX + 'ANOTHER_CLIENT_ANSWERED', - ANOTHER_CLIENT_REJECTED: CLIENT_ENDED_PRFX + 'ANOTHER_CLIENT_REJECTED', - ANOTHER_CLIENT_PULLED_CALL: CLIENT_ENDED_PRFX + 'ANOTHER_CLIENT_PULLED_CALL', - CALL_MOVED_TO_ANOTHER_CONV: CLIENT_ENDED_PRFX + 'CALL_MOVED_TO_ANOTHER_CONV', - CALLER_LEFT_CONFERENCE: CLIENT_ENDED_PRFX + 'CALLER_LEFT_CONFERENCE', - ENDED_BY_ANOTHER_USER: CLIENT_ENDED_PRFX + 'ENDED_BY_ANOTHER_USER', - LOST_WEBSOCKET_CONNECTION: CLIENT_ENDED_PRFX + 'LOST_WEBSOCKET_CONNECTION', - NO_USERS_LEFT: CLIENT_ENDED_PRFX + 'NO_USERS_LEFT', - REQUEST_TO_SERVER_FAILED: CLIENT_ENDED_PRFX + 'REQUEST_TO_SERVER_FAILED', - RTC_SESSION_START_FAILED: CLIENT_ENDED_PRFX + 'RTC_SESSION_START_FAILED', - SET_REMOTE_SDP_FAILED: CLIENT_ENDED_PRFX + 'SET_REMOTE_SDP_FAILED', - USER_ENDED: CLIENT_ENDED_PRFX + 'USER_ENDED', - LOST_MEDIA_STREAM: CLIENT_ENDED_PRFX + 'LOST_MEDIA_STREAM', - ICE_TIMED_OUT: CLIENT_ENDED_PRFX + 'ICE_TIMED_OUT', - USER_LOGGED_OUT: CLIENT_ENDED_PRFX + 'USER_LOGGED_OUT', - PAGE_UNLOADED: CLIENT_ENDED_PRFX + 'PAGE_UNLOADED', - DISCONNECTED: CLIENT_ENDED_PRFX + 'WS_DISCONNECTED', - FAILED_TO_SEND: CLIENT_ENDED_PRFX + 'WS_FAILED_TO_SEND', - REQUEST_TIMEOUT: CLIENT_ENDED_PRFX + 'WS_REQUEST_TIMEOUT', - MEDIA_RENEGOTIATION: CLIENT_ENDED_PRFX + 'MEDIA_RENEGOTIATION' - }); - - function LocalCall(conversation, options) { - options = options || {}; - - // Import here due to Circular dependency - var RtcSessionController = circuit.RtcSessionController; - - if (!conversation || !conversation.rtcSessionId) { - throw new Error('Cannot create LocalCall object without a valid conversation'); - } - - // Call the base constructor - LocalCall.parent.constructor.call(this, conversation, false); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - // - // These variables can only be accessed via getters/setters. - // Critical variables that cannot be changed directly must be defined here. - /////////////////////////////////////////////////////////////////////////////////////// - var _that = this; - var _sessionCtrl = null; - var _hasTerminated = false; - var _ringingTimer = null; - var _hasActiveRemoteVideo = false; - var _remoteVideoDisabled = false; - var _remoteAudioDisabled = false; - var _hasUnmutedParticipants = false; - var _isMocked = false; - var _isMeetingPointInvited = false; - var _curtain = null; // current curtain data object (from events) - var _playbackDevice = null; - var _recordingDevice = null; - var _videoDevice = null; - var _atcAdvancing = false; - var _playbackDeviceList = []; - var _recordingDeviceList = []; - var _videoDeviceList = []; - - // Remember the current view for the call. - // true: call stage is collapsed and user is viewing the conversation feed or details - // false: call stage is expanded and user is in the conference object view - var _conversationFeedView = false; - - var DEFAULT_RINGING_TIME = 15000; - var RING_ALL_MAX_NUM_OF_PEERS = 9; - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function createSessionController() { - if (!_sessionCtrl) { - _sessionCtrl = new RtcSessionController({ - callId: _that.callId, - isDirectCall: _that.isDirect, - isTelephonyCall: _that.isTelephonyCall, - isLargeConference: _that.isLarge, - isAtcPullCall: options.isAtcPullCall, - reuseDesktopStreamFrom: options.reuseDesktopStreamFrom - }); - _sessionCtrl.onMediaUpdate = onMediaUpdate; - _sessionCtrl.onLocalVideoURL = onLocalVideoURL; - _sessionCtrl.onRemoteObjectURL = onRemoteObjectURL; - _sessionCtrl.onScreenSharePointerStatus = onScreenSharePointerStatus; - - if (!_that.isDirect) { - // For group conversations tell the RTCSessionController to offer - // additional recvonly media streams for video - _sessionCtrl.setExtraVideoChannelsForParticipants(_that.numConvParticipants); - } - } - } - - function unregisterSessionController() { - if (_sessionCtrl) { - _sessionCtrl.onMediaUpdate = null; - _sessionCtrl.onLocalVideoURL = null; - _sessionCtrl.onRemoteObjectURL = null; - _sessionCtrl.onScreenSharePointerStatus = null; - } - } - - function onMediaUpdate() { - logger.debug('[LocalCall]: RtcSessionController - onMediaUpdate: callId =', _that.callId); - - _that.activeMediaType = _sessionCtrl.getActiveMediaType(); - logger.debug('[LocalCall]: Set active media type to ', _that.activeMediaType); - - _that.localMediaType = _sessionCtrl.getMediaConstraints(); - logger.debug('[LocalCall]: Set local media type to ', _that.localMediaType); - - _that.updateMediaType(); - _that.updateCallState(); - } - - function onLocalVideoURL(event) { - logger.debug('[LocalCall]: RtcSessionController - onLocalVideoURL: callId =', _that.callId); - _that.localVideoUrl = event.url; - logger.debug('[LocalCall]: Set local video URL to', event.url || '<empty>'); - } - - function onScreenSharePointerStatus(data) { - logger.debug('[LocalCall]: RtcSessionController - onScreenSharePointerStatus', data); - _that.pointer = { - isSupported: data.isSupported, - isEnabled: data.isEnabled - }; - } - - function onRemoteObjectURL(event) { - logger.debug('[LocalCall]: RtcSessionController - onRemoteObjectURL: callId =', _that.callId); - - _that.remoteAudioUrl = (event.audioUrl && event.audioUrl.url) || ''; - logger.debug('[LocalCall]: Set remote audio URL to', _that.remoteAudioUrl || '<empty>'); - - _that.remoteVideoUrlStreams = event.videoUrl || []; - logger.debug('[LocalCall]: Set remote video URL streams to', event.videoUrl || '<empty>'); - - // Set the remote video URL for all participants - _that.participants.forEach(function (p) { - _that.setParticipantRemoteVideoURL(p); - }); - _that.checkForActiveRemoteVideo(); - } - - function setCurtainObject(curtain) { - if (curtain && curtain.curtainState) { - if (_curtain) { - curtain.lastCurtainState = _curtain.curtainState; - } - _curtain = curtain; - _curtain.curtainClosed = curtain.curtainState !== Constants.CurtainState.OPEN; - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - // Define the read-only properties to access the internal variables - Object.defineProperties(this, { - sessionCtrl: { - get: function () { return _sessionCtrl; }, - enumerable: true, - configurable: false - }, - isVdi: { - get: function () { return !!_sessionCtrl.isRemoteController; }, - enumerable: true, - configurable: false - }, - locallyMuted: { - get: function () { return _sessionCtrl.isMuted(); }, - enumerable: true, - configurable: false - }, - remoteVideoDisabled: { - get: function () { return _remoteVideoDisabled; }, - enumerable: true, - configurable: false - }, - remoteAudioDisabled: { - get: function () { return _remoteAudioDisabled; }, - enumerable: true, - configurable: false - }, - isMocked: { - get: function () { return _isMocked; }, - enumerable: true, - configurable: false - }, - isMeetingPointInvited: { - get: function () { return _isMeetingPointInvited; }, - enumerable: true, - configurable: false - }, - ringAllAllowed: { - get: function () { - // This property only verifies the number of users in the conversation, not the call state - return !!(this.peerUsers && this.peerUsers.length > 0 && this.peerUsers.length <= RING_ALL_MAX_NUM_OF_PEERS); - }, - enumerable: true, - configurable: false - }, - ringAllMaxNumber: { - get: function () { return RING_ALL_MAX_NUM_OF_PEERS; }, - enumerable: true, - configurable: false - }, - conversationFeedView: { - get: function () { return _conversationFeedView || this.state === CallState.Ringing; }, - set: function (value) { _conversationFeedView = value; }, - enumerable: true, - configurable: false - }, - curtain: { - get: function () { return _curtain || {}; }, - set: function (value) { setCurtainObject(value); }, - enumerable: true, - configurable: false - }, - playbackDevice: { - get: function () { return _playbackDevice; }, - enumerable: true, - configurable: false - }, - recordingDevice: { - get: function () { return _recordingDevice; }, - enumerable: true, - configurable: false - }, - videoDevice: { - get: function () { return _videoDevice; }, - enumerable: true, - configurable: false - }, - playbackDeviceList: { - get: function () { return _playbackDeviceList; }, - enumerable: true, - configurable: false - }, - recordingDeviceList: { - get: function () { return _recordingDeviceList; }, - enumerable: true, - configurable: false - }, - videoDeviceList: { - get: function () { return _videoDeviceList; }, - enumerable: true, - configurable: false - }, - atcAdvancing: { - get: function () { return _atcAdvancing; }, - set: function (value) { _atcAdvancing = !!value; }, - enumerable: true, - configurable: false - }, - savedRTPStats: { - get: function () { - if (this.sessionCtrl) { - return this.sessionCtrl.getLastSavedStats(); - } - }, - enumerable: true, - configurable: false - } - }); - - // The blob URLs for local video and remote audio/video - this.localVideoUrl = ''; - this.remoteAudioUrl = ''; - this.remoteVideoUrlStreams = []; - - // Stores the media types negotiated at the RTC level. - this.activeMediaType = {audio: false, video: false, desktop: false}; - this.ringingTimeout = DEFAULT_RINGING_TIME; - - // Is screenshare pointing supported and enabled - this.pointer = { - isSupported: false, - isEnabled: false - }; - - // Bad quality notification bar on call stage - this.badQualityNotification = { - detected: false, - acted: false, - dismissed: false - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Members - // (Only includes members which need to access private data) - /////////////////////////////////////////////////////////////////////////////////////// - this.terminate = function () { - if (_hasTerminated) { - return; - } - - logger.debug('[LocalCall]: Terminating call with callId =', this.callId); - unregisterSessionController(); - _sessionCtrl.terminate(); - - this.localVideoUrl = ''; - this.remoteAudioUrl = ''; - this.remoteVideoUrlStreams = []; - - // Call the prototype's terminate() function - LocalCall.prototype.terminate.call(this); - - this.stopRingingTimer(); - _hasTerminated = true; - }; - - this.setMockedCall = function () { - logger.debug('[LocalCall]: Setting call as mocked'); - _isMocked = true; - _sessionCtrl.setMockedCall(); - - var simulateVideoActiveSpeaker = _sessionCtrl.getNumberOfExtraVideoChannels() === 0; - if (simulateVideoActiveSpeaker) { - // Assign the video stream to a participant with screenshare or to the - // first participant with video. - var activeParticipant; - this.participants.forEach(function (p) { - p.streamId = ''; - if (p.mediaType.desktop || (!activeParticipant && p.mediaType.video)) { - activeParticipant = p; - } - }); - if (activeParticipant) { - activeParticipant.streamId = 'mock'; - } - } else { - this.participants.forEach(function (p) { - p.streamId = (p.mediaType.video || p.mediaType.desktop) ? 'mock' : ''; - }); - } - - // Initialize the remote stream after a 100 ms timeout to simulate - // the delay in the real scenarios. - window.setTimeout(function () { - if (this.state !== CallState.Terminated) { - logger.debug('[LocalCall]: Initialize remote mocked streams'); - - onRemoteObjectURL({ - audioUrl: '', - videoUrl: [{ - streamId: 'mock', - url: 'content/images/mock/trailer.mp4' - }] - }); - _sessionCtrl.onRemoteObjectURL = null; - } - }, 100); - }; - - this.startRingingTimer = function (cb) { - if (typeof cb !== 'function') { - return; - } - - logger.info('[LocalCall]: Starting ringing timer for callId =', _that.callId); - _ringingTimer = window.setTimeout(function () { - logger.info('[LocalCall]: Ringing timeout for callId =', _that.callId); - _ringingTimer = null; - cb(); - }, this.ringingTimeout); - }; - - this.stopRingingTimer = function () { - if (_ringingTimer) { - logger.info('[LocalCall]: Cancelling ringing timer for callId =', _that.callId); - window.clearTimeout(_ringingTimer); - _ringingTimer = null; - } - }; - - // Override hasRemoteVideo to check if we have a valid remote video URL for at - // least one participant - this.hasRemoteVideo = function () { - return _hasActiveRemoteVideo; - }; - - this.checkForActiveRemoteVideo = function () { - if (_remoteVideoDisabled) { - _hasActiveRemoteVideo = false; - } else { - _hasActiveRemoteVideo = this.participants.some(function (p) { - return !!p.videoUrl; - }); - } - logger.debug('[LocalCall]: Set hasActiveRemoteVideo to ', _hasActiveRemoteVideo); - }; - - this.enableRemoteVideo = function () { - if (!_remoteVideoDisabled) { - return; - } - _remoteVideoDisabled = false; - this.checkForActiveRemoteVideo(); - this.sessionCtrl.enableRemoteVideo(); - logger.debug('[LocalCall]: Enabled remote video'); - }; - - this.disableRemoteVideo = function () { - if (_remoteVideoDisabled) { - return; - } - _remoteVideoDisabled = true; - _hasActiveRemoteVideo = false; - - this.participants.forEach(function (p) { - p.videoUrl = ''; - if (_that.isDirect) { - p.streamId = ''; - } - }); - this.sessionCtrl.disableRemoteVideo(); - logger.debug('[LocalCall]: Disabled remote video'); - }; - - this.enableRemoteAudio = function () { - if (!_remoteAudioDisabled) { - return; - } - _remoteAudioDisabled = false; - logger.debug('[LocalCall]: Enabled remote audio.'); - }; - - this.disableRemoteAudio = function () { - if (_remoteAudioDisabled) { - return; - } - _remoteAudioDisabled = true; - logger.debug('[LocalCall]: Disabled remote audio.'); - }; - - this.setMeetingPointInviteState = function (inviteState) { - _isMeetingPointInvited = !!inviteState; - logger.debug('[LocalCall]: Set Circuit Meeting Point state to ', inviteState); - }; - - this.setHasUnmutedParticipants = function () { - _hasUnmutedParticipants = this.participants.some(function (p) { - return !p.muted; - }); - }; - - this.hasUnmutedParticipants = function () { - return _hasUnmutedParticipants; - }; - - this.getUnmutedParticipantIds = function () { - var unmutedParticipants = []; - this.participants.forEach(function (p) { - if (!p.muted) { - unmutedParticipants.push(p.userId); - } - }); - return unmutedParticipants; - }; - - this.getMediaNode = function () { - return (this.isTestCall ? RtcSessionController.mediaNode : null); - }; - - this.updateMediaDevices = function () { - - var getDeviceLabels = function (devices) { - if (!devices) { - return []; - } - return devices.filter(function (d) { - return d.id !== 'default' && d.id !== 'communications' && d.label; - }).map(function (d) { - return d.label; - }); - }; - - WebRTCAdapter.getMediaSources(function (audioSources, videoSources, audioOutputDevices) { - if (WebRTCAdapter.getMediaSourcesSupported) { - var dev = Utils.selectMediaDevice(audioOutputDevices, RtcSessionController.playbackDevices); - _playbackDevice = dev && dev.label; - _playbackDeviceList = getDeviceLabels(audioOutputDevices); - dev = Utils.selectMediaDevice(audioSources, RtcSessionController.recordingDevices); - _recordingDevice = dev && dev.label; - _recordingDeviceList = getDeviceLabels(audioSources); - dev = Utils.selectMediaDevice(videoSources, RtcSessionController.videoDevices); - _videoDevice = dev && dev.label; - _videoDeviceList = getDeviceLabels(videoSources); - } else { - // Mobile devices don't support getMediaSources, so get these devices through Audio object - _playbackDevice = Audio.getPlaybackDevice && Audio.getPlaybackDevice(); - _recordingDevice = Audio.getRecordingDevice && Audio.getRecordingDevice(); - _videoDevice = Audio.getVideoDevice && Audio.getVideoDevice(); - } - logger.debug('[LocalCall]: Updated media devices: recording=' + _recordingDevice + ' playback=' + - _playbackDevice + ' video=' + _videoDevice + ' callId=' + _that.callId); - }); - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////////////////// - createSessionController(); - conversation = null; // Remove reference to conversation object - _that.updateMediaDevices(); - - logger.info('[LocalCall]: Created new WebRTC call with callID = ', this.callId); - } - - Utils.inherit(LocalCall, BaseCall); - - function setMockStreamId(call, participant) { - if (!participant.mediaType.video && !participant.mediaType.desktop) { - participant.streamId = ''; - return; - } - var simulateVideoActiveSpeaker = call.sessionCtrl.getNumberOfExtraVideoChannels() === 0; - if (simulateVideoActiveSpeaker) { - var activeParticipant; - call.participants.some(function (p) { - if (p.streamId) { - activeParticipant = p; - return true; - } - }); - participant.streamId = (!activeParticipant || activeParticipant.userId === participant.userId) ? 'mock' : ''; - } else { - participant.streamId = 'mock'; - } - } - - LocalCall.prototype.hasRemoteMedia = function () { - return !!(this.remoteAudioUrl || this.remoteVideoUrlStreams.length); - }; - - LocalCall.prototype.hasRemoteScreenShare = function () { - return this.mediaType.desktop && !this.remoteVideoDisabled && - (!this.localMediaType.desktop || (this.isDirect && this.participants[0].mediaType.desktop)); - }; - - LocalCall.prototype.addParticipant = function (participant) { - if (this.isMocked && participant && participant.mediaType) { - setMockStreamId(this, participant); - } - var addedParticipant = LocalCall.parent.addParticipant.apply(this, arguments); - if (addedParticipant) { - this.setParticipantRemoteVideoURL(addedParticipant); - this.checkForActiveRemoteVideo(); - this.setHasUnmutedParticipants(); - } - return addedParticipant; - }; - - LocalCall.prototype.updateParticipant = function (participant) { - if (this.isMocked && participant && participant.mediaType) { - setMockStreamId(this, participant); - } - var updatedParticipant = LocalCall.parent.updateParticipant.apply(this, arguments); - if (updatedParticipant) { - this.setParticipantRemoteVideoURL(updatedParticipant); - this.checkForActiveRemoteVideo(); - this.setHasUnmutedParticipants(); - } - return updatedParticipant; - }; - - LocalCall.prototype.removeParticipant = function () { - var removedParticipant = LocalCall.parent.removeParticipant.apply(this, arguments); - if (removedParticipant) { - this.setHasUnmutedParticipants(); - } - return removedParticipant; - }; - - LocalCall.prototype.setParticipantRemoteVideoURL = function (participant) { - participant.videoUrl = ''; - - if (this.isDirect) { - participant.streamId = ''; - } - - if (this.remoteVideoDisabled) { - return; - } - - if (participant.mediaType && !participant.mediaType.video && !participant.mediaType.desktop) { - logger.debug('[LocalCall]: The participant does not have video or screen-share. Do not set video URL. userId = ', participant.userId); - return; - } - - if (this.isDirect) { - if (this.remoteVideoUrlStreams && this.remoteVideoUrlStreams.length) { - var remoteUrlStream = this.remoteVideoUrlStreams[this.remoteVideoUrlStreams.length - 1]; - participant.streamId = remoteUrlStream.streamId; - participant.videoUrl = remoteUrlStream.url; - participant.pcState = participant.muted ? ParticipantState.Muted : ParticipantState.Active; - } - } else { - if (!participant.streamId && !participant.screenStreamId) { - logger.info('[LocalCall]: The participant does not have a video streamId. userId = ', participant.userId); - return; - } - - if (this.remoteVideoUrlStreams && this.remoteVideoUrlStreams.length) { - this.remoteVideoUrlStreams.some(function (u) { - if (u.streamId === participant.streamId || u.streamId === participant.screenStreamId) { - participant.videoUrl = u.url; - - // If the participant has a remote video URL, it must be Active - participant.pcState = participant.muted ? ParticipantState.Muted : ParticipantState.Active; - - logger.debug('[LocalCall]: Set remote video URL for participant: ', participant); - return true; - } - }); - } - } - - if (!participant.videoUrl) { - logger.debug('[LocalCall]: We still do not have a video URL for the participant. ', participant); - } - }; - - LocalCall.prototype.hasLocalScreenShare = function () { - return this.sessionCtrl.hasScreenShare(); - }; - - LocalCall.prototype.isMuted = function () { - return this.remotelyMuted || this.locallyMuted; - }; - - LocalCall.prototype.mute = function (cb) { - if (this.checkState(CallState.Terminated)) { - logger.error('[LocalCall]: Cannot mute a call that has already been terminated'); - return false; - } - - logger.debug('[LocalCall]: Mute call with callId =', this.callId); - return this.sessionCtrl.mute(cb); - }; - - LocalCall.prototype.unmute = function (cb) { - if (this.checkState(CallState.Terminated)) { - logger.error('[LocalCall]: Cannot mute a call that has already been terminated'); - return false; - } - - logger.debug('[LocalCall]: Unmute call with callId =', this.callId); - return this.sessionCtrl.unmute(cb); - }; - - LocalCall.prototype.isLocalMuteAllowed = function () { - return this.sessionCtrl.isLocalMuteAllowed(); - }; - - LocalCall.prototype.toggleMute = function () { - if (this.sessionCtrl.isMuted()) { - return this.unmute(); - } - return this.mute(); - }; - - LocalCall.prototype.setActiveSpeakers = function (activeSpeakers) { - if (!activeSpeakers) { - return false; - } - if (!(activeSpeakers instanceof Array)) { - activeSpeakers = [activeSpeakers]; - } - var changed = false; - this.participants.forEach(function (p) { - var active = (activeSpeakers.indexOf(p.userId) !== -1); - if (p.activeSpeaker !== active) { - p.activeSpeaker = active; - changed = true; - } - }); - return changed; - }; - - LocalCall.prototype.updateCallState = function () { - if (!this.state.established) { - // The call has not yet been established - return; - } - - var newState; - if (!this.sessionCtrl.isConnected() || // Media is not connected - (!this.isDirect && !this.hasOtherParticipants()) || // Group conference with no other participants - (this.isATCCall && !this.getCstaState().established)) { // ATC call not established - newState = CallState.Waiting; - } else if (this.sessionCtrl.isHolding()) { - newState = CallState.Holding; - } else if (this.sessionCtrl.isHeld()) { - newState = CallState.Held; - } else { - newState = CallState.Active; - } - this.setState(newState); - }; - - LocalCall.prototype.isDtmfAllowed = function () { - if (this.checkState([CallState.Active, CallState.Delivered])) { - if (!this.sessionCtrl.canSendDTMFDigits()) { - if (this.checkState(CallState.Active)) { - return !!this.atcCallInfo; // For ATC calls we can use CSTA to generate digits (only in Active state) - } - return false; - } - return true; - } - return false; - }; - - // Exports - circuit.LocalCall = LocalCall; - circuit.Enums.CallServerTerminatedReason = CallServerTerminatedReason; - circuit.Enums.CallClientTerminatedReason = CallClientTerminatedReason; - - return circuit; - -})(Circuit || {}); - -// Define external globals for JSHint -/*global chrome, require, window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var AppMessagingHandlerSingleton = circuit.AppMessagingHandlerSingleton; - var ChromeExtension = circuit.ChromeExtension; - var Constants = circuit.Constants; - var logger = circuit.logger; - var Utils = circuit.Utils; - var TemasysAdapter = circuit.TemasysAdapter; - - /////////////////////////////////////////////////////////////////////////////////////// - // Controller for Screen Sharing - /////////////////////////////////////////////////////////////////////////////////////// - var ScreenSharingController = (function () { - - var _appMsgHandler = AppMessagingHandlerSingleton ? AppMessagingHandlerSingleton.getInstance() : null; - - var _api = { - getScreen: function (successCb, errorCb) { - errorCb && errorCb('NOT_SUPPORTED'); - }, - unregEvtHandlers: function () {}, - isScreensharingAvailable: function () { - return false; - }, - installExtension: function () {}, - injectExtensionSvc: function () {}, - injectTemasysSvc: function () {} - }; - - if (window.navigator.platform === 'iOS') { - return _api; - } - - var _browser = Utils.getBrowserInfo(); - if (circuit.isElectron) { - _api.getScreen = function (successCb, errorCb) { - var ipcRenderer = require('electron').ipcRenderer; - ipcRenderer.once('screenshare-chooser', function (e, type, data) { - if (type === 'share') { - logger.info('[ScreenSharingController]: Got screen share stream. Stream id: ', data.streamId); - successCb && successCb({ - streamId: data.streamId, - pointer: data.pointer - }); - } else if (type === 'cancel') { - logger.info('[ScreenSharingController]: User canceled screen share'); - errorCb && errorCb(Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED); - } - }); - logger.info('[ScreenSharingController]: Request screen in Electron'); - ipcRenderer.send('screenshare-chooser', 'open'); - }; - - _api.isScreensharingAvailable = function () { - return true; - }; - - } else if (circuit.isVdiApp) { - _api.getScreen = function (successCb, errorCb) { - _appMsgHandler.sendInternalMessage({ method: 'showwindow' }); - - window.setTimeout(function () { - chrome.desktopCapture.chooseDesktopMedia(['screen'], null, function (streamId) { - _appMsgHandler.sendInternalMessage({ method: 'minimizewindow' }); - if (streamId) { - successCb && successCb({streamId: streamId}); - } else { - errorCb && errorCb(Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED); - } - }); - }, 0); - }; - - _api.isScreensharingAvailable = function () { - return true; - }; - - } else if (_browser.chrome || _browser.phantomjs) { - // Chrome Extension helpers - var _extConnHandler; - var _extScreenShareEventsHandler; - var _extensionSvc; - - var unregExtensionEvtHandlers = function (eventHandler) { - if (!eventHandler || eventHandler !== _extScreenShareEventsHandler) { - // This must be an old handler that has already been unregistered - return; - } - if (_extConnHandler && _extScreenShareEventsHandler) { - _extConnHandler.removeEventListener(ChromeExtension.BgTarget.SCREEN_SHARE, _extScreenShareEventsHandler); - _extScreenShareEventsHandler = null; - } - }; - - var onExtScreenShareEvent = function (successCb, errorCb, evt) { - var data = evt.data; - if (!evt.suppressLog) { - logger.info('[ScreenSharingController]: Received screenshare event from extension - ', data.type); - } - - switch (data.type) { - case ChromeExtension.BgScreenShareMsgType.CHOOSE_DESKTOP_MEDIA_DONE: - logger.info('[ScreenSharingController]: CHOOSE_DESKTOP_MEDIA done. Stream Id:', data.streamId); - unregExtensionEvtHandlers(_extScreenShareEventsHandler); - successCb && successCb({streamId: data.streamId}); - break; - case ChromeExtension.BgScreenShareMsgType.CHOOSE_DESKTOP_MEDIA_CANCELLED: - logger.info('[ScreenSharingController]: CHOOSE_DESKTOP_MEDIA cancelled'); - unregExtensionEvtHandlers(_extScreenShareEventsHandler); - errorCb(Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED); - break; - } - }; - - var regExtensionEvtHandlers = function (successCb, errorCb) { - if (_extConnHandler) { - // Unregister previous event handler (if there is one) - unregExtensionEvtHandlers(_extScreenShareEventsHandler); - - _extScreenShareEventsHandler = onExtScreenShareEvent.bind(null, successCb, errorCb); - _extConnHandler.addEventListener(ChromeExtension.BgTarget.SCREEN_SHARE, _extScreenShareEventsHandler); - return _extScreenShareEventsHandler; - } - }; - - _api.getScreen = function (successCb, errorCb) { - var evtHandler = null; - if (_extensionSvc && _extensionSvc.isExtensionRunning()) { - evtHandler = regExtensionEvtHandlers(successCb, errorCb); - - _extConnHandler.getScreenShareUserMedia(function (error) { - if (error) { - logger.warn('[ScreenSharingController]: Error starting screen share using chrome extension: ', error); - unregExtensionEvtHandlers(evtHandler); - errorCb('res_AccessToExtensionFailed'); - } - }); - } else { - // Under normal circumstances, we shouldn't hit this else branch - errorCb('res_AccessToExtensionFailed'); - } - return evtHandler; - }; - - _api.isScreensharingAvailable = function () { - return !!_extensionSvc && _extensionSvc.isExtensionRunning(); - }; - - _api.installExtension = function () { - if (_extensionSvc) { - logger.debug('[ScreenSharingController]: Install Chrome extension'); - _extensionSvc.installExtension(true).then(function () { - logger.info('[ScreenSharingController]: Chrome extension was successfully installed'); - }).catch(function (error) { - logger.warn('[ScreenSharingController]: Failed to install Chrome extension. ', error); - }); - } - }; - - _api.injectExtensionSvc = function (extensionSvc, extConnHandler) { - _extensionSvc = extensionSvc; - _extConnHandler = extConnHandler; - }; - - _api.unregEvtHandlers = unregExtensionEvtHandlers; - - } else if (_browser.msie) { - var _temasysSvc; - - _api.getScreen = function (successCb, errorCb) { - if (TemasysAdapter && TemasysAdapter.isScreensharingAvailable()) { - successCb && successCb(); - } else { - errorCb && errorCb('NOT_AVAILABLE'); - } - }; - - _api.isScreensharingAvailable = function () { - return !!TemasysAdapter && TemasysAdapter.isScreensharingAvailable(); - }; - - _api.installExtension = function () { - _temasysSvc && _temasysSvc.showPopup(); - }; - - _api.injectTemasysSvc = function (temasysSvc) { - _temasysSvc = temasysSvc; - }; - } else if (_browser.firefox) { - var SCREENSHARE_SUPPORT_VERSION = 33; - - _api.getScreen = function (successCb) { - successCb && successCb(); - }; - - _api.isScreensharingAvailable = function () { - /** Need to add more correct check. - * This method only verify if screen sharing enabled (since version 33). - * When Mozilla integrate correct screen sharing of multiple monitors - * version will increase. - */ - return parseInt(_browser.version, 10) >= SCREENSHARE_SUPPORT_VERSION; - }; - } else if (window.navigator.platform === 'Android') { - _api.getScreen = function (successCb) { - successCb && successCb(); - }; - _api.isScreensharingAvailable = function () { - return true; - }; - } - - return _api; - })(); - - // Exports - circuit.ScreenSharingController = ScreenSharingController; - - return circuit; - -})(Circuit || {}); - -// Define external globals for JSHint -/*global window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = Circuit.logger; - var Utils = circuit.Utils; - - var _isMobile = Utils.isMobile(); - - /*eslint-disable key-spacing, no-multi-spaces*/ - var RTPAudioStatType = Object.freeze({ - GOOG_JITTER_RECEIVED: {StatName: 'jr', StatText: 'googJitterReceived', Threshold: 50, RecurrenceThreshold: _isMobile ? 2 : 3, reportIssues: true}, - GOOG_RTT: {StatName: 'rtt', StatText: 'googRtt', Threshold: 300, RecurrenceThreshold: _isMobile ? 2 : 3, reportIssues: true}, - PACKET_LOSS_RECV: {StatName: '', StatText: 'packetLossRecv', Threshold: 0.05, RecurrenceThreshold: _isMobile ? 2 : 3, reportIssues: true}, - PACKET_LOSS_SEND: {StatName: '', StatText: 'packetLossSend', Threshold: 0.05, RecurrenceThreshold: _isMobile ? 0 : 3, reportIssues: true}, - NO_PACKETS_RECV: {StatName: '', StatText: 'noPacketsReceived', Threshold: 0, RecurrenceThreshold: _isMobile ? 2 : 3, reportIssues: true}, - ECHO_LIKELIHOOD: {StatName: 'ecmax', StatText: 'googResidualEchoLikelihoodRecentMax', Threshold: 0.5, RecurrenceThreshold: _isMobile ? 2 : 3, reportIssues: false} - }); - - var RTPVideoStatType = Object.freeze({ - GOOG_RTT: {StatName: 'rtt', StatText: 'googRtt', Threshold: 300, RecurrenceThreshold: _isMobile ? 2 : 3}, - PACKET_LOSS_RECV: {StatName: '', StatText: 'packetLossRecv', Threshold: 0.05, RecurrenceThreshold: _isMobile ? 2 : 3}, - PACKET_LOSS_SEND: {StatName: '', StatText: 'packetLossSend', Threshold: 0.05, RecurrenceThreshold: _isMobile ? 0 : 3} - }); - /*eslint-enable key-spacing, no-multi-spaces*/ - - var RtpStatsConfig = { - COLLECTION_INTERVAL: 5000, - DELAY_UPON_ANSWER: _isMobile ? 0 : 5000, - DELAY_RTT_PROCESSING: 3 // googRTT takes few seconds to settle, so skip 3 stat collection cycles - }; - - var RtpQualityLevel = Object.freeze({ - RTP_QUALITY_LOW: {levelName: 'low', value: 1, threshold: 0.1}, - RTP_QUALITY_MEDIUM: {levelName: 'medium', value: 2, threshold: 0.03}, - RTP_QUALITY_HIGH: {levelName: 'high', value: 3, threshold: 0}, - - getLevel: function (packetsLost) { - if (packetsLost > this.RTP_QUALITY_LOW.threshold) { - return this.RTP_QUALITY_LOW; - } else if (packetsLost > this.RTP_QUALITY_MEDIUM.threshold) { - return this.RTP_QUALITY_MEDIUM; - } else { - return this.RTP_QUALITY_HIGH; - } - } - }); - - var CallStats = Object.freeze({ - audioOutputLevel: 'aol', - bytesReceived: 'or', - bytesSent: 'os', - googAvailableSendBandwidth: 'abw', - /* - googAvgEncodeMs: 'ae', - googBandwidthLimitedResolution: 'blr', - googCaptureJitterMs: 'cj', - googCaptureQueueDelayMsPerS: 'cqd', - googCpuLimitedResolution: 'clr', - googCurrentDelayMs: 'cdm', - googDecodeMs: 'dm', - googEchoCancellationEchoDelayMedian: 'ecedm', - googEchoCancellationEchoDelayStdDev: 'ecedsd', - googEchoCancellationQualityMin: 'ecqm', - googEchoCancellationReturnLoss: 'ecrl', - googEchoCancellationReturnLossEnhancement: 'ecrle', - */ - googResidualEchoLikelihoodRecentMax: 'ecmax', - /* - googEncodeUsagePercent: 'eup', - googExpandRate: 'ert', - googFirsReceived: 'fr', - googFirsSent: 'fs', - */ - googFrameHeightReceived: 'fhr', - googFrameHeightSent: 'fhs', - googFrameWidthReceived: 'fwr', - googFrameWidthSent: 'fws', - googFrameRateDecoded: 'frd', - googFrameRateSent: 'frs', - /* - googFrameRateInput: 'fri', - googFrameRateOutput: 'fro', - googFrameRateReceived: 'frr', - googJitterBufferMs: 'jb', - */ - googJitterReceived: 'jr', - /* - googMaxDecodeMs: 'mdm', - googMinPlayoutDelayMs: 'mpd', - googNacksReceived: 'nr', - googNacksSent: 'ns', - googPlisReceived: 'plr', - googPlisSent: 'pls', - googPreferredJitterBufferMs: 'pjb', - googRenderDelayMs: 'rd', - */ - googRtt: 'rtt', - /* - googTargetDelayMs: 'td', - */ - googTransmitBitrate: 'tbr', - /* - googViewLimitedResolution: 'vlr', - */ - packetsLost: 'pl', - packetsReceived: 'pr', - packetsSent: 'ps' - }); - - - var RtcPromise; - if (typeof window.Promise !== 'undefined') { - // By default use the standard Promise object if supported by the browser - RtcPromise = window.Promise; - } - - ///////////////////////////////////////////////////////////////////////////// - // Helper functions - ///////////////////////////////////////////////////////////////////////////// - function formatStatsForClientDiagnostics(startTime, stats, pc, pcType, includeQosData) { - if (!stats || !pc) { - return null; - } - - var formattedStats = { - startTime: startTime, - pcType: pcType, - audio: {}, - video: {} - }; - var audioCandidateId, videoCandidateId; - var results = (stats.result) ? stats.result() : stats; - if (!Array.isArray(results)) { - return null; - } - - // Process the stats according to their types: - // 1. googComponent (ignored) - // 2. googCandidatePair (processed if googActiveConnection=true) - // 3. VideoBwe (processed) - // 4. googCertificate (ignored) - // 5. googTrack (ignored) - // 6. ssrc (processed) - results.forEach(function (res) { - if (!res) { - return; - } - var statsPtr = null; - if ((res.type === 'googCandidatePair') && (res.stat('googActiveConnection') === 'true')) { - if (res.stat('googChannelId') === 'Channel-audio-1') { - formattedStats.audio.rtt = parseInt(res.stat('googRtt'), 10); - if (includeQosData) { - audioCandidateId = res.stat('localCandidateId'); - formattedStats.audio.channelInfo = Utils.getChannelInfo(res); - } - } else { - formattedStats.video.rtt = parseInt(res.stat('googRtt'), 10); - if (includeQosData) { - videoCandidateId = res.stat('localCandidateId'); - formattedStats.video.channelInfo = Utils.getChannelInfo(res, res.stat('localCandidateId')); - } - } - } else if (res.type === 'ssrc') { - // For lack of better method, we check for specific stats in each ssrc so we can - // tell if it's audio or video (receive or transmit) - var commonData = { - id: res.id, - ssrc: res.stat('ssrc'), - timestamp: res.timestamp - }; - if (res.stat('audioInputLevel')) { - if (!formattedStats.audio.transmit) { - formattedStats.audio.transmit = commonData; - } - statsPtr = formattedStats.audio.transmit; - } else if (res.stat('audioOutputLevel')) { - if (!formattedStats.audio.receive) { - formattedStats.audio.receive = commonData; - } - statsPtr = formattedStats.audio.receive; - } else if (res.stat('googFrameHeightSent')) { - if (!formattedStats.video.transmit) { - formattedStats.video.transmit = commonData; - } - statsPtr = formattedStats.video.transmit; - } else if (res.stat('googFrameHeightReceived')) { - if (!formattedStats.video.receive) { - formattedStats.video.receive = commonData; - } - statsPtr = formattedStats.video.receive; - } - } else if (res.type === 'VideoBwe') { - if (!formattedStats.video.bw) { - formattedStats.video.bw = {id: res.id}; - } - statsPtr = formattedStats.video.bw; - } - - // Now get only the stats that we are interested in - if (res.names && statsPtr) { - var resNames = res.names() || []; - resNames.forEach(function (name) { - if (CallStats[name]) { - statsPtr[CallStats[name]] = parseFloat(res.stat(name)); - } - }); - // The codec might change during the call and right now we're not keeping track of these changes... - statsPtr.en = res.stat('googCodecName'); - } - }); - - if (includeQosData) { - // First determine how many local candidates we need to stat (audio/video or audio+video) - var count = formattedStats.audio.channelInfo ? 1 : 0; - if (formattedStats.video.channelInfo) { - count++; - } - var parsedLocalSdp; // Save parsed local SDP so we won't have to parse it twice - results.some(function (res) { - if (!res) { - return; - } - if (res.type === 'localcandidate') { - // Get network interface info and IP address from the local candidate - if (audioCandidateId === res.id && formattedStats.audio.channelInfo) { - // Audio - formattedStats.audio.channelInfo.ni = res.stat('networkType'); - parsedLocalSdp = parsedLocalSdp || circuit.sdpParser.parse(pc.localDescription && pc.localDescription.sdp); - formattedStats.audio.channelInfo.candidate = Utils.getIceCandidateInfo(res, parsedLocalSdp, 'audio', res.stat('portNumber')); - return --count <= 0; - } - if (videoCandidateId === res.id && formattedStats.video.channelInfo) { - // Video - formattedStats.video.channelInfo.ni = res.stat('networkType'); - parsedLocalSdp = parsedLocalSdp || circuit.sdpParser.parse(pc.localDescription && pc.localDescription.sdp); - formattedStats.video.channelInfo.candidate = Utils.getIceCandidateInfo(res, parsedLocalSdp, 'video', res.stat('portNumber')); - return --count <= 0; - } - } - }); - } - return formattedStats; - } - - function getPcStats(pc, pcName, startTime, includeQosData) { - return new RtcPromise(function (resolve) { - if (!pc.getStats) { - resolve(); - return; - } - - pc.getStats(function (stats) { - if (!stats) { - logger.warn('[CallStatsHandler]: getStats[' + pcName + '] did not return a response'); - } else { - stats = formatStatsForClientDiagnostics(startTime, stats, pc, pcName, includeQosData); - } - resolve(stats); - }, function (err) { - // Log that getStats for this particular peer connection failed, but don't reject the promise - logger.warn('[CallStatsHandler]: getStats[' + pcName + '] failed with err = ', err); - resolve(); - }); - }); - } - - - function CallStatsHandler(peerConnections) { - if (!Array.isArray(peerConnections)) { - throw new Error('peerConnections must be an array'); - } - - ///////////////////////////////////////////////////////////////////////////// - // Internal Variables - ///////////////////////////////////////////////////////////////////////////// - var LOG_SKIP_COUNTER = 6; // If there's no issues, log stats every 30 seconds - var MAX_VIOLATIONS = 20; // Only log the violations up to this limit - - var _peerConnections = peerConnections.filter(function (pc) { return pc; }); - if (!peerConnections.length) { - throw new Error('Cannot create CallStatsHandler object without at least one peer connection'); - } - - var _options = {}; - - var _rtpStatsDelayUponAnsTimer = null; - var _rtpStatsCollectionTimer = null; - var _rtpStatsCollectionCounter = 0; - - var _savedQualityEstimation; - var _savedCurrentQualityLevel; - var _savedRTPStatsNQ; - var _didReceiveLowQualityLevel; - - var _savedRTPStats = []; - var _savedAudioChannelInfo = []; - var _savedVideoChannelInfo = []; - var _audioIssues = []; - var _videoIssues = []; - var _qualityIssueDetected; - var _qosCollected; - - var _that = this; - - ///////////////////////////////////////////////////////////////////////////// - // Internal Functions - ///////////////////////////////////////////////////////////////////////////// - function getRTPStatsForClientDiagnostics(force) { - var promises = []; - // If QoS has already been collected, we don't need to collect it again - var includeQosData = !_qosCollected; - _qosCollected = _qosCollected || includeQosData; - _peerConnections.forEach(function (p) { - if (!p) { - return; - } - - // Collect stats only if media is connected - if (!force && p.iceConnectionState !== 'completed' && p.iceConnectionState !== 'connected') { - logger.warn('[CallStatsHandler]: Do not collect stats. pc.iceConnectionState = ', p.iceConnectionState); - return; - } - - if (p.mainPeerConnection) { - promises.push(getPcStats(p.mainPeerConnection, 'AUDIO/VIDEO', p.startTime, includeQosData)); - p.groupPeerConnections.forEach(function (groupPc) { - promises.push(getPcStats(groupPc, 'AUX_VIDEO', p.startTime, includeQosData)); - }); - } else { - promises.push(getPcStats(p, 'SCREEN_SHARE', p.startTime, includeQosData)); - } - }); - - return new RtcPromise(function (resolve) { - RtcPromise.all(promises) - .then(function (stats) { - stats = stats && stats.filter(function (s, i) { - if (s) { - // Add the channelInfo if available - if (s.audio && !s.audio.channelInfo && _savedAudioChannelInfo[i]) { - s.audio.channelInfo = _savedAudioChannelInfo[i]; - } - if (s.video && !s.video.channelInfo && _savedVideoChannelInfo[i]) { - s.video.channelInfo = _savedVideoChannelInfo[i]; - } - } - return s; - }); - if (stats && stats.length) { - resolve(stats); - } else { - resolve(); - } - }); - }); - } - - function startCollectingRTPStats() { - if (!_rtpStatsDelayUponAnsTimer && !_rtpStatsCollectionTimer) { - logger.debug('[CallStatsHandler]: Waiting to start collecting RTPstats'); - - _rtpStatsDelayUponAnsTimer = window.setTimeout(function () { - logger.info('[CallStatsHandler]: Start collecting RTPStats'); - _rtpStatsDelayUponAnsTimer = null; - _rtpStatsCollectionCounter = 0; - getFormattedRTPStats(); - _rtpStatsCollectionTimer = window.setInterval(function () { - getFormattedRTPStats(); - }, RtpStatsConfig.COLLECTION_INTERVAL); - }, RtpStatsConfig.DELAY_UPON_ANSWER); - } else { - logger.debug('[CallStatsHandler]: Restart collecting RTPStats'); - _savedQualityEstimation = null; - _savedCurrentQualityLevel = null; - _savedRTPStatsNQ = null; - _didReceiveLowQualityLevel = false; - } - } - - function calculatePLSent(current, savedStats) { - var previousPL = 0, previousPS = 0; - if (savedStats) { - previousPL = savedStats.pl || 0; - previousPS = savedStats.ps || 0; - } - return (current.pl - previousPL) / ((current.ps - previousPS) || -1); - } - - function calculatePLReceived(current, savedStats) { - var previousPL = 0, previousPR = 0; - if (savedStats) { - previousPL = savedStats.pl || 0; - previousPR = savedStats.pr || 0; - } - return (current.pl - previousPL) / ((current.pr - previousPR + current.pl - previousPL) || -1); - } - - function calculateAverageAndMax(current, savedStats, field) { - var sum = field + 'Sum'; - var counter = field + 'Count'; - var max = field + 'Max'; - if (savedStats) { - if (savedStats[sum] !== undefined) { - current[sum] = savedStats[sum] + (current[field] || 0); - current[counter] = ++savedStats[counter]; - } else { - current[sum] = current[field] || 0; - current[counter] = 1; - } - if (savedStats[max] !== undefined) { - current[max] = Math.max(savedStats[max], (current[field] || 0)); - } else { - current[max] = current[field] || 0; - } - } else { - // First sample - current[sum] = current[max] = current[field] || 0; - current[counter] = 1; - } - } - - function logStats(s, issues) { - var log; - - if (s && (s.audio || s.video)) { - // Log stats in a more compact message - log = '[CallStatsHandler]: statsReport: audioRtt=' + (s.audio && s.audio.rtt || 'n/a') + ' videoRtt=' + - (((s.video.receive || s.video.transmit) && s.video && s.video.rtt) || 'n/a'); - var p; - if (s.audio) { - p = s.audio.receive; - if (p) { - log += '\n audioReceive: id=' + p.id + ' pl=' + p.pl + ' pr=' + p.pr + ' or=' + p.or + ' jr=' + p.jr; - } - p = s.audio.transmit; - if (p) { - log += '\n audioTransmit: id=' + p.id + ' pl=' + p.pl + ' ps=' + p.ps + ' os=' + p.os; - } - } - if (s.video) { - p = s.video.receive; - if (p) { - log += '\n videoReceive: id=' + p.id + ' pl=' + p.pl + ' pr=' + p.pr + ' or=' + p.or - + ' fwr=' + p.fwr + ' fhr=' + p.fhr + ' frd=' + p.frd; - } - p = s.video.transmit; - if (p) { - log += '\n videoTransmit: id=' + p.id + ' pl=' + p.pl + ' ps=' + p.ps + ' os=' + p.os - + ' fws=' + p.fws + ' fhs=' + p.fhs + ' frs=' + p.frs; - } - } - logger.debug(log); - } - - if (issues && issues.length) { - var logIssues; - log = '[CallStatsHandler]: statsReport:\n'; - issues.forEach(function (i, idx) { - log += (idx === 0) ? ' Audio issues=' : '\n Video issues='; - var lst = Object.keys(i); - if (lst.length) { - logIssues = true; - lst.forEach(function (l) { - log += ' ' + l + '=' + i[l].value + ' count=' + i[l].counter; - }); - } else { - log += ' none'; - } - }); - logIssues && logger.info(log); - } - } - - function issueCounter(t, stat, value, startTime, currentStats, savedStats) { - var issue = (stat.reportIssues && t[stat.StatText]) || {}; - var viStart = stat.StatText + 'LastViStart'; - var vi = stat.StatText + 'Violations'; - var viCounter = stat.StatText + 'ViCounter'; - // Propagate the violation records from the savedStats to the current - currentStats[viStart] = savedStats && savedStats[viStart]; - currentStats[vi] = savedStats && savedStats[vi]; - currentStats[viCounter] = savedStats && savedStats[viCounter] || 0; - if (value === -1) { - // -1 means stat value is below threshold - if (currentStats[viStart]) { - // There's a violation in progress and now we should mark it as ended - var viRecords = currentStats[vi]; - viRecords = viRecords ? viRecords + ',' : ''; // Add a coma - currentStats[vi] = viRecords + '(' + currentStats[viStart] + ',' + parseInt((Date.now() - startTime) / 1000, 10) + ')'; // Add end time - currentStats[viCounter]++; - delete currentStats[viStart]; // Delete the start time, otherwise it will be recorded again - } - if (issue.reported) { - // Issue already reported, so now we're counting the consecutive good readings (below threshold) - issue.counter = ++issue.counter; - } else if (issue.counter) { - // Issue has occurred in last reading(s) but hasn't been reported yet. So clear it. - delete t[stat.StatText]; - logger.debug('[CallStatsHandler]: issueCounter cleared=', stat.StatText); - } - } else { - if (issue.reported) { - // Reset counter because it should be counting only the consecutive 'good readings' - issue.counter = 0; - issue.value = value; - } else { - if (issue.counter) { - // Issue not reported yet, so increment its counter... - issue.counter = ++issue.counter; - issue.value = value; - } else { - // ... or start counting it - t[stat.StatText] = { - statObj: stat, - counter: 1, - value: value - }; - if (currentStats[viCounter] < MAX_VIOLATIONS) { - // Start recording this violation - currentStats[viStart] = parseInt((Date.now() - startTime) / 1000, 10); - } - } - } - } - } - - function processRTPStats(statsReports) { - if (!statsReports || !statsReports.length) { - return; - } - - var hasIssues = false; - statsReports.forEach(function (statsReport, index) { - - var currentPL; - var send = statsReport.audio && statsReport.audio.transmit; - var rcv = statsReport.audio && statsReport.audio.receive; - var audioIssues = _audioIssues[index] || {}; - var videoIssues = _videoIssues[index] || {}; - var savedStats = _savedRTPStats[index] || {}; - - // 1. Audio stats - // * Packets received - check if we're receiving packets. If not, assume that either we - // lost connection or the other peer stopped responding - if (rcv && savedStats.audio && savedStats.audio.receive && !_options.sendOnlyStream) { - issueCounter(audioIssues, RTPAudioStatType.NO_PACKETS_RECV, rcv.pr > savedStats.audio.receive.pr ? -1 : true, - statsReport.startTime, statsReport.audio, savedStats.audio); - } - - // * Jitter received - var statType = RTPAudioStatType.GOOG_JITTER_RECEIVED; - var statValue; - if (rcv) { - calculateAverageAndMax(rcv, savedStats.audio && savedStats.audio.receive, RTPAudioStatType.GOOG_JITTER_RECEIVED.StatName); - statValue = rcv[statType.StatName]; - issueCounter(audioIssues, statType, statValue && statValue >= statType.Threshold ? statValue : -1, - statsReport.startTime, statsReport.audio, savedStats.audio); - } - - // * Packet loss - statType = RTPAudioStatType.PACKET_LOSS_SEND; - if (send && send.pl && send.ps) { - currentPL = calculatePLSent(send, savedStats.audio && savedStats.audio.transmit); - issueCounter(audioIssues, statType, currentPL >= statType.Threshold ? currentPL : -1, statsReport.startTime, - statsReport.audio, savedStats.audio); - } - statType = RTPAudioStatType.PACKET_LOSS_RECV; - if (rcv && rcv.pl && rcv.pr) { - currentPL = calculatePLReceived(rcv, savedStats.audio && savedStats.audio.receive); - issueCounter(audioIssues, statType, currentPL >= statType.Threshold ? currentPL : -1, statsReport.startTime, - statsReport.audio, savedStats.audio); - } - - // * RTT - if (statsReport.audio && _rtpStatsCollectionCounter > RtpStatsConfig.DELAY_RTT_PROCESSING) { - calculateAverageAndMax(statsReport.audio, savedStats.audio, RTPAudioStatType.GOOG_RTT.StatName); - statType = RTPAudioStatType.GOOG_RTT; - issueCounter(audioIssues, statType, statsReport.audio.rtt >= statType.Threshold ? - statsReport.audio.rtt : -1, statsReport.startTime, statsReport.audio, savedStats.audio); - } - - // * Echo likelihood - statType = RTPAudioStatType.ECHO_LIKELIHOOD; - if (send) { - calculateAverageAndMax(send, savedStats.audio && savedStats.audio.transmit, RTPAudioStatType.ECHO_LIKELIHOOD.StatName); - statValue = send[statType.StatName]; - issueCounter(audioIssues, statType, statValue && statValue >= statType.Threshold ? statValue : -1, - statsReport.startTime, statsReport.audio, savedStats.audio); - } - - // 2. Video stats - send = statsReport.video && statsReport.video.transmit; - rcv = statsReport.video && statsReport.video.receive; - - // * Packet loss - statType = RTPVideoStatType.PACKET_LOSS_SEND; - if (send && send.pl && send.ps) { - currentPL = calculatePLSent(send, savedStats.video && savedStats.video.transmit); - issueCounter(videoIssues, statType, currentPL >= statType.Threshold ? currentPL : -1, statsReport.startTime, - statsReport.video, savedStats.video); - } - statType = RTPVideoStatType.PACKET_LOSS_RECV; - if (rcv && rcv.pl && rcv.pr) { - currentPL = calculatePLReceived(rcv, savedStats.video && savedStats.video.receive); - issueCounter(videoIssues, statType, currentPL >= statType.Threshold ? currentPL : -1, statsReport.startTime, - statsReport.video, savedStats.video); - } - - // * RTT - // This check is necessary because WebRTC sometimes report video stats for audio only calls - if ((send || rcv) && _rtpStatsCollectionCounter > RtpStatsConfig.DELAY_RTT_PROCESSING) { - calculateAverageAndMax(statsReport.video, savedStats.video, RTPAudioStatType.GOOG_RTT.StatName); - statType = RTPVideoStatType.GOOG_RTT; - issueCounter(videoIssues, statType, statsReport.video && statsReport.video.rtt >= statType.Threshold ? - statsReport.video.rtt : -1, statsReport.startTime, statsReport.video, savedStats.video); - } - - // Channel info is collected just once, so save it and make it available for subsequent stats - if (statsReport.audio && statsReport.audio.channelInfo) { - _savedAudioChannelInfo[index] = statsReport.audio.channelInfo; - } - if (statsReport.video && statsReport.video.channelInfo) { - _savedVideoChannelInfo[index] = statsReport.video.channelInfo; - } - _savedRTPStats[index] = statsReport; - _audioIssues[index] = audioIssues; - _videoIssues[index] = videoIssues; - - // Check which issues need to be reported (based on their recurrences) - var reportAudioIssues = reportIssue(audioIssues); - var reportVideoIssues = reportIssue(videoIssues); - if (reportAudioIssues || reportVideoIssues) { - hasIssues = true; - if (!_qualityIssueDetected) { - reportAudioIssues = reportAudioIssues || 'none'; - reportVideoIssues = reportVideoIssues || 'none'; - _qualityIssueDetected = true; - if (reportAudioIssues.noPacketsReceived) { - logger.warn('[CallStatsHandler]: processRTPStats - No packets received from remote peer'); - } else { - logger.warn('[CallStatsHandler]: processRTPStats - Quality issues detected', - [{audio: reportAudioIssues}, {video: reportVideoIssues}]); - _that.onThresholdExceeded && _that.onThresholdExceeded(); - } - } - } - }); - - if (!hasIssues && _qualityIssueDetected) { - // Since there are no issues we can clear the threshold exceeded error - logger.debug('[CallStatsHandler]: processRTPStats - No quality issues detected. Clear threshold exceeded error.'); - _qualityIssueDetected = false; - _that.onThresholdExceeded && _that.onThresholdExceeded(true); - } - - if (_qualityIssueDetected || (_rtpStatsCollectionCounter % LOG_SKIP_COUNTER === 0)) { - _savedRTPStats.forEach(function (statsReport, index) { - logStats(statsReport, [_audioIssues[index], _videoIssues[index]]); - }); - } - _rtpStatsCollectionCounter++; - } - - function getFormattedRTPStats() { - circuit.isElectron && logger.debug('[CallStatsHandler]: Get RTP stats for local call(s)'); - getRTPStatsForClientDiagnostics() - .then(function (stats) { - if (stats) { - circuit.isElectron && logger.debug('[CallStatsHandler]: Process RTP stats for local call(s)'); - processRTPStats(stats); - processNetworkIndication(stats); - } else { - circuit.isElectron && logger.debug('[CallStatsHandler]: No RTP stats collected'); - } - }); - } - - function stopCollectingRTPStats() { - return new RtcPromise(function (resolve) { - getRTPStatsForClientDiagnostics(true) - .then(function (stats) { - resolve(stats); - }); - - if (_rtpStatsDelayUponAnsTimer) { - logger.debug('[CallStatsHandler]: cancel collecting RTPStats '); - window.clearTimeout(_rtpStatsDelayUponAnsTimer); - _rtpStatsDelayUponAnsTimer = null; - } else { - if (_rtpStatsCollectionTimer) { - logger.debug('[CallStatsHandler]: stop collecting RTPStats'); - window.clearInterval(_rtpStatsCollectionTimer); - _rtpStatsCollectionTimer = null; - - _savedQualityEstimation = null; - _savedCurrentQualityLevel = null; - _savedRTPStatsNQ = null; - _didReceiveLowQualityLevel = false; - - if (_savedRTPStats) { - logger.debug('[CallStatsHandler]: savedRTPStats =', _savedRTPStats); - } - _that.onThresholdExceeded && _that.onThresholdExceeded(true); - _qualityIssueDetected = false; - } - } - }); - } - - // Parameter t contains all stats for a particular media (audio or video) - function reportIssue(t) { - var report = null; - var statNames = Object.keys(t); - statNames.forEach(function (name) { - var stat = t[name]; - if (stat.counter >= stat.statObj.RecurrenceThreshold) { - // The number of reoccurrences are over the threshold, report or clear the issue - if (!stat.reported) { - // Issue not reported yet, so report it - stat.reported = true; - stat.counter = 0; - report = report || {}; - report[name] = true; - } else { - // Issue already reported, so clear it - delete t[name]; - logger.debug('[CallStatsHandler]: issueCounter cleared=', name); - } - } else if (stat.reported) { - // We should still report it until it's cleared - report = report || {}; - report[name] = true; - } - }); - return report; - } - - /** - * Network indication will calculate the percentage of packet lost for send and receive line and publish - * the current quality as a qualityLevel to the clients to present it. - * If worse than currently presented quality is detected, it is directly published. - * Better quality needs to be detected twice in a row to get published. This is done to avoid bouncing between - * bad and good quality just because we luckily had an iteration without packet lost in a bad network, - * or missed an RTCP packet. - * - * @param {Array} statsReports an array of statistics reports to be processed - */ - function processNetworkIndication(statsReports) { - if (!statsReports || !statsReports.length) { - logger.debug('[CallStatsHandler]: processNetworkIndication - No stats for network indication.'); - return; - } - - if (!_savedRTPStatsNQ) { - _savedRTPStatsNQ = []; - _savedCurrentQualityLevel = RtpQualityLevel.RTP_QUALITY_HIGH; - _savedQualityEstimation = {noPacketsSeq: 0, qualityLevelSeq: 0}; - _didReceiveLowQualityLevel = false; - } - - statsReports.forEach(function (statsReport, index) { - var currentPLSend = 0, currentPLReceive = 0; - var send = statsReport.audio && statsReport.audio.transmit; - var rcv = statsReport.audio && statsReport.audio.receive; - var isSendingPackets = true; - var isReceivingPackets = true; - var savedStats = _savedRTPStatsNQ[index] || {}; - - // Packet loss audio - if (send && !isNaN(send.pl) && !isNaN(send.ps)) { - currentPLSend = calculatePLSent(send, savedStats.audio && savedStats.audio.transmit) || 0; - if (savedStats.audio && savedStats.audio.transmit && savedStats.audio.transmit.ps) { - isSendingPackets = send.ps !== savedStats.audio.transmit.ps; - } - if (!isSendingPackets) { - logger.warn('[CallStatsHandler]: processNetworkIndication - No packets sent to remote peer'); - } - } - - if (rcv && !isNaN(rcv.pl) && !isNaN(rcv.pr)) { - currentPLReceive = calculatePLReceived(rcv, savedStats.audio && savedStats.audio.receive) || 0; - if (savedStats.audio && savedStats.audio.receive && savedStats.audio.receive.pr) { - isReceivingPackets = rcv.pr !== savedStats.audio.receive.pr; - } - if (!isReceivingPackets && !_options.sendOnlyStream) { - logger.warn('[CallStatsHandler]: processNetworkIndication - No packets received from remote peer'); - } - } - - // Determining quality for this iteration - var tmpQuality = { - currentPlSend: currentPLSend, - currentPlReceive: currentPLReceive - }; - - if (isSendingPackets) { - _savedQualityEstimation.noPacketsSeq = 0; // reset counter - tmpQuality.qualityLevel = RtpQualityLevel.getLevel(Math.max(currentPLSend, currentPLReceive)); - } else { - _savedQualityEstimation.noPacketsSeq++; - if (_savedQualityEstimation.noPacketsSeq > 1) { - tmpQuality.qualityLevel = RtpQualityLevel.RTP_QUALITY_LOW; - _that.onNoOutgoingPackets && _that.onNoOutgoingPackets(); - } else { - tmpQuality.qualityLevel = RtpQualityLevel.RTP_QUALITY_HIGH; // Default - } - } - - // Set qualityEstimation and publish the result - setQualityEstimation(tmpQuality); - - _savedRTPStatsNQ[index] = statsReport; - }); - } - - function setQualityEstimation(qualityEstimation) { - var estimation = _savedQualityEstimation; - // The just determined qualityLevel - estimation.qualityLevel = qualityEstimation.qualityLevel; - // The packetsLost value for send - estimation.currentPlSend = qualityEstimation.currentPlSend; - // The packetsList value for receive - estimation.currentPlReceive = qualityEstimation.currentPlReceive; - - var currentQuality = _savedCurrentQualityLevel; - if (currentQuality !== estimation.qualityLevel.value) { - logger.debug('[CallStatsHandler]: setQualityEstimation - estimation=' + estimation.qualityLevel.value + ' seq=' + estimation.qualityLevelSeq + ' current=' + currentQuality); - } - if (currentQuality < estimation.qualityLevel.value) { - estimation.qualityLevelSeq++; - if (estimation.qualityLevelSeq > 1) { - _savedCurrentQualityLevel = estimation.qualityLevel.value; - estimation.qualityLevelSeq = 0; // reset counter - } - } else { - _savedCurrentQualityLevel = estimation.qualityLevel.value; - estimation.qualityLevelSeq = 0; // reset counter - } - - if (currentQuality !== _savedCurrentQualityLevel) { - // Send event only if the quality has changed - var firstTimeLowQuality = currentQuality === 1 && !_didReceiveLowQualityLevel; - if (firstTimeLowQuality) { - estimation.firstTimeLowQuality = firstTimeLowQuality; - _didReceiveLowQualityLevel = firstTimeLowQuality; - } - _that.onNetworkQuality && _that.onNetworkQuality(estimation); - } - } - - ///////////////////////////////////////////////////////////////////////////// - // Public interfaces - ///////////////////////////////////////////////////////////////////////////// - this.start = function () { - startCollectingRTPStats(); - }; - - this.stop = function () { - return stopCollectingRTPStats(); - }; - - this.onThresholdExceeded = null; - this.onNoOutgoingPackets = null; - this.onNetworkQuality = null; - - this.setOptions = function (options) { - if (options) { - _options = options; - } - }; - - this.getLastSavedStats = function () { - return _savedRTPStats; - }; - - } - - CallStatsHandler.overridePromise = function ($q) { - RtcPromise = $q; - }; - - // Exports - circuit.CallStats = CallStats; - circuit.CallStatsHandler = CallStatsHandler; - circuit.RtpStatsConfig = RtpStatsConfig; - - return circuit; -})(Circuit); - -// Define external globals for JSHint -/*global window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Constants = circuit.Constants; - var IceCandidate = circuit.IceCandidate; - var logger = circuit.logger; - // Used inline: var RtcPeerConnections = circuit.RtcPeerConnections; - var ScreenSharingController = circuit.ScreenSharingController; - var sdpParser = circuit.sdpParser; - var Utils = circuit.Utils; - var WebRTCAdapter = circuit.WebRTCAdapter; - var CallStatsHandler = circuit.CallStatsHandler; - - var SdpStatus = Object.freeze({ - None: 'None', - AnswerPending: 'Answer-Pending', - AnswerSent: 'Answer-Sent', - AnswerApplied: 'Answer-Applied', - OfferPending: 'Offer-Pending', - OfferSent: 'Offer-Sent', - OfferReceived: 'Offer-Received', - Connected: 'Connected', - PrAnswerReceived: 'PrAnswer-Received', - AnswerReceived: 'Answer-Received', - Disconnected: 'Disconnected' - }); - - // Default timeout for waiting for candidates when Trickle ICE is not enabled - var DEFAULT_CANDIDATES_TIMEOUT = 2000; - var DEFAULT_TRICKLE_ICE_TIMEOUT = 200; // Default timeout waiting for candidates when Trickle ICE is enabled - var DEFAULT_ENABLE_AUDIO_AGC = true; - - // Default upscale factor for remote screen share video - var DEFAULT_UPSCALE_FACTOR = 1.5; - - // getUserMedia errors - var GUM_CONSTRAINT_ERROR = 'ConstraintNotSatisfiedError'; - var GUM_PERMISSION_DENIED_ERROR = 'PermissionDeniedError'; - - // Timeout waiting for relay candidates over a TURN TCP/TLS connection - var DESKTOP_RELAY_CANDIDATES_TIMEOUT = 5000; - - // Timeout before raising onIceDisconnected event - var ICE_DISCONNECT_DELAY = 1000; - - var DEFAULT_SDP_PARAMS = { - audio: { - maxplaybackrate: 24000, - 'sprop-maxcapturerate': 24000, - maxaveragebitrate: 16000, - usedtx: 1 - }, - video: { - maxBw: 512 - }, - hdVideo: { - maxBw: 2000 - }, - screenShare: { - maxBw: 300, - maxFrameRate: 5, - minFrameRate: 3 - }, - rtcpMuxPolicy: 'negotiate', - preferVp9: false - }; - - var DEFAULT_XGOOGLE = { - video: { - minBitRate: 60 - }, - hdVideo: { - // [CMR] -> Tweaking BW settings based on Chromecast reverse engineering SDP - // https://www.bountysource.com/issues/877114-chromecast-video-output - startBitRate: 500, - minBitRate: 1000, - maxBitRate: 2000, - maxQuantization: 56 - }, - screenShare: {} - }; - - var LOCAL_STREAMS = { - AUDIO_VIDEO: 0, - DESKTOP: 1 - }; - - ///////////////////////////////////////////////////////////////////////////////////////// - // RtcSessionController - // - // This "class" encapsulates the WebRTC API calls and provides the common functionality - // that is required by the UC Client Refresh and Ansible projects. - // - // In a high level, the RtcSessionController provides the following functionality: - // - Gets and maintains the MediaStream object - // - Encapsulates the calls to the RTCPeerConnection object - // - Supports Trickle ICE (can be enabled or disabled) - // - Handles Hold and Retrieve requests and maintains the holding/held status - // - Handles Mute and Unmute requests and maintains current status - // - Handles Add Video and Remove Video requests - // - Handles media renegotiation requests - // - Normalizes the local and remote session descriptions - // - Keeps the local video URL - // - Keeps the remote audio or video URL - // - Provides an eventing interface similar to RTCPeerConnection - // - ///////////////////////////////////////////////////////////////////////////////////////// - function RtcSessionController(options) { - options = options || {}; - - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - var GUM_TIMEOUT = 120000; // Timeout waiting for mic or camera to respond - var CONNECTED_TIMEOUT = 15000; // Timeout waiting for connection state 'connected' on all peer connections - - var MOCK_CONNECT_TIMEOUT = 1000; // Timeout to fake connection state 'connected' for mock scenarios - - // Additional peer connections count to receive group call videos - // [Pastro] Now that we have the VIDEO_ACTIVE_SPEAKER event we are limiting the - // max number of simultaneous video streams to 3 (2 extra + 1). We started with 5, but - // there have been several Chrome crashes in large group video call scenarios so - // we have decided to reduce the number of streams to see if that helps. - // [Eloy] We set the number of extra channels to always be 2 to fix an error scenario where the order of - // video m-lines changes if we add extra channels dynamically (ANS-17366). So, by setting the - // MIN and MAX_VIDEO_EXTRA_CHANNELS to be the same, we turn off the dynamic channel allocation, eliminating the problem. - // The dynamic channel allocation will be enabled again once we have an agreement with the server side colleagues on the - // final solution. - var MIN_VIDEO_EXTRA_CHANNELS = 2; - var MAX_VIDEO_EXTRA_CHANNELS = 2; - - var LOCAL_AUDIO_VIDEO = 0; - var LOCAL_SCREEN_SHARE = 1; - - var SCREEN_SHARE_MEDIA_ID = 'video_screen'; - - var DIRECT_CALL_SDP_M_LINE_INDEX = { - audio: 0, - video: 1, - screen: 2 - }; - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var _that = this; - - var _callId = options.callId || ''; - var _isDirectCall = !!options.isDirectCall; - - // Workaround: ANS-14579 - We need this knowledge for SDP handling. This will be removed once SBC is fixed. - var _isTelephonyCall = !!options.isTelephonyCall; - - // Number of extra channels must be between 0 and MULTI_VIDE_EXTRA_CHANNELS - var _numberOfExtraVideoChannels = 0; - - // Local Media Streams - // Index 0 contains the Audio+Video stream - // Index 1 contains the Desktop (screen-sharing) stream - var _localStreams = [null, null]; - - // RTCPeerConnections instance (audio/video streams) - var _pc = null; - // WebRTCAdapter.PeerConnection instance (screenshare stream) - var _desktopPc = null; - // Collect call statistics - var _callStats = null; - - // In case this client only supports 1 peer connection and we receive an extra m-line in the invite, - // we have to disable and save it here (we need it when sending the answer SDP back) - var _disabledVideoMline = null; - - var _turnUris = []; - var _turnCredentials = {}; - - // Flag to indicate that a media renegotiation is in progress - var _renegotiationInProgress = false; - var _renegotiationStartedLocally = false; - - // Flag to indicate that a change in the Screen Share desktop media Stream is in progress - var _selectNewDesktopMediaInProgress = false; - - // Callback function that must be invoked when the media renegotiation is completed - var _renegotiationCb = null; - - // The previous MediaStreams and PC instances that are saved during media renegotiations - var _oldLocalStreams = null; - var _oldPC = null; - var _oldDesktopPC = null; - var _oldCallStats = null; - - // Local and Remote object URLs - var _localVideoUrl = null; - var _localDesktopUrl = null; - - var _remoteVideoUrls = []; - var _remoteAudioUrl = null; - - // State information - var _sdpStatus = SdpStatus.None; - - // The negotiated media types. This variable only tracks audio & video since we - // cannot differentiate video and desktop (screen share) from the SDP negotiation. - var _activeMediaType = {audio: false, video: false}; - var _oldActiveMediaType = null; - - var _held = false; - var _holding = false; - var _holdInProgress = false; - var _retrieveInProgress = false; - // all participants in a large conference are muted on start - var _isMuted = !!options.isLargeConference; - - // Constraints - var _mediaConstraints = {audio: true, video: false, hdVideo: false, desktop: false}; - var _oldMediaConstraints = null; - - // The folowing variables enable/disable the support of Trickle ICE for SDPs generated by the WebRTC client. - // Trickle ICE for Offer is enabled by default for all non telephony calls. - var _trickleIceForOffer = !_isTelephonyCall && !RtcSessionController.disableTrickleIce; - // Trickle ICE for Answer is enabled if the Offer indicates support for Trickle ICE. - var _trickleIceForAnswer = false; - - // Timer to wait for candidates when Trickle ICE is not enabled - var _candidatesTimer = null; - // Timer to wait for connection state to change to connected - var _connectedTimer = null; - - // The following variable is used to send DTMF digits using the RTCDTMFSender interface. - var _dtmfSender = null; - - // Flag that indicates whether the session has been warmed-up. - var _warmedUp = false; - // The _warmedUpSdp is the SDP that has been passed as input to the warmup function - var _warmedUpSdp = null; - - var _isMockedCall = false; - - // Flag to control whether or not to disable remote video streams - var _remoteVideoDisabled = false; - - var _remoteStreams = []; - - var _largeConference = !!options.isLargeConference; - - // For an ATC pull call scenario the first received SDP Offer is a dummy Offer which - // will not be used for the actual connection. In this case we should not waste time - // collecting candidates from a TURN server. - var _ignoreTurnOnNextPC = !!options.isAtcPullCall; - - var _sentIceCandidates = []; - var _pendingRemoteSdp = null; - var _pendingChangeMedia = null; - var _sessionTerminated = false; - - // Event handler registered with the Extension to assure that we only - // clean up the handler of this RTCSessionController and not one of a - // concurrent instance (e.g. when moving to another conversation - ANS-36268). - var _screenShareEventHandler = null; - - // Flag used to indicate that the new SDP Offer/Answer exchange will be ignored, so we - // don't need to waste time waiting for ICE candidates. - var _ignoreNextConnection = false; - - ///////////////////////////////////////////////////////////////////////////// - // Media Capture and Streams - //////////////////////////////////////////////////////////////////////////// - function onInactiveStream(stream, isDesktop) { - if (_isMockedCall) { - // Ignore event for mock calls - return; - } - var pc = isDesktop && !_isDirectCall ? _desktopPc : _pc; - var localStreams = pc ? pc.getLocalStreams() : []; - if (localStreams.indexOf(stream) >= 0) { - logger.warn('[RtcSessionController]: Local ' + (isDesktop ? 'Desktop' : 'Audio/Video') + ' Media Stream has ended unexpectedly'); - sendLocalStreamEnded(isDesktop); - } - } - - function chooseDesktopAndGetUserMedia(successCb, errorCb, screenConstraint) { - // Check if the MediaStream object has already been provided - if (screenConstraint.mediaStream) { - var stream = screenConstraint.mediaStream; - stream.oninactive = onInactiveStream.bind(null, stream, true); - - setLocalStream(stream, LOCAL_SCREEN_SHARE); - successCb(); - } else if (screenConstraint.streamId || screenConstraint.screenType) { - getUserMediaDesktop(successCb, errorCb, screenConstraint); - } else { - // Fallback to ScreenSharingController (used by VDI) - _screenShareEventHandler = ScreenSharingController.getScreen(function (data) { - if (data && data.streamId) { - screenConstraint.streamId = data.streamId; - } - getUserMediaDesktop(successCb, errorCb, screenConstraint); - }, errorCb); - } - } - - function getUserMedia(successCb, errorCb, screenConstraint) { - logger.debug('[RtcSessionController]: Get User Media for ', _mediaConstraints); - screenConstraint = screenConstraint || {}; - if (_mediaConstraints.audio || _mediaConstraints.video) { - getUserMediaAudioVideo(function () { - if (_mediaConstraints.desktop && (window.navigator.platform !== 'iOS') && (window.navigator.platform !== 'dotnet')) { - if (_localStreams[LOCAL_SCREEN_SHARE]) { - // There's a screen share stream already available - screenConstraint.mediaStream = _localStreams[LOCAL_SCREEN_SHARE]; - chooseDesktopAndGetUserMedia(successCb, errorCb, screenConstraint); - } else if (_renegotiationInProgress && _oldMediaConstraints.desktop && !_selectNewDesktopMediaInProgress) { - setLocalStream(_oldLocalStreams[LOCAL_SCREEN_SHARE], LOCAL_SCREEN_SHARE); - successCb(); - } else { - chooseDesktopAndGetUserMedia(successCb, errorCb, screenConstraint); - //reset the flag, as soon as we choose desktop media - _selectNewDesktopMediaInProgress = false; - } - } else { - // Make sure there is no Desktop (i.e. screen-share) stream - // and then invoke the success callback. - setLocalStream(null, LOCAL_SCREEN_SHARE); - successCb(); - } - }, errorCb); - } else if (_mediaConstraints.desktop) { - // Make sure there is no AV stream and then get the Desktop stream - if (_renegotiationInProgress && _oldMediaConstraints.desktop && !_selectNewDesktopMediaInProgress) { - screenConstraint.mediaStream = _oldLocalStreams[LOCAL_SCREEN_SHARE]; - } - chooseDesktopAndGetUserMedia(successCb, errorCb, screenConstraint); - } else { - window.setTimeout(successCb, 0); - } - } - - function getUserMediaAudioVideo(successCb, errorCb) { - var reuseLocalStream = function () { - // NOTE: 1. The check for IE can be removed once Temasys allows getting a - // second media stream for the same camera (ticket #1519). - // 2. FF doesn't call gerUserMedia callback if the browser is not focused (bug #1195654 on bugzilla). - // Reuse stream when screensharing is switching off to prevent errors after the timeout expires. - var streamCanBeUsed = !_renegotiationStartedLocally || WebRTCAdapter.browser === 'msie' || - (WebRTCAdapter.browser === 'firefox' && !_mediaConstraints.desktop && _oldMediaConstraints.desktop); - - return _renegotiationInProgress && streamCanBeUsed && - (WebRTCAdapter.browser !== 'android') && - (_mediaConstraints.audio === _oldMediaConstraints.audio) && - (_mediaConstraints.video === _oldMediaConstraints.video) && - (_mediaConstraints.hdVideo === _oldMediaConstraints.hdVideo); - }; - - logger.debug('[RtcSessionController]: getUserMediaAudioVideo()'); - var gumTimeout = null; - try { - var constraints = { - audio: false, - video: false - }; - - // Get the up-to-date list of devices that are plugged in and select one based on the list of - // devices previously selected by the user - WebRTCAdapter.getMediaSources(function (audioSources, videoSources, audioOutputDevices) { - logger.debug('[RtcSessionController]: Retrieved media sources'); - if (_mediaConstraints.audio) { - var source = Utils.selectMediaDevice(audioSources, RtcSessionController.recordingDevices); - var playback = Utils.selectMediaDevice(audioOutputDevices, RtcSessionController.playbackDevices); - constraints.audio = WebRTCAdapter.getAudioOptions({ - enableAudioAGC: RtcSessionController.enableAudioAGC, - sourceId: source && source.id, - playbackDeviceId: playback && playback.id - }); - } - - var fullHD = false, regularHD = false; - var videoDevice; - if (_mediaConstraints.video) { - fullHD = _mediaConstraints.hdVideo; - - videoDevice = Utils.selectMediaDevice(videoSources, RtcSessionController.videoDevices); - constraints.video = WebRTCAdapter.getVideoOptions({ - sourceId: videoDevice && videoDevice.id, - hdVideo: fullHD ? '1080p' : '' - }); - } else if (_mediaConstraints.desktop && Utils.isMobile()) { - // We need to use the same getUserMedia to get the audio and screen share for the mobile clients, - // so use the video constraints for the screen share - constraints.video = WebRTCAdapter.getDesktopOptions(); - } - - if (reuseLocalStream()) { - setLocalStream(_oldLocalStreams[LOCAL_AUDIO_VIDEO], LOCAL_AUDIO_VIDEO); - successCb(); - } else { - gumTimeout = window.setTimeout(function () { - gumTimeout = null; - logger.warn('[RtcSessionController]: Failed to access local media: device unresponsive'); - errorCb('res_MediaInputDevicesUnresponsive'); - }, GUM_TIMEOUT); - - // Stop local video if has to change the HDVideo - if (_renegotiationInProgress && - _mediaConstraints.video && - _oldMediaConstraints.video && - _mediaConstraints.hdVideo !== _oldMediaConstraints.hdVideo) { - WebRTCAdapter.stopLocalVideoTrack(_oldLocalStreams[LOCAL_AUDIO_VIDEO]); - } - - var getLocalMedia = function () { - logger.info('[RtcSessionController]: Calling getUserMedia - constraints = ', constraints); - WebRTCAdapter.getUserMedia(constraints, function (stream) { - logger.info('[RtcSessionController]: User has granted access to local media'); - logger.debug('[RtcSessionController]: Local MediaStream: ', stream.id); - - if (gumTimeout) { - window.clearTimeout(gumTimeout); - gumTimeout = null; - } else { - logger.info('[RtcSessionController]: GUM time out already fired'); - stopStream(stream); - return; - } - if (_sessionTerminated) { - logger.info('[RtcSessionController]: Session already terminated. Stop audio/video MediaStream: ', stream.id); - stopStream(stream); - return; - } - stream.oninactive = onInactiveStream.bind(null, stream, false); - - if (_mediaConstraints.video && !stream.getVideoTracks().length) { - if (_renegotiationInProgress && !_oldMediaConstraints.video) { - errorCb('res_CameraAccessRestricted'); - return; - } - _mediaConstraints.video = false; - logger.info('[RtcSessionController]: Failed to get access to video stream. Fallback to audio-only.'); - sendAsyncWarning('res_AddVideoFailed'); - logger.info('[RtcSessionController]: New getUserMedia - constraints = ', _mediaConstraints); - } - setLocalStream(stream, LOCAL_AUDIO_VIDEO); - successCb(); - }, function (error) { - var errorName = (error && error.name) || 'Unspecified'; - if ((fullHD || regularHD) && errorName === GUM_CONSTRAINT_ERROR) { - logger.warn('[RtcSessionController]: Failed to access local media with HD constraints: ', constraints.video); - regularHD = fullHD; - fullHD = false; - constraints.video = WebRTCAdapter.getVideoOptions({ - sourceId: videoDevice && videoDevice.id, - hdVideo: regularHD ? '720p' : '' - }); - getLocalMedia(); - return; - } - logger.warn('[RtcSessionController]: Failed to access local media: ', errorName); - if (gumTimeout) { - window.clearTimeout(gumTimeout); - gumTimeout = null; - } - var strError; - if (errorName.indexOf(GUM_PERMISSION_DENIED_ERROR) !== -1) { - strError = _mediaConstraints.video ? 'res_AccessToMediaInputDevicesFailedPermissionDenied' : 'res_AccessToAudioInputDeviceFailedPermissionDenied'; - } else { - strError = _mediaConstraints.video ? 'res_AccessToMediaInputDevicesFailed' : 'res_AccessToAudioInputDeviceFailed'; - } - - errorCb(strError); - }); - }; - - getLocalMedia(); - - logger.debug('[RtcSessionController]: Requested access to Local Media'); - } - }); - - } catch (e) { - logger.error('[RtcSessionController]: Exception while handling getUserMedia: ', e); - if (gumTimeout) { - window.clearTimeout(gumTimeout); - gumTimeout = null; - } - errorCb(_mediaConstraints.video ? 'res_AccessToMediaInputDevicesFailed' : 'res_AccessToAudioInputDeviceFailed'); - } - } - - function getUserMediaDesktop(successCb, errorCb, screenShareParams) { - logger.debug('[RtcSessionController]: getUserMediaDesktop()'); - try { - var constraints = {audio: false}; - var screenConstraint = { - frameRate: { - min: RtcSessionController.sdpParameters.screenShare.minFrameRate, - max: RtcSessionController.sdpParameters.screenShare.maxFrameRate - } - }; - constraints.video = WebRTCAdapter.getDesktopOptions(screenShareParams.streamId || screenShareParams.screenType, screenConstraint); - - logger.info('[RtcSessionController]: Calling getUserMedia - constraints = ', constraints); - - WebRTCAdapter.getUserMedia(constraints, function (stream) { - logger.info('[RtcSessionController]: User has granted access for screen capture. Stream Id:', stream.id); - - if (_sessionTerminated) { - logger.info('[RtcSessionController]: Session already terminated. Stop desktop MediaStream: ', stream.id); - stopStream(stream); - return; - } - - sendScreenSharePointerStatus(screenShareParams.pointer); - - stream.oninactive = onInactiveStream.bind(null, stream, true); - - setLocalStream(stream, LOCAL_SCREEN_SHARE); - successCb(); - }, function (error) { - var errorName = (error && error.name) || 'Unspecified'; - logger.warn('[RtcSessionController]: Failed to access local media: ', errorName); - - // If getUserMedia returns PermissionDeniedError/SecurityError, assume that user has cancelled screenshare, - // Error message was replaced with NotAllowedError in FF >= 49. - var browser = Utils.getBrowserInfo(); - if ((browser.msie && errorName === 'PermissionDeniedError') || - (browser.firefox && (errorName === 'SecurityError' || errorName === 'NotAllowedError'))) { - errorCb(Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED); - } else { - errorCb('res_AccessToDesktopFailed'); - } - }); - logger.debug('[RtcSessionController]: Requested access for screen capture'); - } catch (e) { - logger.error('[RtcSessionController]: Exception while handling getUserMedia: ', e); - errorCb('res_AccessToDesktopFailed'); - } - } - - ///////////////////////////////////////////////////////////////////////////// - // WebRTC 1.0: Real-time Communication Between Browsers - ///////////////////////////////////////////////////////////////////////////// - function createPeerConnection() { - if (_pc) { - // The RTCPeerConnection instance has already been created. - return true; - } - - var config = {}; - - if (_isTelephonyCall) { - // RTCP MUX support is still being added to the SBC, so we need to negotiate it (https://groups.google.com/forum/m/#!topic/discuss-webrtc/eM57DEy89MY) - config.rtcpMuxPolicy = RtcSessionController.sdpParameters.rtcpMuxPolicy; // Configurable (default is negotiate) - } - - if (_turnUris.length && !_ignoreTurnOnNextPC) { - // WORKAROUND!!! - // Firefox suppors TURN TLS in version 53 and above, but throws exception for URLs with 'turns' scheme - // and IP address instead of hostname. To avoid this just remove them from list. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1056934 - if (WebRTCAdapter.browser === 'firefox') { - _turnUris = _turnUris.filter(function (uri) { - return !/turns:\d{1,3}(\.\d{1,3}){3}/.test(uri); - }); - } - config.iceServers = [{ - urls: _turnUris, - credential: _turnCredentials.password, - username: _turnCredentials.username - }]; - if (RtcSessionController.allowOnlyRelayCandidates) { - config.iceTransportPolicy = 'relay'; - } - } else { - config.iceServers = []; - } - var connectionBypassed = !!_ignoreTurnOnNextPC; - _ignoreTurnOnNextPC = false; - - logger.debug('[RtcSessionController]: Creating RtcPeerConnections. config: ', config); - - var pcConstraints = { - optional: [ - {DtlsSrtpKeyAgreement: true}, - // Improved bandwidth calculation - {googImprovedWifiBwe: true}, - // These are useful for very detailed (HD) video - {googHighBitrate: true}, - {googVeryHighBitrate: true}, - // Enable DSCP support - {googDscp: true} - ] - }; - logger.debug('[RtcSessionController]: Creating peer connections. constraints:', pcConstraints); - - var options = { - extraVideoChannels: _remoteVideoDisabled ? 0 : _numberOfExtraVideoChannels - }; - logger.debug('[RtcSessionController]: Creating peer connections. options:', options); - - try { - _remoteStreams = []; - // Use dynamic circuit object for unit tests to work. - _pc = new circuit.RtcPeerConnections(config, pcConstraints, options); - _pc.connectionBypassed = connectionBypassed; - logger.debug('[RtcSessionController]: Created audio/video peer connections. Connection bypassed: ', connectionBypassed); - - // Create desktop peer connection only if: - // Feature is not disabled and... - // ...it's not iOS client and... - // ..it's a group call - if (!RtcSessionController.disableDesktopPc && (window.navigator.platform !== 'iOS') && (window.navigator.platform !== 'dotnet') && !_isDirectCall) { - config.iceCandidatePoolSize = 1; // Pre-allocate ICE candidates for 1 media type (video) - _desktopPc = new WebRTCAdapter.PeerConnection(config, pcConstraints); - logger.debug('[RtcSessionController]: Created screenshare peer connection'); - } - } catch (e) { - _pc = null; - _desktopPc = null; - logger.error('[RtcSessionController]: Failed to create peer connections.', e); - return false; - } - - _callStats = new CallStatsHandler([_pc, _desktopPc]); - registerPcEvtHandlers(_pc, _callStats); - registerPcEvtHandlers(_desktopPc); - return true; - } - - function registerPcEvtHandlers(pc, stats) { - if (pc) { - pc.onicecandidate = handleIceCandidate.bind(null, pc); - pc.oniceconnectionstatechange = handleConnectionStateChange.bind(null, pc); - pc.onaddstream = handleAddStream.bind(null, pc); - pc.onremovestream = handleRemoveStream.bind(null, pc); - } - if (stats) { - stats.onThresholdExceeded = function (cleared) { sendEvent(_that.onStatsThresholdExceeded, {cleared: cleared}); }; - stats.onNoOutgoingPackets = function () { sendEvent(_that.onStatsNoOutgoingPackets, {}); }; - stats.onNetworkQuality = function (quality) { sendEvent(_that.onNetworkQuality, {quality: quality}); }; - } - } - - function unregisterPcEvtHandlers(pc, stats) { - if (pc) { - pc.onicecandidate = null; - pc.oniceconnectionstatechange = null; - pc.onaddstream = null; - pc.onremovestream = null; - } - if (stats) { - stats.onThresholdExceeded = null; - stats.onNoOutgoingPackets = null; - stats.onNetworkQuality = null; - } - } - - function handleIceCandidate(pc, event) { - if (pc !== _pc && pc !== _desktopPc) { - // Event is for old peer connection. We should never get here, but... - logWarning('Received icecandidate event for old PC'); - return; - } - - // Avoid calling pc.localDescription since this makes a call to native code for the mobile clients - var localDescription; - - if (event.candidate) { - var candidate = new IceCandidate(event.candidate.candidate); - if (candidate.transport !== 'udp') { - logDebug('Ignore non UDP candidate'); - return; - } - - logDebug('New ICE candidate: ', event.candidate); - // Only access pc.localDescription for Trickle ICE scenarios - if (useTrickleIce()) { - localDescription = pc.localDescription; - if (!useTrickleIce(localDescription.type)) { - return; - } - - var data = { - candidate: event.candidate.candidate, - sdpMid: event.candidate.sdpMid, - sdpMLineIndex: event.candidate.sdpMLineIndex - }; - if (checkRelayCandidate(data)) { - if (_sdpStatus !== SdpStatus.None && _sdpStatus !== SdpStatus.OfferPending && _sdpStatus !== SdpStatus.AnswerPending) { - collectCandidate(data); - } else { - logDebug('Ignore ICE candidate in state ', _sdpStatus); - } - } - } - } else { - logDebug('End of candidates. Audio/video PC:', pc === _pc); - pc.endOfCandidates = true; // Mark this peer connection as finished and... - // ... only proceed if all peer connections are done - if (isEndOfCandidates()) { - localDescription = pc.localDescription; - if (!localDescription) { - logError('End of candidates, but cannot access pc.localDescription'); - return; - } - - if (useTrickleIce(localDescription.type)) { - if (_sdpStatus !== SdpStatus.None && _sdpStatus !== SdpStatus.OfferPending && _sdpStatus !== SdpStatus.AnswerPending) { - sendEndOfCandidates(); - } else { - sendLocalDescription(localDescription); - } - } else { - sendLocalDescription(localDescription); - } - } - } - if (_candidatesTimer && useTrickleIce() && (_sdpStatus === SdpStatus.OfferPending || _sdpStatus === SdpStatus.AnswerPending)) { - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // When collecting host ICE candidates takes too long the client waits additionaly for the first ICE - // candidate before to send the local session description. - localDescription = pc.localDescription; - sendLocalDescription(localDescription); - } - } - - function handleConnectionStateChange(pc) { - if (pc !== _pc && pc !== _desktopPc) { - // Event is for old peer connection. We should never get here, but... - logWarning('Received iceconnectionstatechange event for old PC'); - return; - } - - var pcType = (pc === _pc ? 'audio/video' : 'desktop'); - logDebug('Connection state change (' + pcType + '): ', pc.iceConnectionState); - - if (_sdpStatus === SdpStatus.Connected) { - switch (pc.iceConnectionState) { - case 'connected': - case 'completed': - sendIceConnected(pc, pcType); - break; - case 'disconnected': - if (!pc.connectionBypassed) { - sendIceDisconnected(pc, pcType); - } - break; - case 'failed': - if (!pc.connectionBypassed) { - setSdpStatus(SdpStatus.Disconnected); // Mark it as disconnected, so we handle this failure only once - logError('[RtcSessionController]: ICE connection has failed'); - handleFailure('res_CallMediaDisconnected'); - } - break; - } - } else if (_sdpStatus === SdpStatus.AnswerReceived || - _sdpStatus === SdpStatus.PrAnswerReceived || - _sdpStatus === SdpStatus.AnswerApplied || - _sdpStatus === SdpStatus.AnswerSent) { - switch (pc.iceConnectionState) { - case 'connected': - case 'completed': - pc.connected = true; - if (isPcConnected()) { - if (_connectedTimer) { - window.clearTimeout(_connectedTimer); - _connectedTimer = null; - } - setSdpStatus(SdpStatus.Connected); - sendIceConnected(pc, pcType); - } - break; - case 'failed': - if (_connectedTimer) { - window.clearTimeout(_connectedTimer); - _connectedTimer = null; - } - handleFailure('res_CallMediaFailed'); - break; - } - } - } - - function handleAddStream(pc, event) { - logDebug('Added Remote Stream'); - if (_renegotiationInProgress) { - // Ignore added streams while renegotiation is in progress. - // The remote streams will be added once the renegotiation is successful. - logDebug('Renegotiation is in progress. Ignore AddStream event.'); - return; - } - putRemoteObjectURLs([event.stream]); - sendRemoteStreamUpdated(); - } - - function handleRemoveStream(pc, event) { - logWarning('Removed Remote Stream'); - //remove URL associated with this stream - deleteRemoteObjectURL(event.stream); - sendRemoteStreamUpdated(); - } - - function handleDTMFToneChange(pc, event) { - logDebug('RTCDTMFSender - ontonechange: tone =', event.tone); - sendDTMFToneChange(event.tone); - } - - ///////////////////////////////////////////////////////////////////////////// - // Event Senders - ///////////////////////////////////////////////////////////////////////////// - function sendEvent(eventHandler, event) { - if (typeof eventHandler === 'function') { - try { - eventHandler(event || {}); - } catch (e) { - logger.error(e); - } - } - } - - function sendIceCandidates(candidates) { - if (candidates) { - var endOfCandidates = false; - candidates = candidates.filter(function (data) { - if (data.candidate.startsWith('a=end-of-candidates')) { - endOfCandidates = true; - return true; - } - var candidate = data.candidate; - var found = _sentIceCandidates.some(function (c) { return c.equals(candidate); }); - if (found) { - logWarning('Ignore duplicate ICE candidate: ', data.candidate); - } else { - _sentIceCandidates.push(new IceCandidate(candidate)); - } - return !found; - }); - if (endOfCandidates) { - _sentIceCandidates = []; - } - if (candidates.length > 0) { - var origin = getSdpOrigin(); - // Invoke onIceCandidate event handler - logger.debug('[RtcSessionController]: Send IceCandidate event'); - sendEvent(_that.onIceCandidate, {origin: origin, candidates: candidates, endOfCandidates: endOfCandidates}); - waitConnectedState(); - } - } - } - - function sendIceConnected(pc, pcType) { - if (pc.iceDisconnectTimeout) { - window.clearTimeout(pc.iceDisconnectTimeout); - delete pc.iceDisconnectTimeout; - } - logger.debug('[RtcSessionController]: send IceConnected event pcType:', pcType); - sendEvent(_that.onIceConnected, {pcType: pcType}); - } - - function sendIceDisconnected(pc, pcType) { - if (pc.iceDisconnectTimeout) { - return; - } - - // Delay the IceDisconnect event for transient cases - pc.iceDisconnectTimeout = window.setTimeout(function () { - delete pc.iceDisconnectTimeout; - if (pc.iceConnectionState === 'disconnected') { - logger.debug('[RtcSessionController]: send IceDisconnected event pcType:', pcType); - sendEvent(_that.onIceDisconnected, {pcType: pcType}); - } - }, ICE_DISCONNECT_DELAY); - } - - - function sendEndOfCandidates() { - var pc = _pc || _desktopPc; - var candidates = []; - if (pc.pendingCandidates) { - logger.info('[RtcSessionController]: Send all pending candidates.'); - candidates = pc.pendingCandidates; - delete pc.pendingCandidates; - } - if (pc.collectCandidatesTimeout) { - window.clearTimeout(pc.collectCandidatesTimeout); - delete pc.collectCandidatesTimeout; - } - if (pc.queuedCandidates) { - logger.info('[RtcSessionController]: There was no relay candidate for TURN over TCP/TLS. Send all queued candidates.'); - Array.prototype.push.apply(candidates, pc.queuedCandidates); - delete pc.queuedCandidates; - } - if (pc.relayRtpCandidateTimeout) { - window.clearTimeout(pc.relayRtpCandidateTimeout); - delete pc.relayRtpCandidateTimeout; - } - candidates.push({candidate: 'a=end-of-candidates\r\n'}); - sendIceCandidates(candidates); - } - - function sendLocalDescription(localDescription) { - if (_candidatesTimer) { - window.clearTimeout(_candidatesTimer); - _candidatesTimer = null; - } - - if (localDescription.type === 'offer') { - if (_sdpStatus === SdpStatus.OfferPending) { - sendSessionDescription(SdpStatus.OfferSent); - } - } else { - if (_sdpStatus === SdpStatus.AnswerPending) { - sendSessionDescription(SdpStatus.AnswerSent); - } - } - } - - function sendSessionDescription(newStatus) { - // Combine all SDPs into one - var sdp = combineDescriptions(); - if (window.navigator.platform === 'iOS' && sdp) { - logger.debug('[RtcSessionController]: For iOS we need to convert the SDP to an actual JSON object'); - sdp = { - type: sdp.type, - parsedSdp: sdp.parsedSdp - }; - } - - var trickleIceEnabled = useTrickleIce(sdp.type); - - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // Chrome is incorrectly moving the position of the "b=" line in-between the "a=" lines. - // This is a violation of RFC4566 and causes the SBC to drop the call since it cannot - // parse the SDP. As a temporary workaround until Chrome is fixed, let's parse and then - // rebuild the SDP to ensure that it is correct. - // (Last verified in v36.0.1985.143) - var parsedSdp = sdp.parsedSdp; - - // Remove the ssrc-audio-level attribute, which imposes a security risk. - parsedSdp = sdpParser.removeAudioLevelAttr(parsedSdp); - - /////////////////////////////////////////////////////////////////////////////////////////// - // Add or remove trickle value in ice-options depending on Trickle ICE setting - parsedSdp = sdpParser.fixTrickleIceOption(parsedSdp, trickleIceEnabled); - - /////////////////////////////////////////////////////////////////////////////////////////// - // Remove 'end-of-candidates' attribute if Trickle ICE is disabled otherwise add it if all - // candidates have already been received and it is missing. - if (!trickleIceEnabled) { - parsedSdp = sdpParser.removeEndOfCandidatesLine(parsedSdp); - } else if (isEndOfCandidates()) { - parsedSdp = sdpParser.addEndOfCandidatesLine(parsedSdp); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // If there's an inactive m-line, Chrome adds empty a=ice-ufrag and a=ice-pwd attributes, - // which is not accepted by the remote peer. So remove these lines. - parsedSdp = sdpParser.removeEmptyAttributeLines(parsedSdp, ['ice-ufrag', 'ice-pwd', 'ice-options']); - - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // Firefox (as of v40) adds an ssrc to recvonly m-lines. Chrome doesn't like it, - // so remove it! - parsedSdp = sdpParser.removeNotNeededSsrc(parsedSdp); - - if (sdpParser.iceTotalCandidatesCount(parsedSdp) === 0 && !_ignoreNextConnection) { - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // When the network on the local computer changes (e.g.: VPN is connected/disconnected), - // Browser sometimes stop collecting any ICE candidates until the browser is restarted! - // So we have to return the appropriate error so the user can be alerted to restart the browser - if (trickleIceEnabled) { - var timeout = getCandidatesTimeout(); - logger.debug('[RtcSessionController]: No ICE candidates collected yet. Waiting ' + timeout + ' more milliseconds.'); - _candidatesTimer = window.setTimeout(handleFailure.bind(null, 'res_RestartBrowser'), timeout); - } else { - handleFailure('res_RestartBrowser'); - } - } else if (sdpParser.validateIceCandidates(parsedSdp) || (trickleIceEnabled && !isEndOfCandidates()) || _ignoreNextConnection) { - if (trickleIceEnabled && !isEndOfCandidates()) { - _sentIceCandidates = sdpParser.getIceCandidates(parsedSdp); - } - var finalSdp = { - type: sdp.type, - sdp: sdpParser.stringify(parsedSdp) - }; - if (_desktopPc) { - finalSdp.screen = SCREEN_SHARE_MEDIA_ID; - if (_pc && _localStreams[LOCAL_AUDIO_VIDEO]) { - // If there's a local stream to be transmitted, set its Id in the sdp video field - finalSdp.video = getVideoStreamId(LOCAL_AUDIO_VIDEO); - } - } - /////////////////////////////////////////////////////////////////////////////////////////// - // Invoke onSessionDescription event handler - logger.debug('[RtcSessionController]: send SessionDescription event'); - _ignoreNextConnection = false; - sendEvent(_that.onSessionDescription, {sdp: finalSdp}); - setSdpStatus(newStatus); - } else { - // There are ICE candidates but at least one m-line has no valid candidates - logger.error('[RtcSessionController]: Failure: SDP contains at least one m line without IceCandidates'); - handleFailure(); - } - } - - function sendSdpConnected() { - // Invoke onSdpConnected event handler for RTC (or Conversation) Handler - logger.debug('[RtcSessionController]: send SdpConnected event'); - sendEvent(_that.onSdpConnected, {}); - } - - function sendRemoteVideoAdded() { - // Invoke onRemoteVideoAdded event handler for RTC (or Conversation) Handler - logger.debug('[RtcSessionController]: send RemoteVideoAdded event'); - sendEvent(_that.onRemoteVideoAdded, {}); - } - - function sendRemoteVideoRemoved() { - // Invoke onRemoteVideoRemoved event handler for RTC (or Conversation) Handler - logger.debug('[RtcSessionController]: send RemoteVideoRemoved event'); - sendEvent(_that.onRemoteVideoRemoved, {}); - } - - function sendRemoteStreamUpdated() { - // Invoke onRemoteVideoRemoved event handler for RTC (or Conversation) Handler - logger.debug('[RtcSessionController]: send RemoteStreamUpdated event'); - sendEvent(_that.onRemoteStreamUpdated, {}); - } - - function sendDTMFToneChange(tone) { - // Invoke onDTMFToneChange event handler - logger.debug('[RtcSessionController]: send DTMFToneChanged event'); - sendEvent(_that.onDTMFToneChange, {tone: tone}); - } - - function sendClosed() { - // Invoke onClosed event handler - logger.debug('[RtcSessionController]: send Closed event'); - sendEvent(_that.onClosed, {}); - } - - function sendError(error) { - // Invoke onRtcError event handler - logger.debug('[RtcSessionController]: send Error event'); - sendEvent(_that.onRtcError, {error: error}); - } - - function sendAsyncError(error) { - if (typeof _that.onRtcError === 'function') { - window.setTimeout(function () { - sendError(error); - }, 0); - } - } - - function sendWarning(warning) { - // Invoke onRtcWarning event handler - logger.debug('[RtcSessionController]: send Warning event'); - sendEvent(_that.onRtcWarning, {warning: warning}); - } - - function sendAsyncWarning(warning) { - if (typeof _that.onRtcWarning === 'function') { - window.setTimeout(function () { - sendWarning(warning); - }, 0); - } - } - - function sendMediaUpdate() { - // Invoke onMediaUpdate event handler for Call - logger.debug('[RtcSessionController]: send MediaUpdate event'); - sendEvent(_that.onMediaUpdate, {}); - } - - function sendLocalVideoURL() { - // Invoke onLocalVideoURL event handler - logger.debug('[RtcSessionController]: send LocalVideoURL event'); - - // The local desktop (screen share) URL has precendece over the local video URL. - sendEvent(_that.onLocalVideoURL, { - url: _localDesktopUrl || _localVideoUrl - }); - sendMediaUpdate(); - } - - function sendRemoteObjectURL() { - // Invoke onRemoteObjectURL event handler - logger.debug('[RtcSessionController]: send RemoteObjectURL event'); - sendEvent(_that.onRemoteObjectURL, { - audioUrl: _remoteAudioUrl, - videoUrl: _remoteVideoUrls - }); - } - - function sendLocalStreamEnded(isDesktop) { - // Invoke onLocalStreamEnded event handler - logger.debug('[RtcSessionController]: send LocalStreamEnded event'); - sendEvent(_that.onLocalStreamEnded, {isDesktop: isDesktop}); - } - - function sendScreenSharePointerStatus(pointer) { - // Invoke onScreenSharePointerStatus event handler - logger.debug('[RtcSessionController]: send ScreenSharePointerStatus event'); - sendEvent(_that.onScreenSharePointerStatus, { - isSupported: !!(pointer && pointer.isSupported), - isEnabled: !!(pointer && pointer.isEnabled) - }); - } - - function sendQosAvailable(qos, isRenegotiation, lastSavedStats) { - // Invoke onQosAvailable event handler - logger.debug('[RtcSessionController]: send QosAvailable event'); - sendEvent(_that.onQosAvailable, {qos: qos, renegotiationInProgress: isRenegotiation, lastSavedStats: lastSavedStats}); - } - - ///////////////////////////////////////////////////////////////////////////// - // Internal functions - ///////////////////////////////////////////////////////////////////////////// - function logDebug(text, param) { - logger.debug('[RtcSessionController][' + _callId + ']: ' + text, param); - } - - function logWarning(text, param) { - logger.warn('[RtcSessionController][' + _callId + ']: ' + text, param); - } - - function logError(text, param) { - logger.error('[RtcSessionController][' + _callId + ']: ' + text, param); - } - - function normalizeMediaType(mediaType) { - if (!mediaType) { - return {audio: true, video: false, hdVideo: false, desktop: false}; - } - return { - audio: !!mediaType.audio, - video: !!mediaType.video, - hdVideo: !!mediaType.hdVideo, - desktop: !!mediaType.desktop - }; - } - - function useTrickleIce(type) { - if (!type) { - return _trickleIceForOffer || _trickleIceForAnswer; - } - return (type === 'offer' && _trickleIceForOffer) || (type === 'answer' && _trickleIceForAnswer); - } - - function isTrickleIceIndicated(type, sdp) { - if (!_isDirectCall) { - // TODO Remove this check when Media Server includes *ice-options:trickle* attribute... - return true; - } - var trickleIceIndication = sdp && sdpParser.isTrickleIceOption(sdp); - if (trickleIceIndication) { - logger.debug('[RtcSessionController]: SDP ' + type + ' indicates support for Trickle-ICE.'); - } - return !!trickleIceIndication; - } - - function isPcConnected() { - return ((!_pc || _pc.connected) && (!_desktopPc || _desktopPc.connected)); - } - - function isEndOfCandidates() { - return ((!_pc || _pc.endOfCandidates) && (!_desktopPc || _desktopPc.endOfCandidates)); - } - - function getSdpOrigin() { - var pc = _pc || _desktopPc; - if (!pc) { - return ''; - } - if (!pc.sdpOrigin) { - pc.sdpOrigin = sdpParser.getOrigin(pc.localDescription.sdp); - } - return pc.sdpOrigin; - } - - function collectCandidate(data) { - var pc = _pc || _desktopPc; - if (!pc.pendingCandidates) { - pc.pendingCandidates = []; - logger.debug('[RtcSessionController]: Start timer to collect candidates before send.'); - pc.collectCandidatesTimeout = window.setTimeout(function () { - logger.debug('[RtcSessionController]: Timer expired. Send all pending candidates.'); - delete pc.collectCandidatesTimeout; - if (pc.pendingCandidates) { - sendIceCandidates(pc.pendingCandidates); - delete pc.pendingCandidates; - } - }, DEFAULT_TRICKLE_ICE_TIMEOUT); - } - pc.pendingCandidates.push(data); - } - - function checkRelayCandidate(data) { - var pc = _pc || _desktopPc; - // We only need to check relay candidates for the screen share connection - if (RtcSessionController.allowAllIceCandidatesScreenShare || data.sdpMid !== SCREEN_SHARE_MEDIA_ID || - pc.hasRelayRtpCandidate || pc.sendAllCandidates) { - return true; - } - var candidate = new IceCandidate(data.candidate); - if (candidate.isTcpTlsRelay()) { - if (candidate.isRtp()) { - logger.info('[RtcSessionController]: Found local relay candidate for TURN over TCP/TLS. Remove queued candidates.'); - pc.hasRelayRtpCandidate = true; - delete pc.queuedCandidates; - if (pc.relayRtpCandidateTimeout) { - window.clearTimeout(pc.relayRtpCandidateTimeout); - delete pc.relayRtpCandidateTimeout; - } - } - return true; - } - if (candidate.transport === 'udp') { // ignore any tcp candidates - if (!pc.queuedCandidates) { - pc.queuedCandidates = []; - logger.debug('[RtcSessionController]: Start timer to wait for relay candidate for TURN over TCP/TLS.'); - pc.relayRtpCandidateTimeout = window.setTimeout(function () { - logger.info('[RtcSessionController]: Timer expired. Stop waiting for relay candidate for TURN over TCP/TLS and send all pending candidates.'); - delete pc.relayRtpCandidateTimeout; - if (pc.queuedCandidates) { - sendIceCandidates(pc.queuedCandidates); - delete pc.queuedCandidates; - } - pc.sendAllCandidates = true; - }, DESKTOP_RELAY_CANDIDATES_TIMEOUT); - } - // Save candidate in case we don't get any relay candidate for TURN over TCP/TLS - pc.queuedCandidates.push(data); - logger.debug('[RtcSessionController]: Queue candidate in case there is no TURN over TCP/TLS relay candidate'); - } - return false; - } - - function waitConnectedState() { - if (_connectedTimer) { - window.clearTimeout(_connectedTimer); - _connectedTimer = null; - } - if (isPcConnected()) { - setSdpStatus(SdpStatus.Connected); - return; - } - - // Wait until (ice)connectionstate is 'connected' or 'completed' and fail if timeout expired. - // The timeout will be restarted on each ICE_CANDIDATES message or event. - var timeout = _isMockedCall ? MOCK_CONNECT_TIMEOUT : CONNECTED_TIMEOUT; - logger.debug('[RtcSessionController]: Starting ICE CONNECTED timer'); - _connectedTimer = window.setTimeout(function () { - _connectedTimer = null; - if ((_sdpStatus === SdpStatus.AnswerSent || _sdpStatus === SdpStatus.AnswerReceived || _sdpStatus === SdpStatus.AnswerApplied) && !isPcConnected()) { - if (_isMockedCall) { - // Simulate SDP connected for the mock server. - setSdpStatus(SdpStatus.Connected); - } else { - logger.warn('[RtcSessionController]: Timed out waiting for connected state.'); - handleFailure('res_CallMediaFailed'); - } - } - }, timeout); - } - - function closePC(pc, stats) { - if (pc) { - if (pc.iceDisconnectTimeout) { - window.clearTimeout(pc.iceDisconnectTimeout); - delete pc.iceDisconnectTimeout; - } - unregisterPcEvtHandlers(pc, stats); - WebRTCAdapter.closePc(pc); - } - } - - function addLocalStreams() { - logger.debug('[RtcSessionController]: Add local streams to existing peer connection'); - try { - var pcStreams = _pc ? _pc.getLocalStreams() : []; - if (pcStreams.isEmpty()) { - _localStreams[LOCAL_AUDIO_VIDEO] && _pc.addStream(_localStreams[LOCAL_AUDIO_VIDEO]); - logger.debug('[RtcSessionController]: Added local audio/video stream'); - - if (!_desktopPc && _localStreams[LOCAL_SCREEN_SHARE]) { - // Use the main peerconnection to send screenshare - _pc.addStream(_localStreams[LOCAL_SCREEN_SHARE]); - logger.debug('[RtcSessionController]: Added local screenshare stream'); - } - } - if (_desktopPc) { - pcStreams = _desktopPc.getLocalStreams(); - if (pcStreams.isEmpty() && _localStreams[LOCAL_SCREEN_SHARE]) { - _desktopPc.addStream(_localStreams[LOCAL_SCREEN_SHARE]); - logger.debug('[RtcSessionController]: Added local screenshare stream'); - } - } - } catch (e) { - logger.error('[RtcSessionController]: Failed to add Local Streams ', e); - return false; - } - return true; - } - - function removeLocalStreams() { - logger.debug('[RtcSessionController]: Remove local streams from existing PeerConnections'); - try { - var pcs = [_pc, _desktopPc]; - pcs.forEach(function (pc) { - if (pc) { - pc.getLocalStreams().forEach(function (s, i) { - if (s) { - pc.removeStream(s); - logger.debug('[RtcSessionController]: Removed PeerConnection Stream #', i); - } - }); - } - }); - } catch (e) { - logger.error('[RtcSessionController]: Failed to remove Local Streams ', e); - return false; - } - return true; - } - - function stopStream(stream) { - if (!stream) { - return; - } - stream.oninactive = null; - WebRTCAdapter.stopMediaStream(stream); - } - - function stopStreams(streams) { - var reusedStreams = (streams === _oldLocalStreams) ? _localStreams : _oldLocalStreams; - streams && streams.forEach(function (stream) { - // Make sure the stream being stopped is not being reused. If it is, don't stop it! - if (!reusedStreams || reusedStreams.indexOf(stream) === -1) { - stopStream(stream); - } - }); - } - - function setLocalVideoUrl(streams) { - // Index 0 is video stream. Index 1 is the desktop stream. - var url = ['', '']; - - streams && streams.forEach(function (stream, idx) { - if (stream && stream.getVideoTracks().length > 0) { - url[idx] = WebRTCAdapter.createObjectURL(stream); - } - }); - - if (_localVideoUrl !== url[0] || _localDesktopUrl !== url[1]) { - _localVideoUrl = url[0]; - logger.debug('[RtcSessionController]: Set local video URL to ', _localVideoUrl || '<empty>'); - - _localDesktopUrl = url[1]; - logger.debug('[RtcSessionController]: Set local desktop URL to ', _localDesktopUrl || '<empty>'); - - sendLocalVideoURL(); - } - } - - function setLocalStream(stream, index) { - // Index 0 is the Audio+Video stream - // Index 1 is the Desktop (screen-sharing) stream - if (index !== LOCAL_AUDIO_VIDEO && index !== LOCAL_SCREEN_SHARE) { - logger.error('[RtcSessionController]: setLocalStream invoked with invalid index:', index); - return; - } - - if (_localStreams[index] === stream) { - // No changes - return; - } - - if (_localStreams[index]) { - // Stop the previous local stream - stopStream(_localStreams[index]); - } - _localStreams[index] = stream; - - setLocalVideoUrl(_localStreams); - } - - function clearRemoteObjectURL() { - if (!_remoteAudioUrl && !_remoteVideoUrls.length) { - return; - } - _remoteAudioUrl = null; - _remoteVideoUrls = []; - sendRemoteObjectURL(); - } - - function putRemoteObjectURL(stream) { - var updated = false; - - if (!stream) { - return false; - } - - if (stream.getVideoTracks().length > 0) { - var videoUrl = WebRTCAdapter.createObjectURL(stream, undefined, true /*IE only option*/); - var videoStreamId = WebRTCAdapter.getVideoStreamTrackId(stream); - - var found = _remoteVideoUrls.some(function (v) { - if (v.streamId === videoStreamId) { - if (v.url !== videoUrl) { - v.url = videoUrl; - updated = true; - } - return true; - } - }); - - if (!found) { - _remoteVideoUrls.push({url: videoUrl, streamId: videoStreamId}); - updated = true; - } - - if (updated) { - logger.debug('[RtcSessionController]: Updated Remote Video URLs:', _remoteVideoUrls); - } - } - - if (stream.getAudioTracks().length > 0) { - var audioUrl = WebRTCAdapter.createObjectURL(stream); - var audioStreamId = WebRTCAdapter.getAudioStreamTrackId(stream); - - if (_remoteAudioUrl && (_remoteAudioUrl.streamId === audioStreamId)) { - if (_remoteAudioUrl !== audioUrl) { - _remoteAudioUrl.url = audioUrl; - updated = true; - } - } else { - _remoteAudioUrl = {url: audioUrl, streamId: audioStreamId}; - updated = true; - } - - if (updated) { - logger.debug('[RtcSessionController]: Updated Remote Audio URL:', _remoteAudioUrl); - } - } - - return updated; - } - - function putRemoteObjectURLs(streams, clearExisting) { - if (!streams) { - return; - } - if (!(streams instanceof Array)) { - streams = [streams]; - } - var updated = false; - - if (clearExisting && (_remoteAudioUrl || _remoteVideoUrls.length)) { - updated = true; - _remoteAudioUrl = null; - _remoteVideoUrls = []; - } - - streams.forEach(function (s) { - _remoteStreams.push(s); - if (putRemoteObjectURL(s)) { - updated = true; - } - }); - if (updated) { - sendRemoteObjectURL(); - } - } - - function deleteRemoteObjectURL(stream) { - if (!stream) { - return; - } - var updated = false; - var videoTrackId = WebRTCAdapter.getVideoStreamTrackId(stream); - if (videoTrackId) { - _remoteVideoUrls.some(function (v, index) { - if (v.streamId === videoTrackId) { - _remoteVideoUrls.splice(index, 1); - updated = true; - return true; - } - }); - } - var audioTrackId = WebRTCAdapter.getAudioStreamTrackId(stream); - if (_remoteAudioUrl && (_remoteAudioUrl.streamId === audioTrackId)) { - _remoteAudioUrl = null; - updated = true; - } - - if (updated) { - sendRemoteObjectURL(); - } - } - - function enableAudioTrack(enable) { - // Enable/disable both _localStreams and _oldLocalStreams to ensure they - // are in synch in case media renegotiation fails. - var hasAudioTracks = false; - if (_localStreams[LOCAL_AUDIO_VIDEO]) { - hasAudioTracks = WebRTCAdapter.toggleAudio(_localStreams[LOCAL_AUDIO_VIDEO], enable); - } - _isMuted = !hasAudioTracks || !enable; - - if (_oldLocalStreams && _oldLocalStreams[LOCAL_AUDIO_VIDEO]) { - WebRTCAdapter.toggleAudio(_oldLocalStreams[LOCAL_AUDIO_VIDEO], enable); - } - } - - function startRenegotiation(localRequest, renegotiationCb) { - if (_renegotiationInProgress) { - logger.debug('[RtcSessionController]: There\'s a renegotiation in progress, fail or succeed it based on current SdpStatus:', _sdpStatus); - if (_sdpStatus === SdpStatus.AnswerReceived || _sdpStatus === SdpStatus.AnswerApplied || _sdpStatus === SdpStatus.AnswerSent) { - renegotiationSuccessful(); - } else { - renegotiationFailed('Renegotiation already in progress'); - } - } - - if (_pc && _pc.iceDisconnectTimeout) { - logger.info('[RtcSessionController]: Ignore previous ice Disconnection'); - window.clearTimeout(_pc.iceDisconnectTimeout); - delete _pc.iceDisconnectTimeout; - } - - // Save the current RTCPeerConnection and MediaStream objects in case - // the renegotiation fails. - _renegotiationInProgress = true; - _renegotiationStartedLocally = !!localRequest; - _renegotiationCb = renegotiationCb || null; - - _oldPC = _pc; - _oldDesktopPC = _desktopPc; - _oldCallStats = _callStats; - unregisterPcEvtHandlers(_oldPC, _oldCallStats); - unregisterPcEvtHandlers(_oldDesktopPC); - _pc = null; - _desktopPc = null; - _oldLocalStreams = _localStreams; - _localStreams = [null, null]; - _oldMediaConstraints = Utils.shallowCopy(_mediaConstraints); - _oldActiveMediaType = Utils.shallowCopy(_activeMediaType); - _sdpStatus = SdpStatus.None; - _pendingRemoteSdp = null; - _pendingChangeMedia = null; - } - - function invokeRenegotiationCb(error) { - if (!_renegotiationCb) { - return; - } - try { - _renegotiationCb(error && error !== Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED ? 'res_ChangeMediaFailed' : error); - } catch (e) { - logger.error(e); - } - _renegotiationCb = null; - } - - function renegotiationSuccessful() { - logger.info('[RtcSessionController]: The media renegotiation was successful'); - - // Close the old RTCPeerConnection object - logger.info('[RtcSessionController]: Closing the old RTCPeerConnection'); - stopStats(_oldCallStats, true, !_oldPC.connectionBypassed); - closePC(_oldPC, _oldCallStats); - closePC(_oldDesktopPC); - _oldPC = null; - _oldDesktopPC = null; - _oldCallStats = null; - - // Stop the old MediaStream object - logger.info('[RtcSessionController]: Stopping the old MediaStream'); - - // Update the holding flag (it should already be set correctly, but...) - if (_holdInProgress) { - _holding = true; - } else if (_retrieveInProgress) { - _holding = false; - } - - if (!_holding && !_held) { - addLocalStreams(); - } - stopStreams(_oldLocalStreams); - _oldLocalStreams = null; - _oldMediaConstraints = null; - _oldActiveMediaType = null; - - // Update the local and remote object URLs - setLocalVideoUrl(_localStreams); - var remoteStreams = _pc.getRemoteStreams(); - if (_desktopPc) { - remoteStreams = remoteStreams.concat(_desktopPc.getRemoteStreams()); - } - putRemoteObjectURLs(remoteStreams, true); - enableAudioTrack(!_isMuted); - - _holdInProgress = false; - _retrieveInProgress = false; - _renegotiationInProgress = false; - - // Reset the _dtmfSender object so that it's reinitialized when the canSendDTMFDigits() is called - _dtmfSender = null; - // Send the onMediaUpdate event for the Call object - sendMediaUpdate(); - - invokeRenegotiationCb(); - } - - function renegotiationFailed(error) { - logger.info('[RtcSessionController]: The media renegotiation has failed: ', error); - - // Close the new RTCPeerConnection object - closePC(_pc, _callStats); - closePC(_desktopPc); - _sdpStatus = SdpStatus.Connected; - _pc = _oldPC; - _desktopPc = _oldDesktopPC; - _callStats = _oldCallStats; - registerPcEvtHandlers(_oldPC); - registerPcEvtHandlers(_oldDesktopPC); - _oldPC = null; - _oldDesktopPC = null; - _oldCallStats = null; - - // Stop the new MediaStream object - stopStreams(_localStreams); - _localStreams = _oldLocalStreams; - _oldLocalStreams = null; - - // Update the local and remote object URLs - setLocalVideoUrl(_localStreams); - - //Restore media constraints and active media type - _mediaConstraints = Utils.shallowCopy(_oldMediaConstraints); - _activeMediaType = Utils.shallowCopy(_oldActiveMediaType); - _oldMediaConstraints = null; - _oldActiveMediaType = null; - - // Restore the holding flag if applicable - if (_holdInProgress) { - // Hold request failed - _holding = false; - } else if (_retrieveInProgress) { - // Retrieve request failed. Call is still held. - _holding = true; - } - - _holdInProgress = false; - _retrieveInProgress = false; - _renegotiationInProgress = false; - - // Send the onMediaUpdate event for the Call object - sendMediaUpdate(); - - invokeRenegotiationCb(error); - } - - function handleFailure(err, errCb) { - if (_renegotiationInProgress) { - renegotiationFailed(err); - return; - } - if (err === Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED) { - // Special case, the user cancelled the Get User Media - logger.info('[RtcSessionController]: User cancelled the screen share request'); - } else if (err === 'res_RestartBrowser') { - logger.error('[RtcSessionController]: Failure: No ICE candidates collected. Browser needs to be restarted'); - } else { - err = err || 'res_RTCError'; - logger.warn('[RtcSessionController]: There was an internal failure. Send error event: ', err); - } - if (errCb) { - errCb(err); - } else { - sendError(err); - } - } - - function processPendingSdp() { - if (_pendingChangeMedia) { - logger.info('[RtcSessionController]: Process the pending changeMedia'); - changeMedia(_pendingChangeMedia.mediaType, _pendingChangeMedia.cb, _pendingChangeMedia.screenConstraint); - _pendingChangeMedia = null; - } else if (_pendingRemoteSdp) { - logger.info('[RtcSessionController]: Process the pending remote description'); - _that.setRemoteDescription(_pendingRemoteSdp.sdp, _pendingRemoteSdp.cb); - _pendingRemoteSdp = null; - } - } - - function setSdpStatus(newStatus) { - if (_sdpStatus === SdpStatus.PrAnswerReceived && newStatus === SdpStatus.Connected) { - logger.debug('[RtcSessionController]: Cannot change SDP status to ' + newStatus + ' without final SDP answer.'); - return; - } - if (_sdpStatus !== newStatus) { - logger.debug('[RtcSessionController]: Changed SDP status from ' + _sdpStatus + ' to ' + newStatus); - _sdpStatus = newStatus; - if (_pc) { - switch (newStatus) { - case SdpStatus.AnswerApplied: - processPendingSdp(); - break; - case SdpStatus.Connected: - if (_renegotiationInProgress) { - renegotiationSuccessful(); - } - // Send the onSdpConnected event for the RtcHandler - sendSdpConnected(); - processPendingSdp(); - _callStats.start(); // Start collecting stats - break; - case SdpStatus.AnswerSent: - waitConnectedState(); - break; - } - } - } - } - - function updateLocalDescription(pc, rtcSdp) { - logger.debug('[RtcSessionController]: updateLocalDescription with mediaConstraints=', _mediaConstraints); - - var hasVideo = false; - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - sdpParser.disableMultiplex(parsedSdp); - - if (_isTelephonyCall) { - // Workaround: Chrome is now including UDP/TLS in the media protocols, which breaks outgoing calls to GTC/ATC - // For now, we're removing UDP/TLS until SBC (SSM) supports it - sdpParser.removeAudioMediaProtocols(parsedSdp, ['UDP', 'TLS']); - } - - if (_holding) { - sdpParser.setConnectionMode(parsedSdp, 'both', 'inactive'); - logger.info('[RtcSessionController]: updateLocalDescription with Connection Mode Inactive'); - } else if (_mediaConstraints.video || _mediaConstraints.desktop) { - hasVideo = sdpParser.hasVideo(parsedSdp); - } else if (rtcSdp.type === 'answer') { - hasVideo = sdpParser.hasVideo(parsedSdp); - if (hasVideo && _remoteVideoDisabled) { - // Disable video - sdpParser.removeVideo(parsedSdp); - hasVideo = false; - } - } - - if (pc === _desktopPc) { - // Set the a=mid attribute so the server/peer knows which m-line belongs to screenshare - sdpParser.setMediaIdAttribute(parsedSdp, 0, SCREEN_SHARE_MEDIA_ID, true); - sdpParser.setXGoogle(parsedSdp, RtcSessionController.xGoogle.screenShare); - } else if (hasVideo) { - // For the main peer connection, only set the screenshare x-google parameters if there's no _desktopPc - sdpParser.setXGoogle(parsedSdp, _mediaConstraints.desktop && !_desktopPc ? RtcSessionController.xGoogle.screenShare : - (_mediaConstraints.hdVideo ? RtcSessionController.xGoogle.hdVideo : RtcSessionController.xGoogle.video)); - } - sdpParser.setOpusParameters(parsedSdp, RtcSessionController.sdpParameters.audio); - - if (_isDirectCall && RtcSessionController.sdpParameters.preferVp9 && rtcSdp.type === 'offer') { - sdpParser.preferVideoVp9(parsedSdp); - } - - if (rtcSdp.type !== 'answer') { - _activeMediaType = {audio: true, video: hasVideo}; - } - - var newSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - - logger.debug('[RtcSessionController]: Modified local SDP: ', newSdp); - return newSdp; - } - - function updateRemoteDescription(pc, parsedSdp, sdpType) { - logger.debug('[RtcSessionController]: updateRemoteDescription with mediaConstraints=', _mediaConstraints); - - var allVideoRecvOnly = false; - var hasVideo = sdpParser.hasVideo(parsedSdp); - if (hasVideo) { - var videoModes = sdpParser.getVideoConnectionModes(parsedSdp); - logger.info('[RtcSessionController]: Remote description has video modes set to ', videoModes); - allVideoRecvOnly = videoModes.every(function (v) { - return v === 'recvonly'; - }); - hasVideo = !allVideoRecvOnly; - - var mediaType; - if (pc === _desktopPc || (_mediaConstraints.desktop && !_desktopPc)) { - // This is either the desktop PC OR it is a direct call with local screenshare active - mediaType = 'screenShare'; - } else if (_mediaConstraints.hdVideo) { - mediaType = 'hdVideo'; - } else { - mediaType = 'video'; - } - - var maxBw = RtcSessionController.sdpParameters[mediaType].maxBw; - var xGoogle = RtcSessionController.xGoogle[mediaType]; - - parsedSdp = sdpParser.setVideoBandwidth(parsedSdp, maxBw, true); - sdpParser.setXGoogle(parsedSdp, xGoogle); - - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // If the client is receiving an SDP Offer with *recvonly* video AND the client is not - // sending any video, disable all the video m-lines to avoid Chrome-Firefox - // interoperability issue. - if (_isDirectCall && sdpType === 'offer' && allVideoRecvOnly && !_mediaConstraints.video && !_mediaConstraints.desktop) { - parsedSdp = sdpParser.removeVideo(parsedSdp); - } - } - sdpParser.setOpusParameters(parsedSdp, RtcSessionController.sdpParameters.audio); - - var oldHeld = _held; - if (sdpParser.isHold(parsedSdp)) { - // If this is an SDP Offer, that means the other party is putting the call on hold. - // If this is an SDP Answer AND we are not holding the call AND we offered audio, - // that also means the other party is holding the call. - _held = (sdpType === 'offer' || (!_holding && _mediaConstraints.audio)); - } else { - _held = false; - } - if (oldHeld !== _held) { - if (_held) { - logger.info('[RtcSessionController]: The client has been held'); - } else { - logger.info('[RtcSessionController]: The client has been retrieved'); - } - } - - // Check if the remote party is adding or removing video - if (sdpType === 'offer' && _sdpStatus === SdpStatus.Connected && !_holding && !_held) { - if ((_remoteVideoUrls.length) && !hasVideo) { - sendRemoteVideoRemoved(); - } else if (!_remoteVideoUrls.length && hasVideo) { - sendRemoteVideoAdded(); - } - } - - if (!_holding) { - if (sdpType !== 'answer' || !allVideoRecvOnly) { - _activeMediaType = {audio: true, video: hasVideo}; - } - } - - return parsedSdp; - } - - function getCandidatesTimeout() { - var timeout = RtcSessionController.candidatesTimeout || DEFAULT_CANDIDATES_TIMEOUT; - if (_numberOfExtraVideoChannels > 0) { - timeout += timeout * (WebRTCAdapter.iceTimeoutSafetyFactor || 0); - } - return timeout; - } - - function getTrickleIceTimeout() { - return RtcSessionController.trickleIceTimeout || DEFAULT_TRICKLE_ICE_TIMEOUT; - } - - function setLocalAndSendMessage(audioVideoSdp, desktopSdp) { - logger.debug('[RtcSessionController]: Successfully created local audio/video description:', audioVideoSdp); - if (desktopSdp) { - logger.debug('[RtcSessionController]: Successfully created local screenshare description:', desktopSdp); - } - - if (_renegotiationInProgress) { // CQ00304251 Work around for noise during media renegotiation - removeLocalStreams(); - } - if (_candidatesTimer) { - window.clearTimeout(_candidatesTimer); - _candidatesTimer = null; - } - - var timeout; - var sdpType = (audioVideoSdp && audioVideoSdp.type) || (desktopSdp && desktopSdp.type); - var localSdpToBeSet = 2; - var errorOccurred = false; - var onLocalSdpSet = function (pc, err) { - localSdpToBeSet--; - if (err) { - logger.error('[RtcSessionController]: Failed to apply Local ' + pc + ' description:', err.message || err); - if (!errorOccurred) { - errorOccurred = true; - handleFailure(); - } - } else { - logger.debug('[RtcSessionController]: Local description was successfully applied. PC=', pc); - } - if (!errorOccurred && localSdpToBeSet <= 0) { - if (_ignoreNextConnection) { - sendSessionDescription(SdpStatus.Connected); - if (_pc) { - _pc.connectionBypassed = true; - unregisterPcEvtHandlers(_pc, _callStats); - } - if (_desktopPc) { - _desktopPc.connectionBypassed = true; - unregisterPcEvtHandlers(_desktopPc); - } - return; - } - - if (!_renegotiationInProgress) { - sendMediaUpdate(); - } - - if (sdpType === 'offer') { - setSdpStatus(SdpStatus.OfferPending); - if (_trickleIceForOffer) { - timeout = getTrickleIceTimeout(); - if (timeout > 0) { - logger.debug('[RtcSessionController]: Trickle ICE is enabled. Start a short timer before sending SDP Offer (' + timeout + 'ms).'); - window.setTimeout(function () { - if (_sdpStatus === SdpStatus.OfferPending) { - logger.debug('[RtcSessionController]: Trickle ICE timer expired. Send SDP Offer.'); - sendSessionDescription(SdpStatus.OfferSent); - } - }, timeout); - } else { - logger.debug('[RtcSessionController]: Trickle ICE is enabled. Send the SDP Offer.'); - sendSessionDescription(SdpStatus.OfferSent); - } - } - } else { - setSdpStatus(SdpStatus.AnswerPending); - if (_trickleIceForAnswer) { - timeout = getTrickleIceTimeout(); - if (timeout > 0) { - logger.debug('[RtcSessionController]: Trickle ICE is enabled. Start a short timer before sending SDP Answer (' + timeout + 'ms).'); - window.setTimeout(function () { - if (_sdpStatus === SdpStatus.AnswerPending) { - logger.debug('[RtcSessionController]: Trickle ICE timer expired. Send SDP Answer.'); - sendSessionDescription(SdpStatus.AnswerSent); - } - }, timeout); - } else { - logger.debug('[RtcSessionController]: Trickle ICE is enabled. Send the SDP Answer.'); - sendSessionDescription(SdpStatus.AnswerSent); - } - } - } - if ((_sdpStatus === SdpStatus.OfferPending && !_trickleIceForOffer) || - (_sdpStatus === SdpStatus.AnswerPending && !_trickleIceForAnswer)) { - timeout = getCandidatesTimeout(); - logger.debug('[RtcSessionController]: Waiting for ICE candidates (' + timeout + 'ms).'); - _candidatesTimer = window.setTimeout(function () { - _candidatesTimer = null; - logger.warn('[RtcSessionController]: Timed out waiting for candidates. Send session description.'); - if (!_pc) { - logger.warn('[RtcSessionController]: RTCPeerConnection instance no longer exists.'); - return; - } - - if (sdpType === 'offer') { - if (_sdpStatus === SdpStatus.OfferPending) { - sendSessionDescription(SdpStatus.OfferSent); - } - } else { - if (_sdpStatus === SdpStatus.AnswerPending) { - sendSessionDescription(SdpStatus.AnswerSent); - } - } - }, timeout); - } - } - }; - try { - if (_pc && audioVideoSdp) { - var mainPcCb = onLocalSdpSet.bind(null, 'audio/video'); - audioVideoSdp = updateLocalDescription(_pc, audioVideoSdp); - _pc.setLocalDescription(audioVideoSdp, mainPcCb, mainPcCb); - _pc.startTime = Date.now(); - } else { - localSdpToBeSet--; - } - - if (_desktopPc && desktopSdp) { - var desktopPcCb = onLocalSdpSet.bind(null, 'screenshare'); - desktopSdp = updateLocalDescription(_desktopPc, desktopSdp); - _desktopPc.setLocalDescription(desktopSdp, desktopPcCb, desktopPcCb); - _desktopPc.startTime = Date.now(); - } else { - localSdpToBeSet--; - } - } catch (e) { - onLocalSdpSet('unknown', e || 'error'); - } - } - - function onAnswerSdp() { - if (_holdInProgress || (_holding && !_retrieveInProgress)) { - _pc.oniceconnectionstatechange = null; - logger.debug('[RtcSessionController]: Call is already held or is being put on hold. Stop monitoring iceConnectionState changes'); - setSdpStatus(SdpStatus.Connected); - } else if (_sdpStatus === SdpStatus.AnswerReceived) { - setSdpStatus(SdpStatus.AnswerApplied); - } - } - - function getOfferToReceiveVideo(constraints) { - return (!_remoteVideoDisabled || !!(constraints && constraints.video)) && (Utils.isMobile() || !_largeConference); - } - - function setRemoteDescription(rtcSdp) { - logger.debug('[RtcSessionController]: setRemoteDescription()'); - - try { - // Make sure that we already have an RTCPeerConnection object - if (!createPeerConnection()) { - handleFailure(); - return; - } - - var localStreams = _pc.getLocalStreams(); - - var parsedSdp = sdpParser.parse(rtcSdp.sdp); - - // Screenshare m-line (if available) - var desktopSdpMline; - if (rtcSdp.screen) { - // Try to find the screenshare m-line indicated by the server - var index = sdpParser.findMediaLineIndex(parsedSdp, rtcSdp.screen); - if (index !== -1) { - // Found it! Retrieve it and remove it from the 'main' sdp - desktopSdpMline = parsedSdp.m[index]; - parsedSdp.m.splice(index, 1); - } - } - switch (rtcSdp.type) { - case 'offer': - _trickleIceForAnswer = !RtcSessionController.disableTrickleIce && isTrickleIceIndicated(rtcSdp.type, parsedSdp); - if (_trickleIceForAnswer) { - logger.info('[RtcSessionController]: Enabled Trickle-ICE for answer.'); - } - break; - case 'pranswer': - case 'answer': - setSdpStatus(rtcSdp.type === 'pranswer' ? SdpStatus.PrAnswerReceived : SdpStatus.AnswerReceived); - if (_trickleIceForOffer) { - // We may need to disable trickle ICE for Offer if the peer doesn't support it. - // This should never happen when connecting to other Circuit clients. - _trickleIceForOffer = isTrickleIceIndicated(rtcSdp.type, parsedSdp); - if (!_trickleIceForOffer) { - logger.warn('[RtcSessionController]: Disabled Trickle-ICE for offer.'); - } - } - break; - } - - var remoteSdpToBeSet = 2; - var errorOccurred = false; - var onRemoteSdpSet = function (pc, err) { - remoteSdpToBeSet--; - if (err) { - logger.error('[RtcSessionController]: Failed to apply Remote ' + pc + ' description:', err.message || err); - if (!errorOccurred) { - errorOccurred = true; - handleFailure(); - } - } else { - logger.debug('[RtcSessionController]: Remote description was successfully applied. PC=', pc); - } - if (!errorOccurred && remoteSdpToBeSet <= 0) { - logger.debug('[RtcSessionController]: Remote Descriptions were successfully applied.'); - if (!_renegotiationInProgress) { - sendMediaUpdate(); - } - - ///////////////////////////////////////////////////////////////////////////// - // No RTP packets should be sent if: - // 1. The client is holding the call - // 2. SDP offer is inactive or sendonly (i.e. the client is being held) - // - // The browser will not send RTP packets if the RTCPeerConnection instance - // does not have a local stream. - ///////////////////////////////////////////////////////////////////////////// - - switch (rtcSdp.type) { - case 'offer': - if (_holding || _held) { - if (localStreams.length > 0) { - removeLocalStreams(); - logger.debug('[RtcSessionController]: Removed Local Stream (hold)'); - } else { - logger.debug('[RtcSessionController]: Local Stream has not been set (hold)'); - } - } else { - addLocalStreams(); - } - - logger.debug('[RtcSessionController]: Creating Local Description Answer...'); - - var sdpConstraints = { - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: getOfferToReceiveVideo() - } - }; - // Tell the PC what the local media is for its own SDP manipulation. The PC could tell what the local media - // is by inspecting the local streams, but this way is faster. - var localMedia = { - audio: _mediaConstraints.audio, - video: _mediaConstraints.video || _mediaConstraints.desktop - }; - - _pc.createAnswer(function (mainSdp) { - if (_desktopPc && desktopSdpMline) { - var desktopSdpConstraints = { - mandatory: { - OfferToReceiveAudio: false, // We don't want audio in the desktop PC - OfferToReceiveVideo: !_remoteVideoDisabled - } - }; - _desktopPc.createAnswer(function (desktopSdp) { - setLocalAndSendMessage(mainSdp, desktopSdp); - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create screenshare Local Description: ', error.message || error); - handleFailure(); - }, desktopSdpConstraints); - } else { - setLocalAndSendMessage(mainSdp); - } - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create Local Description: ', error.message || error); - handleFailure(); - }, sdpConstraints, localMedia); - break; - - case 'pranswer': - // Early Media applied - logger.debug('[RtcSessionController]: Received pranswer, applying early media SDP'); - onAnswerSdp(); - waitConnectedState(); - // SDP status has already been set to SdpStatus.PrAnswerReceived - break; - - case 'answer': - onAnswerSdp(); - waitConnectedState(); - break; - - default: - logger.error('[RtcSessionController]: Invalid SDP Type = ', rtcSdp.type); - handleFailure(); - break; - } - } - }; - - if (_pc) { - if (_isDirectCall && !_desktopPc && desktopSdpMline) { - // This client only supports 1 peerconnection, so replace the video m-line with screenshare m-line - // Set video m-line as inactive and save it for later. We need to send it back in the answer SDP. - _disabledVideoMline = parsedSdp.m[DIRECT_CALL_SDP_M_LINE_INDEX.video]; - _disabledVideoMline.port = 0; - sdpParser.setConnectionMode({m: [_disabledVideoMline]}, 'video', 'inactive'); - parsedSdp.m[DIRECT_CALL_SDP_M_LINE_INDEX.video] = desktopSdpMline; - logger.debug('[RtcSessionController]: Disabled and replaced video m-line with screenshare m-line'); - } - parsedSdp = updateRemoteDescription(_pc, parsedSdp, rtcSdp.type); - var updatedMainSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - logger.debug('[RtcSessionController]: Modified remote audio/video SDP:', updatedMainSdp); - - var mainPcCb = onRemoteSdpSet.bind(null, 'audio/video'); - _pc.setRemoteDescription(updatedMainSdp, mainPcCb, mainPcCb); - _pc.remoteOrigin = sdpParser.getOrigin(parsedSdp).trim(); - _pc.startTime = Date.now(); - } else { - remoteSdpToBeSet--; - } - - if (_desktopPc && desktopSdpMline) { - parsedSdp.m = [desktopSdpMline]; - parsedSdp = updateRemoteDescription(_desktopPc, parsedSdp, rtcSdp.type); - var updatedScreenSdp = new WebRTCAdapter.SessionDescription({ - type: rtcSdp.type, - sdp: sdpParser.stringify(parsedSdp) - }); - logger.debug('[RtcSessionController]: Modified remote screenshare SDP:', updatedScreenSdp); - - _desktopPc.startTime = Date.now(); - var desktopPcCb = onRemoteSdpSet.bind(null, 'screenshare'); - _desktopPc.setRemoteDescription(updatedScreenSdp, desktopPcCb, desktopPcCb); - _desktopPc.remoteOrigin = sdpParser.getOrigin(parsedSdp).trim(); - } else { - closePC(_desktopPc); - _desktopPc = null; - remoteSdpToBeSet--; - } - } catch (e) { - logger.error(e); - handleFailure(); - } - } - - function renegotiateMedia() { - logger.debug('[RtcSessionController]: renegotiateMedia()'); - // Make sure that we already have an RTCPeerConnection object - if (!createPeerConnection()) { - handleFailure(); - return; - } - - // Do not add the local stream to the RTCPeerConnection instance if the - // client is holding the call. No RTP packets are sent in this case. - if (!_holding) { - addLocalStreams(); - } else { - logger.debug('[RtcSessionController]: Local Stream has not been set (holding)'); - } - - logger.debug('[RtcSessionController]: Creating Local Descriptions...'); - - var offerConstraints = { - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: getOfferToReceiveVideo(_mediaConstraints) - } - }; - - try { - _pc.createOffer(function (mainSdp) { - if (_desktopPc && (!_remoteVideoDisabled || _mediaConstraints.desktop)) { - offerConstraints.mandatory.OfferToReceiveVideo = true; - offerConstraints.mandatory.OfferToReceiveAudio = false; - _desktopPc.createOffer(function (desktopSdp) { - setLocalAndSendMessage(mainSdp, desktopSdp); - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create screenshare Local Description: ', error.message || error); - handleFailure(); - }, offerConstraints); - } else { - if (_desktopPc) { - closePC(_desktopPc); - _desktopPc = null; - } - setLocalAndSendMessage(mainSdp); - } - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create audio/video Local Description: ', error.message || error); - handleFailure(); - }, offerConstraints); - } catch (e) { - logger.error(e); - handleFailure(); - } - } - - function changeMedia(mediaType, cb, screenConstraint) { - if (_sdpStatus === SdpStatus.AnswerReceived) { - logger.warn('[RtcSessionController]: Still processing the previous answer remote description. Queue the changeMedia'); - _pendingChangeMedia = {mediaType: mediaType, cb: cb, screenConstraint: screenConstraint}; - return; - } - if (!isConnStable()) { - sendAsyncCallback(cb, 'Connection not stable'); - return; - } - startRenegotiation(true, cb); - if (typeof mediaType.audio === 'boolean') { - _mediaConstraints.audio = mediaType.audio; - } - if (typeof mediaType.video === 'boolean') { - _mediaConstraints.video = mediaType.video; - } - if (typeof mediaType.hdVideo === 'boolean') { - _mediaConstraints.hdVideo = mediaType.hdVideo; - } - if (typeof mediaType.desktop === 'boolean') { - _mediaConstraints.desktop = mediaType.desktop; - } - getUserMedia(renegotiateMedia, handleFailure, screenConstraint); - } - - function close() { - logger.debug('[RtcSessionController]: Closing the RTC session controller'); - setSdpStatus(SdpStatus.None); - _activeMediaType = {audio: false, video: false}; - - if (_screenShareEventHandler) { - ScreenSharingController.unregEvtHandlers(_screenShareEventHandler); - } - - if (_candidatesTimer) { - window.clearTimeout(_candidatesTimer); - _candidatesTimer = null; - } - if (_connectedTimer) { - window.clearTimeout(_connectedTimer); - _connectedTimer = null; - } - - // Stop call stats collection. If there is an Old PC then we should send the stats for - // the Old PC since the new PC wouldn't be connected in this case. - var pc = _oldCallStats || _callStats; - if (pc) { - stopStats(pc, false, !pc.connectionBypassed); - } - - if (_oldPC) { - logger.debug('[RtcSessionController]: Closing the old audio/video RTCPeerConnection'); - closePC(_oldPC, _oldCallStats); - _oldPC = null; - } - - if (_oldDesktopPC) { - logger.debug('[RtcSessionController]: Closing the old screenshare RTCPeerConnection'); - closePC(_oldDesktopPC); - _oldDesktopPC = null; - } - - if (_pc) { - logger.debug('[RtcSessionController]: Closing the RTCPeerConnection'); - closePC(_pc, _callStats); - _pc = null; - } - - if (_desktopPc) { - logger.debug('[RtcSessionController]: Closing the Desktop peer connection'); - closePC(_desktopPc); - _desktopPc = null; - } - - if (_oldLocalStreams) { - logger.debug('[RtcSessionController]: Stopping the old MediaStream'); - stopStreams(_oldLocalStreams); - _oldLocalStreams = null; - } - - _renegotiationInProgress = false; - _holdInProgress = false; - _retrieveInProgress = false; - _holding = false; - _held = false; - _isMuted = false; - _dtmfSender = null; - - setLocalStream(null, LOCAL_AUDIO_VIDEO); - setLocalStream(null, LOCAL_SCREEN_SHARE); - clearRemoteObjectURL(); - sendClosed(); - _sessionTerminated = true; - - // Unregister all event handlers, except onQosAvailable, which will be done later - unregisterEventHandlers(['onQosAvailable']); - } - - function enableDTMFSender() { - if (!_pc) { - logger.error('[RtcSessionController]: PeerConnection not setup'); - return false; - } - - if (_dtmfSender) { - logger.debug('[RtcSessionController]: DTMF Sender already enabled'); - return true; - } - - var localStreams = _pc.getLocalStreams(); - if (localStreams.length > 0) { - // [Pastro, Rodrigo] In our client we will always have a single stream, at least in the near future. - var audioTracks = localStreams[LOCAL_AUDIO_VIDEO].getAudioTracks(); - if (audioTracks.length > 0) { - _dtmfSender = _pc.createDTMFSender(audioTracks[0]); - if (_dtmfSender) { - _dtmfSender.ontonechange = handleDTMFToneChange.bind(null, _pc); - logger.info('[RtcSessionController]: Created DTMF Sender using remote audio track: ' + audioTracks[0].label); - } else { - return false; - } - } else { - logger.error('[RtcSessionController]: No Audio Track to create DTMF Sender'); - return false; - } - } else { - logger.error('[RtcSessionController]: No Local Stream to create DTMF Sender'); - return false; - } - - return true; - } - - function startConn(remoteSdp) { - // Successfully got access to media input devices - logger.debug('[RtcSessionController]: startConn()'); - if (remoteSdp) { - setRemoteDescription(remoteSdp); - } else { - addLocalStreams(); - - logger.debug('[RtcSessionController]: Creating Local Description...'); - - var offerConstraints = { - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: getOfferToReceiveVideo(_mediaConstraints) - } - }; - - _pc.createOffer(function (mainSdp) { - if (_desktopPc && (!_remoteVideoDisabled || _mediaConstraints.desktop)) { - var desktopOfferConstraints = { - mandatory: { - OfferToReceiveAudio: false, - OfferToReceiveVideo: true - } - }; - _desktopPc.createOffer(function (desktopSdp) { - setLocalAndSendMessage(mainSdp, desktopSdp); - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create screenshare Local Description: ', error.message || error); - handleFailure(); - }, desktopOfferConstraints); - } else { - setLocalAndSendMessage(mainSdp); - } - }, function (error) { - error = error || 'Unspecified'; - logger.error('[RtcSessionController]: Failed to create Local Description: ', error.message || error); - handleFailure(); - }, offerConstraints); - } - } - - function isConnStable() { - if (!_pc) { - logger.debug('[RtcSessionController]: isConnStable: There is no connection'); - return false; - } - if (_sdpStatus === SdpStatus.AnswerReceived || _sdpStatus === SdpStatus.AnswerApplied) { - logger.debug('[RtcSessionController]: isConnStable: Processing remote answer SDP, the connection is considered stable'); - return true; - } - if (_sdpStatus === SdpStatus.AnswerSent) { - logger.debug('[RtcSessionController]: isConnStable: Answer SDP sent, the connection is considered stable'); - return true; - } - if (_renegotiationInProgress) { - logger.warn('[RtcSessionController]: isConnStable: There is a pending media renegotiation'); - return false; - } - if (_pc.signalingState !== 'stable' && !_pc.connectionBypassed) { - logger.warn('[RtcSessionController]: isConnStable: The connection is not established. signalingState =', _pc.signalingState); - if (!_isMockedCall) { - return false; - } - } - return true; - } - - function sendAsyncCallback(cb, error) { - if (typeof cb === 'function') { - window.setTimeout(function () { - cb(error); - }, 0); - } - } - - function setExtraVideoChannels(extra) { - if (Utils.isMobile()) { - logger.debug('[RtcSessionController]: Number of extra video channels for mobile clients is 0 (zero)'); - return false; - } - - if (_largeConference) { - logger.debug('[RtcSessionController]: Number of extra video channels for large conference is 0 (zero)'); - return false; - } - - if (_numberOfExtraVideoChannels !== extra) { - logger.info('[RtcSessionController]: Set number of extra video channels to', extra); - _numberOfExtraVideoChannels = extra; - return true; - } - return false; - } - - function getVideoStreamId(pcType) { - var result; - var stream = _localStreams[pcType]; - if (stream) { - var id = stream.id + ' '; - var track = stream.getVideoTracks()[0]; - if (track) { - id += track.id; - result = id; - } - } - return result; - } - - function combineDescriptions() { - var parsedSdp; - var type; - if (_pc) { - var mainSdp = _pc.localDescription; - type = mainSdp.type; - parsedSdp = sdpParser.parse(mainSdp.sdp); - - if (!_desktopPc && _disabledVideoMline) { - // Include the disabled video m-line from the offer - parsedSdp.m.splice(DIRECT_CALL_SDP_M_LINE_INDEX.video, 0, _disabledVideoMline); - _disabledVideoMline = null; - } - } - if (_desktopPc) { - var desktopSdp = _desktopPc.localDescription.sdp; - if (desktopSdp) { - if (!type) { - type = _desktopPc.localDescription.type; - } - desktopSdp = sdpParser.parse(desktopSdp); - desktopSdp = sdpParser.verifyFingerprintInMLines(desktopSdp); - - /////////////////////////////////////////////////////////////////////////////////////////// - // WORKAROUND!!! - // To ensure better screenshare quality for calls that go through the media server, we need to - // allow only relay candidates from TCP or TLS TURN Servers. - var removeUdpVideoIce = !_isDirectCall && !RtcSessionController.allowAllIceCandidatesScreenShare; - logger.info('[RtcSessionController]: Remove UDP video candidates for screenshare: ', removeUdpVideoIce); - - var trickleIce = useTrickleIce(type) && !_desktopPc.endOfCandidates; - sdpParser.validateIceCandidates(desktopSdp, removeUdpVideoIce, trickleIce); - - if (parsedSdp) { - parsedSdp.m = parsedSdp.m.concat(desktopSdp.m); - } else { - parsedSdp = desktopSdp; - } - } - } - return {type: type, parsedSdp: parsedSdp}; - } - - function isQosRequired(isRenegotiation) { - return (isRenegotiation || - // Make sure that we have offer/answer exchanged before generating QoS reports - _sdpStatus === SdpStatus.AnswerReceived || - _sdpStatus === SdpStatus.PrAnswerReceived || - _sdpStatus === SdpStatus.AnswerApplied || - _sdpStatus === SdpStatus.AnswerSent || - _sdpStatus === SdpStatus.Connected || - _sdpStatus === SdpStatus.None /*Terminated*/); - } - - function stopStats(callStats, isRenegotiation, publishQos) { - if (callStats) { - callStats.stop() - .then(function (qos) { - if (publishQos && isQosRequired(isRenegotiation) && qos) { - // By the time the qos available, the _renegotiationInProgress flag is already cleared, - // that's why we need to pass this isRenegotiation parameter around - sendQosAvailable(qos, isRenegotiation, callStats.getLastSavedStats()); - } - if (!isRenegotiation) { - _that.onQosAvailable = null; - } - }); - } else if (!isRenegotiation) { - _that.onQosAvailable = null; - } - } - - ///////////////////////////////////////////////////////////////////////////// - // Event Handlers - ///////////////////////////////////////////////////////////////////////////// - var _eventList = [ - 'onIceCandidate', - 'onSessionDescription', - 'onSdpConnected', - 'onIceConnected', - 'onIceDisconnected', - 'onRemoteVideoAdded', - 'onRemoteVideoRemoved', - 'onRemoteStreamUpdated', - 'onDTMFToneChange', - 'onClosed', - 'onRtcError', - 'onRtcWarning', - 'onLocalStreamEnded', - 'onScreenSharePointerStatus', - // CallStats events - 'onMediaUpdate', - 'onLocalVideoURL', - 'onRemoteObjectURL', - 'onQosAvailable', - 'onStatsNoOutgoingPackets', - 'onStatsThresholdExceeded', - 'onNetworkQuality' - ]; - - // Initialize event handlers - _eventList.forEach(function (eventName) { - _that[eventName] = null; - }); - - function unregisterEventHandlers(exceptionList) { - exceptionList = exceptionList || []; - _eventList.forEach(function (eventName) { - if (!exceptionList.includes(eventName)) { - _that[eventName] = null; - } - }); - } - - ///////////////////////////////////////////////////////////////////////////// - // Public interfaces - ///////////////////////////////////////////////////////////////////////////// - this.setTurnCredentials = function (credentials) { - _turnCredentials = credentials || {}; - }; - - this.setTurnUris = function (uris) { - if (!uris || !uris.length) { - return; - } - _turnUris = uris; - }; - - this.getLocalVideoUrl = function () { - // The local desktop (screen share) URL has precedence over the local video URL - return _localDesktopUrl || _localVideoUrl; - }; - - this.getLocalDesktopUrl = function () { - return _localDesktopUrl; - }; - - this.getRemoteAudioUrl = function () { return _remoteAudioUrl; }; - - this.getRemoteVideoUrls = function () { return _remoteVideoUrls; }; - - this.getSdpStatus = function () { return _sdpStatus; }; - - // This is the SDP that has been passed as input to the warmup function - this.getWarmedUpSdp = function () { return _warmedUpSdp; }; - - this.isHolding = function () { - return (_holding && !_holdInProgress) || _retrieveInProgress; - }; - - this.isHoldInProgress = function () { - return _holdInProgress; - }; - - this.isRetrieveInProgress = function () { - return _retrieveInProgress; - }; - - this.isHeld = function () { return _held; }; - - this.getActiveMediaType = function () { return Utils.shallowCopy(_activeMediaType); }; - - this.getMediaConstraints = function () { return Utils.shallowCopy(_mediaConstraints); }; - - this.getLocalMediaType = function () { - // This is supposed to be the intersection of media constraints and the - // active media type. - return { - audio: _mediaConstraints.audio && _activeMediaType.audio, - video: _mediaConstraints.video && _activeMediaType.video, - desktop: _mediaConstraints.desktop && _activeMediaType.video - }; - }; - - this.hasScreenShare = function () { - return !!_localDesktopUrl; - }; - - this.warmup = function (mediaType, remoteSdp, successCb, errorCb, screenConstraint) { - successCb = successCb || function () {}; - errorCb = errorCb || function () {}; - - if (_sessionTerminated) { - logger.error('[RtcSessionController]: Warmup Connection failed: Session already terminated'); - sendAsyncCallback(errorCb, 'res_UnexpectedError'); - return; - } - - if (sdpParser.isNoOfferSdp(remoteSdp)) { - remoteSdp = null; - } - // This function is used to 'warmup' the RTCPeerConnection prior to starting it. - // This is the only function which uses success and error callbacks. All other - // functions will asynchronously raise an onError event in case of errors. - mediaType = normalizeMediaType(mediaType); - - logger.info('[RtcSessionController]: Warming up the connection - mediaType = ', mediaType); - - if (!WebRTCAdapter.enabled) { - logger.warn('[RtcSessionController]: WebRTC not supported by browser'); - sendAsyncCallback(errorCb, 'res_NoWebRTC'); - return; - } - - if (_sdpStatus !== SdpStatus.None) { - logger.error('[RtcSessionController]: The connection has already been started'); - sendAsyncCallback(errorCb, 'res_UnexpectedError'); - return; - } - - _mediaConstraints = mediaType; - if (!_mediaConstraints.audio) { - _isMuted = true; - } - - getUserMedia(function () { - // Successfully got access to media input devices - if (remoteSdp) { - _warmedUpSdp = remoteSdp; - } - _warmedUp = true; - createPeerConnection(); // Create the peer connection(s) here so they can pre-allocate ICE candidates (before setLocalDescription) - successCb(); - }, function (err) { - handleFailure(err, errorCb); - }, screenConstraint); - }; - - this.start = function (mediaType, remoteSdp) { - if (_sessionTerminated) { - logger.error('[RtcSessionController]: Start Connection failed: Session already terminated'); - return false; - } - mediaType = normalizeMediaType(mediaType); - logger.info('[RtcSessionController]: Start Connection - mediaType =', mediaType); - - if (!WebRTCAdapter.enabled) { - logger.warn('[RtcSessionController]: WebRTC not supported by browser'); - sendAsyncError('res_NoWebRTC'); - return false; - } - - if (_sdpStatus !== SdpStatus.None) { - logger.error('[RtcSessionController]: The connection has already been started'); - sendAsyncError('res_UnexpectedError'); - return false; - } - - logger.debug('[RtcSessionController]: Creating Peer Connection...'); - - if (_warmedUp) { - // The connection has already been warmed up - remoteSdp = _warmedUpSdp; - _warmedUpSdp = null; - - // all participants in a large conference are muted on start - _isMuted = !mediaType.audio || _largeConference; - if (!createPeerConnection()) { - sendAsyncError('res_RTCError'); - return false; - } - if (mediaType.audio === _mediaConstraints.audio && - mediaType.video === _mediaConstraints.video && - mediaType.hdVideo === _mediaConstraints.hdVideo && - mediaType.desktop === _mediaConstraints.desktop) { - // We can continue using the MediaStream object allocated during the warmup. - startConn(remoteSdp); - return true; - } - } - - if (!createPeerConnection()) { - sendAsyncError('res_RTCError'); - return false; - } - - _mediaConstraints = mediaType; - - // Get access to local media - getUserMedia(function () { - // Successfully got access to media input devices - startConn(remoteSdp); - }, handleFailure); - - return true; - }; - - // The following function returns false in case of collision or improper call state. - // Otherwise, it returns true. - - this.addIceCandidates = function (origin, candidates) { - logger.info('[RtcSessionController]: Add remote ICE candidates'); - var pc = _pc || _desktopPc; - if (pc) { - var sdpOrigin = pc.remoteOrigin; - if (!origin || origin === sdpOrigin) { - candidates.forEach(function (candidate) { - candidate = JSON.parse(candidate); - if (candidate.candidate) { - candidate.candidate = candidate.candidate.replace(/(a=)*([^\r]+)(\r\n)*/i, '$2'); - } - logger.debug('[RtcSessionController]: Remote ICE candidate', candidate); - if (!candidate.candidate || candidate.candidate === 'end-of-candidates') { - logger.debug('[RtcSessionController]: Ignoring end-of-remote-candidates indication.'); - } else { - _pc.addIceCandidate(candidate); - } - }); - waitConnectedState(); - } else { - logger.warn('[RtcSessionController]: Ignoring remote ICE candidate - origin does not match.'); - logger.debug('[RtcSessionController]: ICE candidate origin: ', origin); - logger.debug('[RtcSessionController]: SDP origin: ', sdpOrigin); - } - } else { - logger.warn('[RtcSessionController]: Ignoring remote ICE candidate - no peer connection found.'); - } - }; - - ///////////////////////////////////////////////////////////////////////////// - // The following functions will trigger a new media renegotiation from - // the client. All these functions are supposed to returns false in case - // of collision or improper call state. Otherwise, they must returns true - // and must raise an onRtcError event in case the renegotiation cannot be - // completed. - - this.setRemoteDescription = function (remoteSdp, cb) { - logger.info('[RtcSessionController]: Set Remote Description - type: ', remoteSdp.type); - - // Make sure the warmed-up SDP is null - _warmedUpSdp = null; - - if (_sdpStatus === SdpStatus.AnswerReceived) { - logger.info('[RtcSessionController]: Still processing the previous answer remote description. Queue the new one'); - _pendingRemoteSdp = { - sdp: remoteSdp, - cb: cb - }; - return; - } - - if (_sdpStatus === SdpStatus.PrAnswerReceived) { - // We can't reapply it so just ignore it. - if (remoteSdp.type === 'answer') { - setSdpStatus(SdpStatus.AnswerReceived); - waitConnectedState(); - } - logger.info('[RtcSessionController]: SDP already applied. Ignoring any later remote SDPs'); - sendAsyncCallback(cb); - return; - } - - if (remoteSdp.type === 'offer') { - // This is a media renegotiation started by the remote party. - // Check the connection state. - if (!isConnStable()) { - sendAsyncCallback(cb, 'Connection not stable'); - return; - } - - startRenegotiation(false, cb); - getUserMedia(function () { - setRemoteDescription(remoteSdp); - }, handleFailure); - } else { - // This is the SDP Answer for the initial call setup or for a media - // renegotiation started by the client. - - if (remoteSdp.sdp === 'sdp' || remoteSdp.sdp === 'data') { - // This is a dummy SDP answer from the mock server. - // If this is a media renegotiation just mark the renegotiation as successful. - setSdpStatus(SdpStatus.AnswerReceived); - _renegotiationInProgress && renegotiationSuccessful(); - waitConnectedState(); - } else { - setRemoteDescription(remoteSdp); - } - sendAsyncCallback(cb); - } - }; - - /** - * Start a media renegotiation from the client (i.e. send a new SDP Offer from the client). - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return True if the request is accepted; False otherwise. - */ - this.renegotiateMedia = function (cb) { - logger.info('[RtcSessionController]: Start Media Renegotiation from client'); - if (!isConnStable()) { - sendAsyncCallback(cb, 'Connection not stable'); - } - startRenegotiation(true, cb); - getUserMedia(renegotiateMedia, handleFailure); - }; - - this.enableRemoteVideo = function () { - if (_remoteVideoDisabled) { - logger.info('[RtcSessionController]: Enable receiving remote video'); - _remoteVideoDisabled = false; - } - }; - - this.disableRemoteVideo = function () { - if (!_remoteVideoDisabled) { - logger.info('[RtcSessionController]: Disable receiving remote video'); - _remoteVideoDisabled = true; - } - }; - - /** - * Add audio to the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.addAudio = function (cb) { - logger.info('[RtcSessionController]: Add Audio'); - changeMedia({audio: true}, cb); - }; - - /** - * Remove audio from the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.removeAudio = function (cb) { - logger.info('[RtcSessionController]: Remove Audio'); - changeMedia({audio: false}, cb); - }; - - /** - * Add video to the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.addVideo = function (cb) { - logger.info('[RtcSessionController]: Add Video'); - changeMedia({video: true}, cb); - }; - - /** - * Remove video from the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.removeVideo = function (cb) { - logger.info('[RtcSessionController]: Remove Video'); - changeMedia({video: false}, cb); - }; - - /** - * Add screen-share to the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.addDesktop = function (cb) { - logger.info('[RtcSessionController]: Add Desktop (screen sharing)'); - changeMedia({desktop: true}, cb); - }; - - /** - * Remove screen-share from the existing connection. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.removeDesktop = function (cb) { - logger.info('[RtcSessionController]: Remove Desktop (screen sharing)'); - changeMedia({desktop: false}, cb); - }; - - /** - * Changes the media type for the existing connection. This function initiates a new media renegotiation. - * - * @param {Object} mediaType The media type(s) which are supposed to be added or removed. For instance, use {video: true} to add video and {video: false} to remove it. - * @param {Boolean} changeDesktopMedia Indicates if the desktop media also needs to change or not. - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @param {String} screenConstraint Screenshare parameters. - * @return undefined. - */ - this.changeMediaType = function (mediaType, changeDesktopMedia, cb, screenConstraint) { - logger.info('[RtcSessionController]: Change media type: ', mediaType); - if (!mediaType) { - logger.warn('[RtcSessionController]: Invalid media type'); - sendAsyncCallback(cb, 'Invalid media type'); - return; - } - _selectNewDesktopMediaInProgress = !!changeDesktopMedia; - changeMedia(mediaType, cb, screenConstraint); - }; - - /** - * Hold the existing call. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.holdCall = function (cb) { - logger.info('[RtcSessionController]: Hold Call'); - if (!isConnStable()) { - sendAsyncCallback(cb, 'Connection not stable'); - return; - } - - if (_holding) { - logger.warn('[RtcSessionController]: The call is already held'); - sendAsyncCallback(cb); - return; - } - - // Set _holding to true so we update the SDPs correctly - _holding = true; - // Set _holdInProgress to true so we can change _holding back to false in case the renegotiation fails. - _holdInProgress = true; - - startRenegotiation(true, cb); - getUserMedia(renegotiateMedia, handleFailure); - }; - - /** - * Retrieves the existing call. This function initiates a new media renegotiation. - * - * @param {Function} [cb] Callback function that is invoked when media renegotiation has finished. - * @param {Boolean} cb.success Indicates whether or not the media renegotiation was successful. - * @return undefined. - */ - this.retrieveCall = function (cb) { - logger.info('[RtcSessionController]: Retrieve Call'); - - if (!isConnStable()) { - sendAsyncCallback(cb, 'Connection not stable'); - return; - } - if (!_holding) { - logger.warn('[RtcSessionController]: The call is not held'); - sendAsyncCallback(cb); - return; - } - - // Set _holding to false so we update the SDPs correctly - _holding = false; - // Set _retrieveInProgress to true so we can change _holding back to true in case the renegotiation fails. - _retrieveInProgress = true; - - startRenegotiation(true, cb); - getUserMedia(renegotiateMedia, handleFailure); - return; - }; - - // - // End of media renegotiation functions - ///////////////////////////////////////////////////////////////////////////// - - this.renegotiationFailed = function (error) { - if (_renegotiationInProgress) { - if (!_renegotiationCb) { - sendError('res_RTCError'); - } - renegotiationFailed(error); - } - }; - - this.mute = function (cb) { - logger.info('[RtcSessionController]: Mute Call'); - enableAudioTrack(false); - sendAsyncCallback(cb); - }; - - this.unmute = function (cb) { - logger.info('[RtcSessionController]: Unmute Call'); - enableAudioTrack(true); - sendAsyncCallback(cb); - }; - - this.isMuted = function () { - return _isMuted; - }; - - this.isLocalMuteAllowed = function () { - return _isMuted || (_localStreams[LOCAL_AUDIO_VIDEO] && _localStreams[LOCAL_AUDIO_VIDEO].getAudioTracks().length); - }; - - /*** - * Use this method to set options for call stats collection (e.g.: if it's a voicemail call, there's no incoming packets) - */ - this.setCallStatsOptions = function (options) { - if (_callStats && options) { - logger.debug('[RtcSessionController]: Setting call stats options: ', options); - _callStats.setOptions(options); - } - }; - - this.terminate = function () { - logger.info('[RtcSessionController]: Terminate the RTC session controller'); - close(); - }; - - this.canSendDTMFDigits = function () { - if (!_dtmfSender && !enableDTMFSender()) { - return false; - } - return _dtmfSender && _dtmfSender.canInsertDTMF; - }; - - // when a key is pressed - this.sendDTMFDigits = function (digits) { - logger.info('[RtcSessionController]: Send DTMF Digits - digits=', digits); - - if (!isConnStable()) { - return false; - } - - if (!_dtmfSender && !enableDTMFSender()) { - return false; - } - - if (!_dtmfSender.canInsertDTMF) { - logger.error('[RtcSessionController]: DTMF Sender cannot insert DTMF digits: ' + digits); - return false; - } - - _dtmfSender.insertDTMF(digits, 200, 50); - return true; - }; - - this.setMockedCall = function () { - _isMockedCall = true; - }; - - // The following API is being provided exclusively to allow access to the RTCPeerConnection - // object to the Selenium testing framework for E2E automation test scenarios. - // This API SHALL NOT be used by the EVO/IMP client itself. - this.getCurrentPeerConnection = function () { - return _pc.mainPeerConnection; - }; - - this.setExtraVideoChannelsForParticipants = function (numConvParticipants) { - var extra = 0; - if (numConvParticipants && numConvParticipants > 2) { - // We don't receive a video stream for the local user and we already have one main - // video stream which will be used by one participant. Therefore, the number of extra - // channels is the number of participants minus 2. - extra = numConvParticipants - 2; - extra = Math.max(extra, MIN_VIDEO_EXTRA_CHANNELS); - extra = Math.min(extra, MAX_VIDEO_EXTRA_CHANNELS); - } - return setExtraVideoChannels(extra); - }; - - this.setExtraVideoChannels = function (extra) { - extra = extra || 0; - if (extra > 0) { - extra = Math.max(extra, MIN_VIDEO_EXTRA_CHANNELS); - extra = Math.min(extra, MAX_VIDEO_EXTRA_CHANNELS); - } - return setExtraVideoChannels(extra); - }; - - this.useMaxNumberOfExtraVideoChannels = function () { - return setExtraVideoChannels(MAX_VIDEO_EXTRA_CHANNELS); - }; - - this.getNumberOfExtraVideoChannels = function () { - return _numberOfExtraVideoChannels; - }; - - this.hasMaxExtraVideoChannels = function () { - if (Utils.isMobile()) { - // Number of extra video channels for mobile clients is 0 (zero) - return true; - } - - return (_numberOfExtraVideoChannels === MAX_VIDEO_EXTRA_CHANNELS); - }; - - this.isConnStable = function () { - return isConnStable(); - }; - - this.isConnected = function () { - return _sdpStatus === SdpStatus.Connected || _renegotiationInProgress; - }; - - this.disableIncomingVideo = function () { - _remoteStreams.forEach(function (s) { - if (s.getVideoTracks().length > 0) { - s.getVideoTracks()[0].enabled = false; - } - }); - }; - - this.enableIncomingVideo = function () { - _remoteStreams.forEach(function (s) { - if (s.getVideoTracks().length > 0) { - s.getVideoTracks()[0].enabled = true; - } - }); - }; - - this.getLocalStream = function (id) { - return _localStreams[id]; - }; - - this.setLocalStream = function (id, stream) { - _localStreams[id] = stream; - }; - - this.getRemoteStreams = function () { - return _remoteStreams; - }; - - this.setIgnoreTurnOnNextPC = function () { - logger.debug('[RtcSessionController]: setIgnoreTurnOnNextPC...'); - _ignoreTurnOnNextPC = true; - }; - - this.setIgnoreNextConnection = function () { - logger.debug('[RtcSessionController]: setIgnoreNextConnection...'); - _ignoreNextConnection = true; - }; - - this.getLastSavedStats = function () { - var stats = _oldCallStats || _callStats; - if (stats) { - return stats.getLastSavedStats(); - } - }; - - logger.debug('[RtcSessionController]: Created new RtcSessionController instance'); - } - - // RTC session controller constants - RtcSessionController.DEFAULT_CANDIDATES_TIMEOUT = DEFAULT_CANDIDATES_TIMEOUT; - RtcSessionController.DEFAULT_TRICKLE_ICE_TIMEOUT = DEFAULT_TRICKLE_ICE_TIMEOUT; - RtcSessionController.DEFAULT_ENABLE_AUDIO_AGC = DEFAULT_ENABLE_AUDIO_AGC; - RtcSessionController.DEFAULT_UPSCALE_FACTOR = DEFAULT_UPSCALE_FACTOR; - RtcSessionController.DEFAULT_SDP_PARAMS = DEFAULT_SDP_PARAMS; - RtcSessionController.DEFAULT_XGOOGLE = DEFAULT_XGOOGLE; - RtcSessionController.LOCAL_STREAMS = LOCAL_STREAMS; - - // RTC session controller settings which may be controlled by the application - RtcSessionController.candidatesTimeout = DEFAULT_CANDIDATES_TIMEOUT; // Timeout waiting for candidates when Trickle ICE is not enabled - RtcSessionController.trickleIceTimeout = DEFAULT_TRICKLE_ICE_TIMEOUT; // Timeout waiting for candidates when Trickle ICE is enabled - RtcSessionController.enableAudioAGC = DEFAULT_ENABLE_AUDIO_AGC; - RtcSessionController.screenshareUpscaleFactor = DEFAULT_UPSCALE_FACTOR; - RtcSessionController.cmrScreenshareUpscaleFactor = DEFAULT_UPSCALE_FACTOR; - RtcSessionController.allowAllIceCandidatesScreenShare = true; - RtcSessionController.disableTrickleIce = false; - RtcSessionController.allowOnlyRelayCandidates = false; - RtcSessionController.mediaNode = null; - RtcSessionController.playbackDevices = []; - RtcSessionController.recordingDevices = []; - RtcSessionController.ringingDevices = []; - RtcSessionController.videoDevices = []; - RtcSessionController.sdpParameters = Utils.shallowCopy(DEFAULT_SDP_PARAMS); - RtcSessionController.xGoogle = Utils.shallowCopy(DEFAULT_XGOOGLE); - - // Allow application to disable separate media line for screenshare - RtcSessionController.disableDesktopPc = false; - - // Process the client settings returned by the access server - RtcSessionController.processClientSettings = function (settings) { - settings = settings || {}; - - if (settings.allowAllIceCandidatesScreenShare !== undefined) { - RtcSessionController.allowAllIceCandidatesScreenShare = !!settings.allowAllIceCandidatesScreenShare; - } - if (settings.disableTrickleIce !== undefined) { - RtcSessionController.disableTrickleIce = !!settings.disableTrickleIce; - } - - RtcSessionController.screenshareUpscaleFactor = parseFloat(settings.screenshareUpscaleFactor, 10) || - RtcSessionController.DEFAULT_UPSCALE_FACTOR; - - RtcSessionController.cmrScreenshareUpscaleFactor = parseFloat(settings.cmrScreenshareUpscaleFactor, 10) || - RtcSessionController.DEFAULT_UPSCALE_FACTOR; - - var xGoogle = settings.xGoogle || {}; - ['video', 'hdVideo', 'screenShare'].forEach(function (mediaType) { - RtcSessionController.xGoogle[mediaType] = (xGoogle[mediaType] !== undefined) ? - xGoogle[mediaType] : RtcSessionController.DEFAULT_XGOOGLE[mediaType]; - }); - - logger.info('[RtcSessionController]: Updated RtcSessionController values: ', { - allowAllIceCandidatesScreenShare: RtcSessionController.allowAllIceCandidatesScreenShare, - disableTrickleIce: RtcSessionController.disableTrickleIce, - screenshareUpscaleFactor: RtcSessionController.screenshareUpscaleFactor, - cmrScreenshareUpscaleFactor: RtcSessionController.cmrScreenshareUpscaleFactor, - xGoogle: RtcSessionController.xGoogle - }); - }; - - // Exports - circuit.RtcSessionController = RtcSessionController; - circuit.Enums = circuit.Enums || {}; - circuit.Enums.SdpStatus = SdpStatus; - - return circuit; - -})(Circuit || {}); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var AtcCallInfo = circuit.AtcCallInfo; - var BaseCall = circuit.BaseCall; - var CallState = circuit.Enums.CallState; - var CstaCallState = circuit.Enums.CstaCallState; - var logger = circuit.logger; - var Utils = circuit.Utils; - - function AtcRemoteCall(conversation) { - if (!conversation || !conversation.rtcSessionId) { - throw new Error('Cannot create AtcRemoteCall object without a valid conversation'); - } - - // Call the base constructor - AtcRemoteCall.parent.constructor.call(this, conversation, true); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Properties - /////////////////////////////////////////////////////////////////////////////////////// - // Define the read-only properties to access the internal variables - Object.defineProperties(this, { - isPullAllowed: { - get: function () { - return this.atcCallInfo.isHandoverAllowed() && - this.atcCallInfo.cstaState !== CstaCallState.Ringing; - }, - enumerable: true, - configurable: false - } - }); - this.isAtcRemote = true; - this.atcCallInfo = new AtcCallInfo(); - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////////////////// - conversation = null; // Remove reference to conversation object - - logger.info('[AtcRemoteCall]: Created new ATC remote call with callID = ', this.callId); - } - - Utils.inherit(AtcRemoteCall, BaseCall); - - AtcRemoteCall.prototype.isPresent = function () { - return (!this.checkState([CallState.Idle, CallState.Terminated]) || - !this.checkCstaState([CstaCallState.Idle, CstaCallState.Failed, CstaCallState.TransferFailed, CstaCallState.Terminated])); - }; - - AtcRemoteCall.prototype.isDtmfAllowed = function () { - return this.checkState(CallState.ActiveRemote) && this.checkCstaState(CstaCallState.Active); - }; - - // Exports - circuit.AtcRemoteCall = AtcRemoteCall; - - return circuit; - -})(Circuit || {}); - -// Define global variables for JSHint -/*global atob, Blob, document, FileReader, Image, Promise, Uint8Array, XMLHttpRequest*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - var THUMB_BACKGROUND_COLOR = '#f0f0f0'; // Light-gray. Is needed for images with transparency. - var MAX_FILE_COUNT = 10; - var IMAGE_SMALL_HEIGHT = 312; - var IMAGE_SMALL_WIDTH = 484; - var COMPRESSED_IMG_PREFIX = 'tHuMbNaIl___'; // keep in sync with definition in app - - var UploadPromise; - if (typeof Promise !== 'undefined') { - // By default use the standard Promise object if supported by the browser - UploadPromise = Promise; - } - - /////////////////////////////////////////////////////////////////////////// - // Helper functions - /////////////////////////////////////////////////////////////////////////// - // Resize image to a Circuit thumbnail size - function resizePicture(image) { - var imgWidth, imgHeight; - imgHeight = image.height; - imgWidth = image.width; - - if ((imgHeight / imgWidth) > (IMAGE_SMALL_HEIGHT / IMAGE_SMALL_WIDTH)) { - if (image.height > IMAGE_SMALL_HEIGHT) { - imgWidth = imgWidth * IMAGE_SMALL_HEIGHT / imgHeight; - imgHeight = IMAGE_SMALL_HEIGHT; - } - } else if (imgWidth > IMAGE_SMALL_WIDTH) { - imgHeight = imgHeight * IMAGE_SMALL_WIDTH / imgWidth; - imgWidth = IMAGE_SMALL_WIDTH; - } - - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext('2d'); - canvas.width = imgWidth; - canvas.height = imgHeight; - ctx.fillStyle = THUMB_BACKGROUND_COLOR; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, imgWidth, imgHeight); // draw canvas image - return canvas.toDataURL('image/jpeg'); // Convert canvas to jpeg url - } - - // Create a blob from a dataURI - function dataURItoBlob(dataURI) { - var binary = atob(dataURI.split(',')[1]); - var array = []; - for (var i = 0; i < binary.length; i++) { - array.push(binary.charCodeAt(i)); - } - return new Blob([new Uint8Array(array)], {type: 'image/jpeg'}); - } - - // Create a small image (thumbnail). Returns a promise. - function createThumbnail(image) { - return new UploadPromise(function (resolve/*, reject*/) { - var reader = new FileReader(); - reader.onload = function (e) { - var img = new Image(); - img.onload = function () { - var resizeImg = resizePicture(this); - var blob = dataURItoBlob(resizeImg); - blob.name = COMPRESSED_IMG_PREFIX + (image.modifiedName || image.name); - blob.lastModified = Math.round(Date.now() / 1000); - resolve(blob); - }; - img.src = e.target.result; - }; - reader.readAsDataURL(image); - }); - } - - /////////////////////////////////////////////////////////////////////////// - // FileUpload - /////////////////////////////////////////////////////////////////////////// - function FileUpload(config) { - - /////////////////////////////////////////////////////////////////////////// - // Local variables and functions - /////////////////////////////////////////////////////////////////////////// - var _domain = config ? config.domain : null; - var _nextReqId = 1; - var _pendingUploads = {}; - - /////////////////////////////////////////////////////////////////////////// - // Local functions - /////////////////////////////////////////////////////////////////////////// - - function deleteFiles(deleteUrls) { - deleteUrls && deleteUrls.forEach(function (url) { - var req = new XMLHttpRequest(); - req.withCredentials = true; - req.open('DELETE', url); - req.send(); - }); - } - - // Upload a single file. Returns a promise. - function uploadFile(reqId, file, itemId, urlPrefix, onProgress) { - var clearPendingReq = function () { - if (_pendingUploads[reqId]) { - _pendingUploads[reqId].xhr = null; - } - }; - - return new UploadPromise(function (resolve, reject) { - var fileName = file.modifiedName || file.name; - - var req = new XMLHttpRequest(); - req.withCredentials = true; - req.open('POST', urlPrefix + '/fileapi?itemid=' + (itemId || 'NULL'), true); - req.setRequestHeader('Content-Type', file.type); - req.setRequestHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(fileName) + '"'); - - if (typeof onProgress === 'function') { - req.upload.onprogress = function (event) { - if (event.total > 0) { - onProgress(event.loaded, event.total, fileName); - } - }; - } - - req.onload = function () { - clearPendingReq(); - if (req.status === 200) { - resolve(req.response); - } else { - reject({ - filename: fileName, - status: req.status, - statusText: req.statusText, - response: req.response, - responseText: req.responseText - }); - } - }; - - req.onerror = function () { - clearPendingReq(); - reject({ - filename: fileName, - status: 500, - statusText: 'Connection Failed' - }); - }; - - req.onabort = function () { - clearPendingReq(); - reject({ - filename: fileName, - status: 0, - statusText: 'abort' - }); - }; - - _pendingUploads[reqId].xhr = req; - req.send(file); - }); - } - - /////////////////////////////////////////////////////////////////////////// - // APIs - /////////////////////////////////////////////////////////////////////////// - - // Upload multiple files including images. For images thumbnails are created - // if needed. Returns a promise. - // domain does not include the protocol. E.g. circuitsandbox.net - this.uploadFiles = function (files, domain, itemId, onProgress, noThumbnail) { - if (!UploadPromise) { - throw new Error('Promise support is required to use FileUpload'); - } - - if (files.length > MAX_FILE_COUNT) { - return UploadPromise.reject('Exceeded maximum of 10 files per message'); - } - - domain = domain || _domain; - - var urlPrefix = domain ? 'https://' + domain : ''; - var result = []; - var reqId = _nextReqId++; - - _pendingUploads[reqId] = { - xhr: null, - deleteUrls: [] - }; - - // Find large images that need to be resized - var resizePromises = []; - !noThumbnail && files.forEach(function (file) { - // Even create a thumbnail for small images - if (Utils.isSupportedImage && Utils.isSupportedImage(file.type)) { - resizePromises.push(createThumbnail(file)); - } - }); - - var uploadAllPromise = UploadPromise.all(resizePromises).then(function (blobs) { - // Add the blob's (thumbnails) to the list of files to be upoaded - Array.prototype.push.apply(files, blobs); - - // Sequentially (but async) upload the files. Once all files are - // uploaded, resolve the Promise passing the upload results. - itemId = itemId || null; - return files.reduce(function (sequence, file) { - return sequence.then(function () { - return uploadFile(reqId, file, itemId, urlPrefix, onProgress).then(function (res) { - var resp = JSON.parse(res)[0]; - itemId = itemId || resp.id; - var fileId = resp.fileid[resp.fileid.length - 1]; - var fileName = file.modifiedName || file.name; - var attachmentMetaData = { - fileId: fileId, - fileName: fileName, - itemId: itemId, - mimeType: resp.mimeType || file.type, - size: file.size, - url: urlPrefix + '/fileapi?fileid=' + fileId + '&itemid=' + itemId, - deleteUrl: urlPrefix + '/fileapi?fileid=' + fileId + '&itemid=' + itemId - }; - result.push(attachmentMetaData); - _pendingUploads[reqId].deleteUrls.push(attachmentMetaData.deleteUrl); - return result; - }); - }); - }, UploadPromise.resolve()); - }).then(function (files) { - delete _pendingUploads[reqId]; - return UploadPromise.resolve(files); - }, function (err) { - delete _pendingUploads[reqId]; - return UploadPromise.reject(err); - }); - - uploadAllPromise.__uploadId = reqId; - return uploadAllPromise; - }; - - // Aborts the upload for given promise - this.abort = function (promise) { - if (promise && promise.__uploadId && _pendingUploads[promise.__uploadId]) { - var pendingUpload = _pendingUploads[promise.__uploadId]; - delete _pendingUploads[promise.__uploadId]; - - // Abort ongoing upload and delete uploaded files (if any) - if (pendingUpload.xhr) { - pendingUpload.xhr.abort(); - } - deleteFiles(pendingUpload.deleteUrls); - } - }; - } - - FileUpload.COMPRESSED_IMG_PREFIX = COMPRESSED_IMG_PREFIX; - - FileUpload.overridePromise = function ($q) { - UploadPromise = $q; - }; - - // Exports - circuit.FileUpload = FileUpload; - - return circuit; - -})(Circuit || {}); - -// Define external globals for JSHint - -var Circuit = (function (circuit) { - 'use strict'; - - function BaseEventTarget(logger) { - var _listeners = {}; - - /////////////////////////////////////////////////////////////////////////// - // Public interfaces - /////////////////////////////////////////////////////////////////////////// - this.dispatch = function (event) { - var type = event && event.type; - if (!type) { - logger && logger.error('[EventTarget]: Attempted to dispatch a bad event'); - return; - } - - if (_listeners[type]) { - _listeners[type].forEach(function (listener) { - try { - listener(event); - } catch (e) { - logger && logger.error(e); - } - }); - } else { - logger && logger.debug('[EventTarget]: There is no listener for ' + type); - } - }; - - this.addEventListener = function (type, listener) { - if (!type || typeof listener !== 'function') { - logger && logger.error('[EventTarget]: addEventListener invoked with invalid arguments'); - return; - } - if (!_listeners[type]) { - _listeners[type] = []; - } - if (_listeners[type].indexOf(listener) >= 0) { - logger && logger.warning('[EventTarget]: Attempted to add duplicate event listener'); - return; - } - _listeners[type].push(listener); - logger && logger.debug('[EventTarget]: Added event listener for ' + this.name + - ', type = ' + type + ', count = ' + _listeners[type].length); - }; - - this.removeEventListener = function (type, listener) { - if (_listeners[type]) { - var idx = _listeners[type].indexOf(listener); - if (idx >= 0) { - _listeners[type].splice(idx, 1); - } - - logger && logger.debug('[EventTarget]: Removed event listener for ' + this.name + - ', type = ' + type + ', count = ' + _listeners[type].length); - } - }; - - this.hasEventListener = function (type) { - return !!_listeners[type] && _listeners[type].length > 0; - }; - - // Needed for unit tests, since multiple instances of angular services - // are created during test execution, so multiple listeners are added. - // Also used by the SDK to since multiple users are logged in. - // This API SHALL NOT be used by IMP client itself. - this.removeAllListeners = function () { - _listeners = {}; - }; - } - - BaseEventTarget.prototype.constructor = BaseEventTarget; - - // Helper function to create a generic event with no data - BaseEventTarget.prototype.createEvent = function (type) { - return {type: type}; - }; - - // Helper function to dispatch a generic event with no data - BaseEventTarget.prototype.dispatchEvent = function (type) { - var evt = this.createEvent(type); - this.dispatch(evt); - }; - - // Exports - circuit.BaseEventTarget = BaseEventTarget; - - return circuit; - -})(Circuit || {}); - -// Define external globals for JSHint -/*global __clientVersion, window*/ - -/////////////////////////////////////////////////////////////////////////////// -//--- Handles Connection to Client API Server (inherits from BaseEventTarget) --- -/////////////////////////////////////////////////////////////////////////////// - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var BaseEventTarget = circuit.BaseEventTarget; - var ConnectionState = circuit.Enums.ConnectionState; - var Constants = circuit.Constants; - var Proto = circuit.Proto; - var Utils = circuit.Utils; - - function ConnectionHandler(config) { - var logger = circuit.logger; - config = config || {}; - - // Call the base constructor - ConnectionHandler.parent.constructor.call(this, logger); - - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - var RESPONSE_TIMEOUT = 60000; // increased to 60s to conform real backend - var PING_TIMEOUT = 10000; - var DEFAULT_PING_INTERVAL = 300000; // 5 minutes - var MIN_PING_INTERVAL = 30000; // 30 seconds - - // Delayed applied to ensure that client will process response prior to event - // This is a workaround solution to address the issue that in most scenarios - // the backend sends the event before the response. - var EVENT_DELAY = 500; - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var _that = this; - var _evtCallbacks = {}; - var _reqCallbacks = {}; - var _delayedEvents = []; - - var _url = '/api'; - var _server = window.location.host; - var _accessToken = null; - var _clientInfo = null; - var _validDeviceTypes = Object.keys(Constants.DeviceType); - var _clientInfoTemplate = { - deviceType: true, - deviceSubtype: true, - manufacturer: true, - osVersion: true, - clientVersion: true, - hardwareModel: true, - capabilities: true - }; - var _clientApiVersion = 0; - - // Disable ping for mobile clients. - var _pingIntervalTime = (config.disablePing || Utils.isMobile()) ? -1 : DEFAULT_PING_INTERVAL; - var _pingInterval = null; - var _pingInProgress = false; - - - // Use the circuit reference here instead of defining it in the imports so - // that the Unit tests can mock the Connectioncontroller - var _connCtrl = new circuit.ConnectionController(config); - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function getUrl() { - var obj = {}; - _accessToken && (obj.accessToken = _accessToken); - if (_clientInfo) { - Object.assign(obj, _clientInfo); - } else if (typeof __clientVersion !== 'undefined') { - obj.clientVersion = __clientVersion; - } - return 'wss://' + _server + _url + (Object.keys(obj).length ? '?' + Utils.toQS(obj) : ''); - } - - - function addReqCallback(requestId, cb, keysToOmitFromResponse) { - if (!cb) { return; } - - var responseTimer = window.setTimeout(function (reqId) { - logger.error('[ConnectionHandler]: Timeout waiting for response. RequestId:', reqId); - if (_reqCallbacks[reqId]) { - delete _reqCallbacks[reqId]; - cb(Constants.ReturnCode.REQUEST_TIMEOUT); - - var numPending = Object.keys(_reqCallbacks).length; - logger.debug('[ConnectionHandler]: Remaining callbacks pending: ', numPending); - if (numPending === 0) { - raiseDelayedEvents(); - } - - // Check if websocket connection is still up - _that.pingServer(); - } - }, RESPONSE_TIMEOUT, requestId); - - if (!Array.isArray(keysToOmitFromResponse)) { - keysToOmitFromResponse = null; - } - - _reqCallbacks[requestId] = { - cb: cb, - timer: responseTimer, - keysToOmitFromResponse: keysToOmitFromResponse - }; - } - - function getCallback(msg) { - var cbInfo; - switch (msg.msgType) { - case Constants.WSMessageType.RESPONSE: - if (!msg.response || !msg.response.code) { - return null; - } - if (msg.response.user) { - var localUser; - if (msg.response.user.type === 'GET_STUFF') { - localUser = msg.response.user.getStuff.user; - } else if (msg.response.user.type === 'GET_LOGGED_ON') { - localUser = msg.response.user.getLoggedOn.user; - } - if (localUser) { - localUser.clientId = msg.clientId; - localUser.apiVersion = msg.apiVersion; - } - } - if (_reqCallbacks[msg.response.requestId]) { - cbInfo = _reqCallbacks[msg.response.requestId]; - delete _reqCallbacks[msg.response.requestId]; - - if (cbInfo.timer) { - window.clearTimeout(cbInfo.timer); - } - if (cbInfo.keysToOmitFromResponse) { - msg.keysToOmitFromLogging = cbInfo.keysToOmitFromResponse; - } - return { - type: 'ack', - cb: cbInfo.cb, - data: msg.response - }; - } - logger.debug('[ConnectionHandler]: No callback for response with requestId=', msg.response.requestId); - break; - - case Constants.WSMessageType.EVENT: - var evt = Proto.ParseEvent(msg.event); - if (!evt || !evt.name) { - logger.error('[ConnectionHandler]: Failed to parse event: ', msg.event); - } else if (_evtCallbacks[evt.name]) { - cbInfo = _evtCallbacks[evt.name]; - logger.debug('[ConnectionHandler]: Parsed event: ', evt.name); - - if (_that.ommitKeysFromLog && cbInfo.keysToOmitFromEvent) { - msg.keysToOmitFromLogging = cbInfo.keysToOmitFromEvent; - } - return { - type: 'evt', - cbList: cbInfo.cbList, - name: evt.name, - data: evt.data, - suppressLog: cbInfo.suppressLog - }; - } else { - logger.debug('[ConnectionHandler]: No registered callback for event ', evt.name); - } - break; - - default: - logger.error('[ConnectionHandler]: Unexpected message type: ', msg.msgType); - break; - } - return null; - } - - function raiseEvents(cbInfo) { - logger.debug('[ConnectionHandler]: Raising event', cbInfo.name); - cbInfo.cbList.forEach(function (cb) { - try { - cb(cbInfo.data); - } catch (e) { - logger.error('[ConnectionHandler]: Error raising event. ', e); - } - }); - } - - function raiseDelayedEvents() { - if (_delayedEvents.length > 0) { - logger.debug('[ConnectionHandler]: Raise all delayed events'); - _delayedEvents.forEach(function (cbInfo) { - window.clearTimeout(cbInfo.timeoutId); - raiseEvents(cbInfo); - }); - _delayedEvents = []; - } - } - - /////////////////////////////////////////////////////////////////////////// - // Event Handlers - /////////////////////////////////////////////////////////////////////////// - function onStateChange(evt) { - logger.debug('[ConnectionHandler]: WebSocket connection state changed to ', evt.newState); - logger.debug('[ConnectionHandler]: Raising connectionStateChange event'); - - if (evt.newState === ConnectionState.Connected) { - _clientApiVersion = 0; - if (!_pingInterval && _pingIntervalTime > 0) { - // Ping the server every interval time to detect network errors - _pingInterval = window.setInterval(function () { - _that.pingServer(); - }, _pingIntervalTime); - } - } else { - if (_pingInterval) { - window.clearInterval(_pingInterval); - _pingInterval = null; - } - // Whenever we lose connection, all pending requests will fail (since we're getting - // a new clientId). So fail all of them immediately (don't let them timeout). - if (Object.keys(_reqCallbacks).length) { - // Cleaning must be done only for pending callbacks, as _reqCallbacks will shortly store new callbacks. - var pendingCallbacks = _reqCallbacks; - _reqCallbacks = {}; // ready for new callbacks - window.setTimeout(function () { - Object.keys(pendingCallbacks).forEach(function (reqId) { - var cbInfo = pendingCallbacks[reqId]; - if (cbInfo.timer) { - window.clearTimeout(cbInfo.timer); - } - cbInfo.cb(Constants.ReturnCode.DISCONNECTED); - }); - raiseDelayedEvents(); - }, 0); - } - } - - // Dispatch a 'connectionStateChange' event - var newEvt = _that.createEvent('connectionStateChange'); - newEvt.newState = evt.newState; - newEvt.oldState = evt.oldState; - _that.dispatch(newEvt); - } - _connCtrl.onStateChange = onStateChange; - - function onReconnectFailed() { - logger.debug('[ConnectionHandler]: WebSocket failed to reconnect. Raising reconnectFailed event'); - - // Dispatch a 'reconnectFailed' event - var newEvt = _that.createEvent('reconnectFailed'); - _that.dispatch(newEvt); - } - _connCtrl.onReconnectFailed = onReconnectFailed; - - // Receive Client API Messages from Access Server - function onMessage(msg) { - if (!_clientApiVersion && msg.apiVersion) { - _clientApiVersion = Utils.convertVersionToNumber(msg.apiVersion); - } - - // First check if there are registered callbacks - var cbInfo = getCallback(msg); - if (!cbInfo) { - return; - } - if (!cbInfo.suppressLog) { - logger.msgRcvd('[ConnectionHandler]: ', msg); - } - if (cbInfo.type === 'evt') { - if (Object.keys(_reqCallbacks).length > 0 && /^(?:User|Conversation|Search)\./.test(cbInfo.name)) { - // The client is waiting for a response. Delay the event handling. - logger.debug('[ConnectionHandler]: Delay event:', cbInfo.name); - cbInfo.timeoutId = window.setTimeout(function () { - raiseEvents(cbInfo); - var idx = _delayedEvents.indexOf(cbInfo); - if (idx !== -1) { - _delayedEvents.splice(idx, 1); - } - }, EVENT_DELAY); - // Add cbInfo to delayed events list - _delayedEvents.push(cbInfo); - } else { - // There are no pending requests. Raise the event immediately. - raiseEvents(cbInfo); - } - } else { - var numPending = Object.keys(_reqCallbacks).length; - logger.debug('[ConnectionHandler]: Remaining callbacks pending: ', numPending); - - // Invoke the callback function (error is always the 1st parameter) - cbInfo.cb(null, cbInfo.data); - - if (numPending === 0) { - // If there were no pending callbacks BEFORE the response was processed, - // we can raise the delayed events. - raiseDelayedEvents(); - } - } - } - _connCtrl.onMessage = onMessage; - - ///////////////////////////////////////////////////////////////////////////// - // Public interfaces - ///////////////////////////////////////////////////////////////////////////// - this.ommitKeysFromLog = true; - this.onMessage = onMessage; - - this.setRelativeUrl = function (url) { - _url = url; - logger.debug('[ConnectionHandler]: Set relative URL to ', _url); - }; - - this.setTarget = function (server) { - // Used by Chrome App & mobile App to set the websocket target - _server = server || window.location.host; - logger.debug('[ConnectionHandler]: Set target to ', _server); - }; - - this.setClientInfo = function (clientInfo) { - // Used by Circuit Meeting Room and other apps to set the client info - if (clientInfo) { - if (!clientInfo.deviceSubtype || _validDeviceTypes.indexOf(clientInfo.deviceType) === -1) { - logger.warn('[ConnectionHandler]: Attempted to set invalid clientInfo: ', clientInfo); - return; - } - - _clientInfo = {}; - // Copy only valid fields - Object.keys(clientInfo).forEach(function (key) { - if (_clientInfoTemplate[key]) { - _clientInfo[key] = clientInfo[key]; - } - }); - logger.debug('[ConnectionHandler]: Set clientInfo to ', _clientInfo); - } else { - logger.debug('[ConnectionHandler]: Clear clientInfo'); - _clientInfo = null; - } - }; - - this.setToken = function (token) { - // Used by SDK - _accessToken = token; - }; - - this.getUrl = getUrl; - - this.getState = function () { - return _connCtrl.getState(); - }; - - this.connect = function (successCallback, errorCallback) { - logger.debug('[ConnectionHandler]: Connect WebSocket to Access Server'); - _connCtrl.connect(getUrl(), successCallback, errorCallback); - }; - - this.disconnect = function () { - logger.debug('[ConnectionHandler]: Disconnect WebSocket from Access Server'); - _connCtrl.disconnect(); - }; - - - this.reconnect = function (delay) { - logger.debug('[ConnectionHandler]: Reconnect WebSocket to Access Server.'); - _connCtrl.reconnect(getUrl(), delay); - }; - - this.sendMessage = function (data, cb, keysToOmitFromResponse) { - if (!data || !data.request || !data.request.requestId) { - logger.error('[ConnectionHandler]: Cannot send message. Invalid message: ', data); - cb && window.setTimeout(function () { cb(Constants.ReturnCode.INVALID_MESSAGE); }, 0); - return false; - } - - logger.debug('[ConnectionHandler]: Sending ' + data.request.type + ' request'); - - if (!_connCtrl.sendMessage(data)) { - logger.error('[ConnectionHandler]: Failed to send message'); - cb && window.setTimeout(function () { cb(Constants.ReturnCode.FAILED_TO_SEND); }, 0); - return false; - } - - // Callback must be added in the callback array only if message is actually sent through the connection, not before - cb && addReqCallback(data.request.requestId, cb, keysToOmitFromResponse); - return true; - }; - - this.pingServer = function () { - if (!_connCtrl.isConnected()) { - return; - } - if (_pingInProgress) { - // Don't send multiple pings to the server - return; - } - - var msg = 'PING'; - var reqId; - if (!Utils.isMobile()) { - reqId = ++Proto.requestNr; - msg += '|' + reqId; - } - - _connCtrl.sendMessage(msg); - - if (reqId) { - _pingInProgress = true; - - // Register callback and set timeout specific for PING messages - var responseTimer = window.setTimeout(function () { - _pingInProgress = false; - logger.error('[ConnectionHandler]: Timed out waiting for ping response. RequestId:', reqId); - delete _reqCallbacks[reqId]; - _connCtrl.reconnect(getUrl()); - }, PING_TIMEOUT); - - _reqCallbacks[reqId] = { - cb: function () { - _pingInProgress = false; - }, - timer: responseTimer - }; - } - }; - - this.setPingInterval = function (interval) { - if (typeof interval !== 'number') { - return; - } - // Normalize the interval time - interval = interval < 0 ? -1 : Math.max(Math.floor(interval), MIN_PING_INTERVAL); - - if (_pingIntervalTime === interval) { - // No changes - return; - } - _pingIntervalTime = interval; - - if (_pingInterval) { - window.clearInterval(_pingInterval); - _pingInterval = null; - } - - if (_pingIntervalTime < 0) { - logger.info('[ConnectionHandler]: Disable ping'); - } else { - logger.info('[ConnectionHandler]: Set ping interval time to ', _pingIntervalTime); - if (_connCtrl.isConnected()) { - logger.info('[ConnectionHandler]: Restart ping interval'); - _pingInterval = window.setInterval(function () { - _that.pingServer(); - }, _pingIntervalTime); - } - } - }; - - this.on = function (msgType, cb, keysToOmitFromEvent) { - if (!msgType || !cb) { - return; - } - - var cbInfo = _evtCallbacks[msgType] || {cbList: []}; - cbInfo.cbList.push(cb); - if (keysToOmitFromEvent) { - if (Array.isArray(keysToOmitFromEvent)) { - cbInfo.keysToOmitFromEvent = keysToOmitFromEvent; - } else if (keysToOmitFromEvent === '*') { - cbInfo.suppressLog = true; - } - } - _evtCallbacks[msgType] = cbInfo; - }; - } - - Utils.inherit(ConnectionHandler, BaseEventTarget); - ConnectionHandler.prototype.name = 'ConnectionHandler'; - - // Exports - circuit.ConnectionHandler = ConnectionHandler; - - return circuit; -})(Circuit); - -// Define external globals for JSHint -/*global window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var ConnectionHandler = circuit.ConnectionHandler; - var Constants = circuit.Constants; - var PrivateData = circuit.PrivateData; - var Proto = circuit.Proto; - var Utils = circuit.Utils; - - function ClientApiHandler(config) { - var logger = circuit.logger; - - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - var SP86_API_VERSION = 2090051; // 2.9.51-x - var SP88_API_VERSION = 2090058; // 2.9.58-x - var SP89_API_VERSION = 2090062; // 2.9.62-x - - var NOP = function () {}; - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var _connHandler = new ConnectionHandler(config); - var _developmentMode = false; // Assume production mode by default - var _lastError; - var _clientApiVersion = 0; - var _clientId; - var _userId; - var _connectedBackend; // Used for guest connections - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function sendRequest(contentType, content, cb, keysToOmitFromRequest, keysToOmitFromResponse, tenantContext) { - var msg = Proto.Request(contentType, content, tenantContext); - - if (_developmentMode) { - keysToOmitFromResponse = null; - } else if (keysToOmitFromRequest) { - msg.keysToOmitFromLogging = keysToOmitFromRequest; - } - - _connHandler.sendMessage(msg, function (err, msg) { - try { - cb(err, msg); - } catch (ex) { - logger.error('[ClientApiHandler]: Exception: ', ex); - } - }, keysToOmitFromResponse); - - return msg.request.requestId; - } - - function setError(code, errObj) { - if (!Circuit.Error) { - return; - } - _lastError = new Circuit.Error(code); - if (!errObj) { - return; - } - _lastError.errObj = errObj; - _lastError.message = errObj.info; - } - - function isResponseValid(err, rsp, cb, treatNoResultAsEmptyList) { - _lastError = null; - - if (err) { - setError(err); - cb(err); - return false; - } - if (rsp.code === Constants.ReturnCode.NO_RESULT) { - if (treatNoResultAsEmptyList) { - cb(null, []); - } else { - setError(Constants.ReturnCode.NO_RESULT); - cb(Constants.ReturnCode.NO_RESULT); - } - return false; - } - - var res = rsp.code; - var errObj; - - switch (rsp.code) { - case Constants.ReturnCode.OK: - case Constants.ReturnCode.ENTITY_ALREADY_EXISTS: - return true; - - case Constants.ReturnCode.CLOUD_TELEPHONY_EXCEPTION: - case Constants.ReturnCode.SERVICE_EXCEPTION: - if (rsp.errorInfo && rsp.errorInfo.errorCode) { - res = rsp.errorInfo.errorCode; - if (res === Constants.ErrorCode.THIRDPARTY_ERROR && rsp.errorInfo.thirdpartyError) { - res = rsp.errorInfo.thirdpartyError.type; - } - errObj = rsp.errorInfo; - } - break; - } - - res = res || Constants.ReturnCode.UNEXPECTED_RESPONSE; - setError(res, errObj); - cb(res); - return false; - } - - function sendAsyncResp(cb, error, data) { - error && setError(error); - if (cb && cb !== NOP) { - window.setTimeout(function () { - cb(error, data); - }, 0); - } - } - - function convertMediaType(mediaType) { - var rtMediaType = []; - if (mediaType) { - if (mediaType.audio) { - rtMediaType.push(Constants.RealtimeMediaType.AUDIO); - } - if (mediaType.video) { - rtMediaType.push(Constants.RealtimeMediaType.VIDEO); - } - if (mediaType.desktop) { - rtMediaType.push(Constants.RealtimeMediaType.DESKTOP_SHARING); - } - } else { - // Assume audio by default - rtMediaType.push(Constants.RealtimeMediaType.AUDIO); - } - return rtMediaType; - } - - function getAttachmentData(a) { - // Return only applicable fields for Attachment object in conversation_action.proto - return a && { - fileId: a.fileId, - fileName: a.fileName, - mimeType: a.mimeType, - size: a.size, - itemId: a.itemId - }; - } - - function getAttachmentsData(attachments) { - return attachments && attachments.map(getAttachmentData); - } - - function getExternalAttachmentData(a) { - // Return only applicable fields for ExternalAttachment object in conversation_action.proto - return a && { - attachment: getAttachmentData(a.attachment), - type: a.type, - downloadLocation: a.downloadLocation, - previewLocation: a.previewLocation, - shareLinkToken: a.shareLinkToken - }; - } - - function getExternalAttachmentsData(attachments) { - return attachments && attachments.map(getExternalAttachmentData); - } - - function createJoinData(data) { - var joinData = { - convId: data.convId, - rtcSessionId: data.rtcSessionId, - ownerId: data.ownerId, - sdp: data.sdp, - mediaType: convertMediaType(data.mediaType), - callOut: !!data.callOut, - handover: !!data.handover, - sendInviteCancel: !!data.sendInviteCancel, - peerUserId: data.peerUserId || undefined, // Direct Call Prototype - Not used by Client API - replaces: data.replaces, - from: (data.fromDn || data.fromName) ? {phoneNumber: data.fromDn, displayName: data.fromName, resolvedUserId: data.fromUserId} : undefined, - to: data.dialedDn ? {phoneNumber: data.dialedDn, displayName: data.toName, resolvedUserId: data.toUserId} : undefined, - isTelephonyConversation: !!data.isTelephonyConversation, - displayName: data.displayName, - transactionId: data.transactionId, - screenSharePointerSupported: data.screenSharePointerSupported, - asyncSupported: true, - noCallLog: data.noCallLog || undefined - }; - return joinData; - } - - function getApiVersion(cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getApiVersion...'); - - var request = { - type: Constants.VersionActionType.GET_VERSION - }; - sendRequest(Constants.ContentType.VERSION, request, function (err, rsp) { - if (!err && rsp.code === Constants.ReturnCode.OK) { - var apiVersion = rsp.version.getVersion.version; - - // Save the Client API version that we are connected to - logger.info('[ClientApiHandler]: Setting client API version to ', apiVersion); - _clientApiVersion = Utils.convertVersionToNumber(apiVersion); - } else { - // Something went wrong here - // The version will be initialized in the GET_STUFF response. - _clientApiVersion = 0; - } - cb(); - }); - } - - function getStuff(types, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getStuff...'); - - // Set all if request is empty - if (!types || !types.length) { - types = [ - Constants.GetStuffType.USER, - Constants.GetStuffType.ACCOUNTS, - Constants.GetStuffType.FIRST_WEB_LOGIN, - Constants.GetStuffType.PRESENCE_STATE, - Constants.GetStuffType.RECENT_SEARCHES, - Constants.GetStuffType.SETTINGS, - Constants.GetStuffType.SUPPORT_CONVERSATION_ID, - Constants.GetStuffType.TELEPHONY_CONVERSATION_ID, - Constants.GetStuffType.GLOBAL_PROPERTIES, - Constants.GetStuffType.TENANT, - Constants.GetStuffType.NOTIFICATION_SUBSCRIPTIONS, - Constants.GetStuffType.PENDING_SYSTEM_NOTIFICATIONS - ]; - } - - if (_clientApiVersion <= SP89_API_VERSION) { - logger.debug('[ClientApiHandler]: Backend does not support PENDING_SYSTEM_NOTIFICATIONS type'); - Utils.removeArrayElement(types, Constants.GetStuffType.PENDING_SYSTEM_NOTIFICATIONS); - } - - var request = { - type: Constants.UserActionType.GET_STUFF, - getStuff: { - types: types - } - }; - - var keysToOmitFromResponse = [ - 'response.user.getStuff.accounts.[].telephonyConfiguration.onsSipAuthenticationHash', - 'response.user.getStuff.accounts.[].telephonyConfiguration.ondSipAuthenticationHash', - 'response.user.getStuff.recentSearches', - 'response.user.getStuff.userPresenceState.longitude', - 'response.user.getStuff.userPresenceState.latitude', - 'response.user.getStuff.userPresenceState.locationText', - 'response.user.getStuff.userPresenceState.statusMessage' - ]; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var stuff = rsp.user.getStuff; - if (types.includes(Constants.GetStuffType.USER)) { - stuff.user.userPresenceState = stuff.userPresenceState; - stuff.user.accounts = stuff.accounts || []; - - // Update with API version returned in GET_STUFF - _clientApiVersion = Utils.convertVersionToNumber(stuff.user.apiVersion); - - // Save the connected clientId and userId - _clientId = stuff.user.clientId; - _userId = stuff.user.userId; - } - - cb(null, stuff); - } - }, null, keysToOmitFromResponse); - } - - /////////////////////////////////////////////////////////////////////////////// - // Public Interfaces - /////////////////////////////////////////////////////////////////////////////// - Object.defineProperties(this, { - clientId: { - enumerable: true, - get: function () { - return _clientId; - }, - set: function (clientId) { - // Only allow setting the clientId for Unit tests - if (clientId === 'simulatedDirect') { - _clientId = clientId; - } - } - }, - clientApiVersion: { - enumerable: true, - get: function () { - return _clientApiVersion; - } - } - }); - - this.getLastError = function () { - return _lastError; - }; - - this.connect = function (successCallback, errorCallback, server) { - server && _connHandler.setTarget(server); - _connHandler.connect(successCallback, errorCallback); - }; - - this.getConnState = _connHandler.getState.bind(_connHandler); - this.disconnect = _connHandler.disconnect.bind(_connHandler); - this.reconnect = _connHandler.reconnect.bind(_connHandler); - this.pingServer = _connHandler.pingServer.bind(_connHandler); - this.setPingInterval = _connHandler.setPingInterval.bind(_connHandler); - this.setTarget = _connHandler.setTarget.bind(_connHandler); - this.setClientInfo = _connHandler.setClientInfo.bind(_connHandler); - this.setToken = _connHandler.setToken.bind(_connHandler); - this.setRelativeUrl = _connHandler.setRelativeUrl.bind(_connHandler); - this.addEventListener = _connHandler.addEventListener.bind(_connHandler); - this.removeEventListener = _connHandler.removeEventListener.bind(_connHandler); - - this.setDevelopmentMode = function (developmentMode) { - _developmentMode = !!developmentMode; - _connHandler.ommitKeysFromLog = !developmentMode; - }; - - this.on = function (msgType, cb) { - var keysToOmitFromEvent; - switch (msgType) { - case 'Account.TELEPHONY_CONFIGURATION_UPDATED': - keysToOmitFromEvent = [ - 'event.account.telephonyConfigurationUpdated.configuration.onsSipAuthenticationHash', - 'event.account.telephonyConfigurationUpdated.configuration.ondSipAuthenticationHash' - ]; - break; - case 'Conversation.ADD_ITEM': - keysToOmitFromEvent = [ - 'event.conversation.addItem.item.text', - 'event.conversation.addItem.item.system.renamedConversation' - ]; - break; - case 'Conversation.UPDATE_ITEM': - keysToOmitFromEvent = [ - 'event.conversation.updateItem.item.text' - ]; - break; - case 'Conversation.CREATE': - keysToOmitFromEvent = [ - 'event.conversation.create.conversation.topic', - 'event.conversation.create.conversation.description' - ]; - break; - case 'Conversation.UPDATE': - keysToOmitFromEvent = [ - 'event.conversation.update.conversation', - 'event.conversation.update.topic', - 'event.conversation.update.description' - ]; - break; - case 'RTCSession.QUESTION_EVENT': - keysToOmitFromEvent = [ - 'event.rtcSession.questionEvent.question.questionText' - ]; - break; - case 'User.USER_PRESENCE_CHANGE': - keysToOmitFromEvent = '*'; - break; - case 'Search.BASIC_SEARCH_RESULT': - keysToOmitFromEvent = [ - 'event.search.basicSearchResult.searchResults' - ]; - break; - case 'ThirdParty.CONNECTED_TO_THIRDPARTY': - keysToOmitFromEvent = [ - 'event.thirdParty.oAuthData' - ]; - break; - } - - _connHandler.on(msgType, cb, keysToOmitFromEvent); - }; - - this.isPartnerAdminSupported = function () { - return _clientApiVersion > SP86_API_VERSION; - }; - - this.isCMRSettingsSupported = function () { - return _clientApiVersion > SP88_API_VERSION; - }; - - this.isUpdateTrunkSupported = function () { - return _clientApiVersion > SP88_API_VERSION; - }; - - this.isDeskphoneSupported = function () { - return _clientApiVersion > SP89_API_VERSION; - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public User related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.logout = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: logout...'); - var request = { - type: Constants.UserActionType.LOGOUT, - logoff: {invalidate: true} - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getUser = function (userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUser...'); - var request = { - type: Constants.UserActionType.GET_USER_BY_ID, - getById: {userId: userId} - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getById.user); - } - }); - }; - - this.updateUser = function (userData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateUser...'); - var request = { - type: Constants.UserActionType.UPDATE, - update: userData - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.updateResult.user); - } - }); - }; - - this.getLoggedOnUser = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getLoggedOnUser...'); - var request = { - type: Constants.UserActionType.GET_LOGGED_ON - }; - - var keysToOmitFromResponse = [ - 'response.user.getLoggedOn.user.userPresenceState.longitude', - 'response.user.getLoggedOn.user.userPresenceState.latitude', - 'response.user.getLoggedOn.user.userPresenceState.locationText', - 'response.user.getLoggedOn.user.userPresenceState.statusMessage', - 'response.user.getLoggedOn.userPresenceState.longitude', - 'response.user.getLoggedOn.userPresenceState.latitude', - 'response.user.getLoggedOn.userPresenceState.locationText', - 'response.user.getLoggedOn.userPresenceState.statusMessage' - ]; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - var user, getLoggedOn; - - if (isResponseValid(err, rsp, cb)) { - getLoggedOn = rsp.user.getLoggedOn; - user = getLoggedOn.user; - // Make sure userPresenceState is returned in both response and user object, for backwards compatibility and SDK - getLoggedOn.userPresenceState = getLoggedOn.userPresenceState || user.userPresenceState; - user.userPresenceState = getLoggedOn.userPresenceState; - user.accounts = getLoggedOn.accounts || []; - - // Save the Client API version that we are connected to - _clientApiVersion = Utils.convertVersionToNumber(user.apiVersion); - - // Save the connected clientId and userId - _clientId = user.clientId; - _userId = user.userId; - - cb(null, getLoggedOn); - } - }, null, keysToOmitFromResponse); - }; - - this.getStuff = function (types, cb) { - getApiVersion(getStuff.bind(this, types, cb)); - }; - - this.setPresence = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setPresence...'); - var request = { - type: Constants.UserActionType.SET_PRESENCE, - presence: { - state: data.state, - inTransit: data.inTransit, - longitude: data.longitude, - latitude: data.latitude, - locationText: data.locationText, - isAccurate: data.isAccurate, - timeZoneOffset: data.timeZoneOffset, - mobile: data.mobile, - dndUntil: data.dndUntil, - statusMessage: data.statusMessage || '' - } - }; - - var keysToOmitFromRequest = [ - 'request.user.presence.longitude', - 'request.user.presence.latitude', - 'request.user.presence.locationText', - 'request.user.presence.statusMessage' - ]; - var keysToOmitFromResponse = [ - 'response.user.setPresence.state.longitude', - 'response.user.setPresence.state.latitude', - 'response.user.setPresence.state.locationText', - 'response.user.setPresence.state.statusMessage' - ]; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.setPresence.state); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.getPresence = function (userIds, full, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getPresence...'); - - var keysToOmitFromResponse = [ - 'response.user.getPresence.states.[].longitude', - 'response.user.getPresence.states.[].latitude', - 'response.user.getPresence.states.[].locationText', - 'response.user.getPresence.states.[].statusMessage' - ]; - - var request = { - type: Constants.UserActionType.GET_PRESENCE, - getPresence: { - userIds: userIds, - full: !!full - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getPresence.states); - } - }, null, keysToOmitFromResponse); - }; - - this.subscribePresence = function (userIdsList, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: subscribePresence...'); - - var keysToOmitFromResponse = [ - 'response.user.subscribePresence.states.[].longitude', - 'response.user.subscribePresence.states.[].latitude', - 'response.user.subscribePresence.states.[].locationText', - 'response.user.subscribePresence.states.[].statusMessage' - ]; - - var request = { - type: Constants.UserActionType.SUBSCRIBE_PRESENCE, - subscribePresence: {userIds: userIdsList} - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - // The subscribe request also returns the presence state in the response - var presenceStates = rsp.user && rsp.user.subscribePresence && rsp.user.subscribePresence.states; - cb(null, presenceStates); - } - }, null, keysToOmitFromResponse); - }; - - this.unsubscribePresence = function (userIdsList, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unsubscribePresence...'); - var request = { - type: Constants.UserActionType.UNSUBSCRIBE_PRESENCE, - unsubscribePresence: {userIds: userIdsList} - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.updateStatusSubscription = function (userIdsList, subscribe, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateStatusSubscription - subscribe = ', subscribe); - - var request; - if (subscribe) { - request = { - type: Constants.UserActionType.SUBSCRIBE_PRESENCE, - subscribePresence: { - userIds: userIdsList, - notificationSubscriptionType: Constants.NotificationSubscriptionType.ONLINE_STATUS - } - }; - } else { - request = { - type: Constants.UserActionType.UNSUBSCRIBE_PRESENCE, - unsubscribePresence: { - userIds: userIdsList, - notificationSubscriptionType: Constants.NotificationSubscriptionType.ONLINE_STATUS - } - }; - } - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.changePassword = function (oldPassword, newPassword, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: changePassword...'); - var request = { - type: Constants.UserActionType.CHANGE_PASSWORD, - changePassword: { - oldPassword: oldPassword, - newPassword: newPassword - } - }; - - var keysToOmitFromRequest = [ - 'request.user.changePassword.oldPassword', - 'request.user.changePassword.newPassword' - ]; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.changePasswordResult.changeResult); - } - }, keysToOmitFromRequest); - }; - - this.setPassword = function (password, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setPassword...'); - var request = { - type: Constants.UserActionType.SET_PASSWORD, - setPassword: { - password: password - } - }; - - var keysToOmitFromRequest = [ - 'request.user.setPassword.password' - ]; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.setPasswordResult.resultSetPassword); - } - }, keysToOmitFromRequest); - }; - - this.getLdapTechnicalUserPresence = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getLdapTechnicalUserPresence...'); - - var request = { - type: Constants.UserActionType.GET_LDAP_TECHNICAL_USER_PRESENCE - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getLdapTechnicalUserPresenceResult.state); - } - }); - }; - - this.subscribeDevices = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: subscribeDevices...'); - - var request = { - type: Constants.UserActionType.SUBSCRIBE_DEVICES - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var deviceList = rsp.user && rsp.user.subscribeDevices && rsp.user.subscribeDevices.devices; - cb(null, deviceList); - } - }); - }; - - this.unsubscribeDevices = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unsubscribeDevices...'); - var request = { - type: Constants.UserActionType.UNSUBSCRIBE_DEVICES - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - ///////////////////////////////////////////////////////////////////////////// - // FRN8499 - Email Change - ///////////////////////////////////////////////////////////////////////////// - this.requestEmailChange = function (userId, newEmail, cb) { - logger.debug('[ClientApiHandler]: requestEmailChange'); - var request = { - type: Constants.UserActionType.GENERATE_EMAIL_UPDATE_TOKEN, - generateEmailUpdateToken: { - userId: userId, - emailAddress: newEmail - } - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getUsersByIds = function (userIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUsersByIds...'); - var request = { - type: Constants.UserActionType.GET_USERS_BY_IDS, - usersByIds: { - userIds: userIds, - complete: true - } - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.usersByIds.user); - } - }); - }; - - this.getPartialUsersByIds = function (userIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getPartialUsersByIds...'); - var request = { - type: Constants.UserActionType.GET_USERS_BY_IDS, - usersByIds: { - userIds: userIds, - complete: false - } - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.usersByIds.user); - } - }); - }; - - this.renewToken = function (userId, cb) { - cb = cb || function (err) { - if (err) { - logger.error('[ClientApiHandler]: Error renewing token. This client will be automatically logged out', err); - } else { - logger.debug('[ClientApiHandler]: Successfully renewed token.'); - } - }; - logger.debug('[ClientApiHandler]: renewToken'); - var request = { - type: Constants.UserActionType.RENEW_TOKEN - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.renewToken); - } - }); - }; - - this.getUserSettings = function (areas, cb) { - cb = cb || NOP; - areas = areas || Constants.UserSettingArea.ALL; - logger.debug('[ClientApiHandler]: getUserSettings...'); - var request = { - type: Constants.UserActionType.GET_USER_SETTINGS, - getUserSettings: { - areas: areas - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getUserSettings.settings); - } - }); - }; - - this.setUserSettings = function (settings, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setUserSettings...'); - - if (!settings || (Array.isArray(settings) && !settings.length)) { - logger.warn('[ClientApiHandler]: No settings were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - if (!Array.isArray(settings)) { - settings = [settings]; - } - - var request = { - type: Constants.UserActionType.SET_USER_SETTINGS, - setUserSettings: { - settings: settings - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getUserByEmail = function (emailAddress, cb) { - cb = cb || NOP; - var request = { - type: Constants.UserActionType.GET_USER_BY_MAIL, - getUserByMail: { - emailAddress: emailAddress, - excludeRoles: [Constants.UserRole.SUPPORT, Constants.UserRole.SYSTEM_ADMIN] - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getUserByMail.user); - } - }); - }; - - /** - * Retrieves users with email addresses passed as parameter - * - * @param {Object} filter - * @param {string[]} filter.emailAddresses - Array of email addresses to search for - * @param {boolean} filter.xmppLookup - Set to true to include XMPP users in the result - * @param {Function} cb Callback returning error and user parameters - */ - this.getUsersByEmails = function (filter, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUsersByEmails...'); - var request = { - type: Constants.UserActionType.GET_USERS_BY_MAILS, - getUsersByMails: { - emailAddresses: filter.emailAddresses, - xmppLookup: !!filter.xmppLookup, - excludeRoles: [Constants.UserRole.SUPPORT, Constants.UserRole.SYSTEM_ADMIN] - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.user.getUsersByMails.users); - } - }); - }; - - this.changeUserEmail = function (userId, emailAddress, cb) { - cb = cb || NOP; - var request = { - type: Constants.UserActionType.EMAIL_UPDATE, - emailUpdate: { - userId: userId, - emailAddress: emailAddress - } - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.emailUpdate); - } - }); - }; - - this.goToSleep = function (userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: goToSleep...'); - - var request = { - type: Constants.UserActionType.GO_TO_SLEEP, - userId: userId - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.wakeUp = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: wakeUp...'); - - var request = { - type: Constants.UserActionType.WAKE_UP - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getDevices = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getDevices...'); - - var request = { - type: Constants.UserActionType.GET_DEVICES - }; - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getDevices.devices); - } - }); - }; - - this.getTelephonyData = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyData...'); - - var request = { - type: Constants.UserActionType.GET_TELEPHONY_DATA - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.getTelephonyData.data); - } - }); - }; - - this.getConnectedApps = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConnectedApps...'); - - var request = { - type: Constants.UserActionType.OAUTH_GET_GRANTED_ACCESS_TOKENS - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.user.oAuthGetGrantedAccessTokens.oAuthGrantedAccess); - } - }); - }; - - this.revokeAccessToken = function (tokenId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: revokeAccessToken...'); - - var request = { - type: Constants.UserActionType.OAUTH_REVOKE_ACCESS_TOKEN, - oAuthRevokeGrantedAccessToken: { - tokenId: tokenId - } - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.revokeAllManagedDevices = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: revokeAllManagedDevices...'); - - var request = { - type: Constants.UserActionType.REVOKE_MANAGED_DEVICES - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getSecurityTokenInfo = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getSecurityTokenInfo...'); - - var request = { - type: Constants.UserActionType.GET_SECURITY_TOKEN_INFO - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.user.getSecurityTokenInfoResult.securityTokenInfo); - } - }); - }; - - this.revokeSecurityToken = function (tokenIdHash, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: revokeSecurityToken...'); - - var request = { - type: Constants.UserActionType.REVOKE_SECURITY_TOKEN, - revokeSecurityToken: { - tokenIdHash: tokenIdHash - } - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getUserTenantSettings = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUserTenantSettings...'); - - var request = { - type: Constants.UserActionType.GET_TENANT_SETTINGS - }; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.user.getTenantSettings.tenantSettings); - } - }, null, null, tenantContext); - }; - - this.resetOpenScapeDevicePins = function (circuitUserId, directoryNumber, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: resetOpenScapeDevicePins...'); - - if (!this.isDeskphoneSupported()) { - logger.warn('[ClientApiHandler]: The resetOpenScapeDevicePins operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.UserActionType.RESET_OPENSCAPE_DEVICE_PINS, - resetOpenScapeDevicePins: { - circuitUserId: circuitUserId, - directoryNumber: directoryNumber - } - }; - - var keysToOmitFromResponse = ['response.user.resetOpenScapeDevicePins.openScapeDevicePins']; - - sendRequest(Constants.ContentType.USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.user.resetOpenScapeDevicePins.openScapeDevicePins); - } - }, null, keysToOmitFromResponse); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Search related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.startBasicSearch = function (searchTerms, priorizedConvs, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: startBasicSearch...'); - var request = { - type: Constants.SearchActionType.START_BASIC_SEARCH, - startBasicSearch: { - searchTerm: searchTerms, - priorizedConvs: priorizedConvs || undefined, - newSearch: true - } - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.search.startSearchResult.searchId); - } - }); - }; - - this.startDetailSearch = function (searchTerms, convId, searchId, resultSetLimit, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: startDetailSearch...'); - var request = { - type: Constants.SearchActionType.START_DETAIL_SEARCH, - startDetailSearch: { - searchTerm: searchTerms, - convId: convId, - searchId: searchId || undefined, - resultSetLimit: resultSetLimit || 0 - } - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.search.startSearchResult.searchId); - } - }); - }; - - this.cancelSearch = function (searchId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: cancelSearch...'); - var request = { - type: Constants.SearchActionType.CANCEL_SEARCH, - cancelSearch: { - searchId: searchId - } - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getRecentSearches = function (cb) { - cb = cb || NOP; - var request = { - type: Constants.SearchActionType.GET_RECENT_SEARCHES - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.search.getRecentSearch.recentSearches); - } - }); - }; - - this.addRecentSearch = function (searchObj, cb) { - cb = cb || NOP; - var request = { - type: Constants.SearchActionType.ADD_RECENT_SEARCH, - addRecentSearch: {searchTerm: searchObj} - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.startUserSearch = function (constraints, cb) { - cb = cb || NOP; - var request = { - type: Constants.SearchActionType.START_USER_SEARCH, - startUserSearch: {query: constraints.query, reversePhoneNumberLookup: !!constraints.reversePhoneNumberLookup} - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.search.startSearchResult.searchId); - } - }); - }; - - this.searchConversationParticipants = function (displayName, convId, cb) { - cb = cb || NOP; - var request = { - type: Constants.SearchActionType.SEARCH_CONVERSATION_PARTICIPANTS, - searchConversationParticipants: {displayName: displayName, convId: convId} - }; - sendRequest(Constants.ContentType.SEARCH, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.search.searchConversationParticipantsResult.participants || []); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Conversation related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.createConversation = function (createConversation, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: createConversation...'); - - var convType = createConversation.type; - if (!convType) { - convType = createConversation.participants.length > 2 ? Constants.ConversationType.GROUP : Constants.ConversationType.DIRECT; - } - - var request = { - type: Constants.ConversationActionType.CREATE, - create: { - type: convType, - topic: createConversation.topic, - description: createConversation.description || undefined, - participants: createConversation.participants, - invitations: createConversation.emailAddresses, - locale: createConversation.locale - } - }; - - var keysToOmitFromRequest = [ - 'request.conversation.create.topic', - 'request.conversation.create.description' - ]; - var keysToOmitFromResponse = [ - 'response.conversation.create.conversation.topic', - 'response.conversation.create.conversation.description', - 'response.conversation.create.conversation.topLevelItem' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.create.conversation, rsp.code === Constants.ReturnCode.ENTITY_ALREADY_EXISTS); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - /** - * Update the conversation object on the server. For example the topic. - * @param {object} data Object containing the parameters. - * conversationId: Conversation ID - * topic: Conversation topic - * description: Conversation description - * @param {function} cb Callback returning error and data objects. - */ - this.updateConversation = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateConversation...'); - - var request = { - type: Constants.ConversationActionType.UPDATE, - update: { - convId: data.conversationId, - topic: data.topic, - description: data.description - } - }; - var keysToOmitFromRequest = [ - 'request.conversation.update.topic', - 'request.conversation.update.description' - ]; - var keysToOmitFromResponse = [ - 'response.conversation.update.conversation.participants', - 'response.conversation.update.conversation.topic', - 'response.conversation.update.conversation.description', - 'response.conversation.update.conversation.topLevelItem' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.update.conversation); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.addTextItem = function (textItem, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addTextItem...'); - - // Sanitize content, e.g. convert style attribute to class attribute - var content = (textItem.content && Utils.sanitize(textItem.content) && Utils.sanitizeSymbols(textItem.content)) || ''; - content = PrivateData.convertPrivateDataToContent(textItem) + content; - - var request = { - type: Constants.ConversationActionType.ADD_TEXT_ITEM, - addTextItem: { - convId: textItem.convId, - parentId: textItem.parentId, - contentType: textItem.contentType, - subject: textItem.subject, - content: content, - attachmentMetaData: getAttachmentsData(textItem.attachmentMetaData), - externalAttachmentMetaData: getExternalAttachmentsData(textItem.externalAttachmentMetaData), - preview: textItem.preview, - mentionedUsers: textItem.mentionedUsers - } - }; - - // preview.description field is limited to 256 chars - if (request.addTextItem.preview && request.addTextItem.preview.description) { - request.addTextItem.preview.description = request.addTextItem.preview.description.substr(0, 255); - } - - var keysToOmitFromRequest = [ - 'request.conversation.addTextItem.subject', - 'request.conversation.addTextItem.content' - ]; - var keysToOmitFromResponse = [ - 'response.conversation.addTextItem.item.text' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.addTextItem.item); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.updateTextItem = function (textItem, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTextItem...'); - - // Sanitize content, e.g. convert style attribute to class attribute - var content = (textItem.content && Utils.sanitize(textItem.content) && Utils.sanitizeSymbols(textItem.content)) || ''; - content = PrivateData.convertPrivateDataToContent(textItem) + content; - - var textItemToSend = { - itemId: textItem.itemId, - convId: textItem.convId, - parentId: textItem.parentId, - contentType: Constants.TextItemContentType.RICH, - subject: textItem.subject, - content: content, - attachmentMetaData: getAttachmentsData(textItem.attachmentMetaData), - preview: textItem.preview || {}, // In order to clear preview, an empty object must be used - clustered: textItem.clustered, - externalAttachmentMetaData: getExternalAttachmentsData(textItem.externalAttachmentMetaData), - mentionedUsers: textItem.mentionedUsers - }; - - // preview.description field is limited to 256 chars - if (textItemToSend.preview.description) { - textItemToSend.preview.description = textItemToSend.preview.description.substr(0, 255); - } - - if (!textItemToSend.externalAttachmentMetaData || textItemToSend.externalAttachmentMetaData.length === 0) { - textItemToSend.externalAttachmentMetaData = [{ - type: 'BOX', - downloadLocation: '', - previewLocation: '', - attachment: {} - }]; - } - - // BO7627: If attachments is empty then send an array with empty object: [{}] - if (!textItemToSend.attachmentMetaData || textItemToSend.attachmentMetaData.length === 0) { - textItemToSend.attachmentMetaData = [{}]; - } - - var request = { - type: Constants.ConversationActionType.UPDATE_TEXT_ITEM, - updateTextItem: textItemToSend - }; - - var keysToOmitFromRequest = [ - 'request.conversation.updateTextItem.subject', - 'request.conversation.updateTextItem.content' - ]; - var keysToOmitFromResponse = [ - 'response.conversation.updateTextItem.item.text' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.updateTextItem.item); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.deleteTextItem = function (data, cb) { - cb = cb || NOP; - if (!data || !data.itemId) { - logger.warn('[ClientApiHandler]: No request parameters were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - logger.debug('[ClientApiHandler]: deleteTextItem...'); - var request = { - type: Constants.ConversationActionType.DELETE_TEXT_ITEM, - deleteTextItem: {itemId: data.itemId} - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.deleteTextItem.item); - } - }); - }; - - this.deleteRecording = function (rtcItemId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteRecording...'); - var request = { - type: Constants.ConversationActionType.DELETE_RECORDING, - deleteRecording: {itemId: rtcItemId} - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.deleteRecording.item); - } - }); - }; - - this.updateRtcItemAttachments = function (rtcItemId, attachments, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateRtcItemAttachments...'); - - var request = { - type: Constants.ConversationActionType.UPDATE_RTC_ITEM_ATTACHMENTS, - updateRtcItemAttachments: { - itemId: rtcItemId, - attachments: getAttachmentsData(attachments) - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.updateRtcItemAttachmentsResult.item); - } - }); - }; - - this.getOpenConversations = function (startIndex, numberOfItems, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getOpenConversations...'); - var request = { - type: Constants.ConversationActionType.GET_OPEN_CONVERSATIONS, - getOpenConversations: { - startIndex: startIndex, - numberOfItems: numberOfItems - } - }; - - // Omit all the conversations from the response. We will log a summary with the most - // important data in ConversationSvc. - var keysToOmitFromResponse = [ - 'response.conversation.getOpenConversations.conversations' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var data = rsp.conversation.getOpenConversations; - cb(null, data.conversations, data.lastItemIndex); - } - }, null, keysToOmitFromResponse); - }; - - this.getAllOpenConversations = function (startIndex, numberOfItems, sorting, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAllOpenConversations...'); - var request = { - type: Constants.ConversationActionType.GET_OPEN_CONVERSATIONS, - getOpenConversations: { - startIndex: startIndex, - numberOfItems: numberOfItems, - ownIncluded: true, - sorting: sorting - } - }; - - // Omit all the conversations from the response. We will log a summary with the most - // important data in ConversationSvc. - var keysToOmitFromResponse = [ - 'response.conversation.getOpenConversations.conversations' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var data = rsp.conversation.getOpenConversations; - cb(null, data.conversations, data.lastItemIndex); - } - }, null, keysToOmitFromResponse); - }; - - this.joinOpenConversation = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: JoinOpenConversation...'); - var request = { - type: Constants.ConversationActionType.JOIN_OPEN_CONVERSATION, - joinOpenConversation: { - convId: convId - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.joinOpenConversation.conv); - } - }); - }; - - this.getConversationFeed = function (convId, timestamp, minTotalItems, maxTotalUnread, commentsPerThread, maxUnreadPerThread, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationFeed...'); - - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_FEED, - getConversationFeed: { - convId: convId, - timestamp: timestamp || undefined, - minTotalItems: minTotalItems || undefined, - maxTotalUnread: maxTotalUnread || undefined, - commentsPerThread: commentsPerThread || undefined, - maxUnreadPerThread: maxUnreadPerThread || undefined - } - }; - - var keysToOmitFromResponse = [ - 'response.conversation.getConversationFeedResult.conversationThreads.[].parentItem.attachments', - 'response.conversation.getConversationFeedResult.conversationThreads.[].parentItem.externalAttachments', - 'response.conversation.getConversationFeedResult.conversationThreads.[].parentItem.rtc', - 'response.conversation.getConversationFeedResult.conversationThreads.[].parentItem.text', - 'response.conversation.getConversationFeedResult.conversationThreads.[].parentItem.system', - 'response.conversation.getConversationFeedResult.conversationThreads.[].comments.[].attachments', - 'response.conversation.getConversationFeedResult.conversationThreads.[].comments.[].externalAttachments', - 'response.conversation.getConversationFeedResult.conversationThreads.[].comments.[].text' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var res = rsp.conversation.getConversationFeedResult; - cb(null, res.conversationThreads, res.hasOlderThreads); - } - }, null, keysToOmitFromResponse); - }; - - this.getThreadComments = function (convId, threadId, timestamp, timestampFilter, direction, numberOfComments, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getThreadComments...'); - var request = { - type: Constants.ConversationActionType.GET_THREAD_COMMENTS, - getThreadComments: { - convId: convId, - threadId: threadId, - timestamp: timestamp || undefined, - timestampFilter: timestampFilter || Constants.TimestampFilter.CREATION, - direction: direction || undefined, - totalComments: numberOfComments || undefined - } - }; - - // Set keysToOmitFromResponse to obfuscate the required fields, but also to reduce - // the amount of information that is logged. - var keysToOmitFromResponse = [ - 'response.conversation.getThreadCommentsResult.comments.[].attachments', - 'response.conversation.getThreadCommentsResult.comments.[].externalAttachments', - 'response.conversation.getThreadCommentsResult.comments.[].text' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var res = rsp.conversation.getThreadCommentsResult; - cb(null, res.comments, res.hasMoreComments); - } - }, null, keysToOmitFromResponse); - }; - - this.getThreadsByItemIds = function (requestProperties, cb) { - logger.debug('[ClientApiHandler]: getThreadsByItemIds...'); - cb = cb || NOP; - - if (!requestProperties) { - logger.warn('[ClientApiHandler]: No request parameters were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - if (!requestProperties.convId) { - logger.warn('[ClientApiHandler]: No conversation id was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - if (!requestProperties.threadParams) { - logger.warn('[ClientApiHandler]: No thread parameters were provided (item ids were not provided)'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var keysToOmitFromResponse = [ - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].parentItem.attachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].parentItem.externalAttachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].parentItem.rtc', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].parentItem.text', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].parentItem.system', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].comments.[].item.attachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].comments.[].item.externalAttachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].comments.[].item.text', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].lastComments.[].attachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].lastComments.[].externalAttachments', - 'response.conversation.getThreadsByItemIdsResult.conversationThreadContext.[].lastComments.[].text' - ]; - - var request = { - type: Constants.ConversationActionType.GET_THREADS_BY_ITEM_IDS, - getThreadsByItemIds: { - convId: requestProperties.convId, - threadParams: requestProperties.threadParams, - timestampFilter: requestProperties.timestampFilter || undefined, - orderBy: requestProperties.orderBy || undefined - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var res = rsp.conversation.getThreadsByItemIdsResult; - cb(null, res && res.conversationThreadContext); - } - }, null, keysToOmitFromResponse); - }; - - this.getThreadsByIds = function (requestProperties, cb) { - logger.debug('[ClientApiHandler]: getThreadsByIds...'); - cb = cb || NOP; - - if (!requestProperties) { - logger.warn('[ClientApiHandler]: No request parameters were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - if (!requestProperties.convId) { - logger.warn('[ClientApiHandler]: No conversation id was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - if (!((requestProperties.threadIds instanceof Array) && (requestProperties.threadIds.length > 0))) { - logger.warn('[ClientApiHandler]: No thread parameters were provided (thread ids were not provided)'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var keysToOmitFromResponse = [ - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].parentItem.attachments', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].parentItem.externalAttachments', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].parentItem.rtc', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].parentItem.text', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].parentItem.system', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].comments.[].attachments', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].comments.[].externalAttachments', - 'response.conversation.getThreadsByIdsResult.conversationThreads.[].comments.[].text' - ]; - - var request = { - type: Constants.ConversationActionType.GET_THREADS_BY_IDS, - getThreadsByIds: { - convId: requestProperties.convId, - threadIds: requestProperties.threadIds, - orderBy: requestProperties.orderBy || Constants.SortingType.TIMESTAMP_ASC, - commentsPerThread: requestProperties.commentsPerThread || undefined - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var res = rsp.conversation.getThreadsByIdsResult; - cb(null, res && res.conversationThreads); - } - }, null, keysToOmitFromResponse); - }; - - this.getConversations = function (userId, timestamp, olderThanTimestamp, number, filter, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversations...'); - - var direction = olderThanTimestamp ? Constants.SearchDirection.BEFORE : Constants.SearchDirection.AFTER; - var request = { - type: Constants.ConversationActionType.GET_CONVERSATIONS, - getConversations: { - userId: userId, - modificationDate: timestamp || undefined, - direction: direction, - number: number || undefined, - filter: filter || Constants.ConversationFilter.ALL, - sortByLastItem: !!olderThanTimestamp - } - }; - - // Omit all the conversations from the response. We will log a summary with the most - // important data in ConversationSvc. - var keysToOmitFromResponse = [ - 'response.conversation.getConversations.conversations' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getConversations.conversations); - } - }, null, keysToOmitFromResponse); - }; - - /** - * Get support conversation object on the server. Server will create one if not existing. - * - * @param {function} cb Callback function which will be invoked with conversation data retrieved from server. - */ - this.getSupportConversation = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getSupportConversation...'); - var request = { - type: Constants.ConversationActionType.GET_SUPPORT_CONVERSATION - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getSupportConversation.conversation); - } - }); - }; - - /** - * Get support conversation ID on the server. Empty conversation ID will be returned if support conversation has not been created. - * - * @param {function} cb Callback function which will be invoked with support conversation ID retrieved from server. - */ - this.getSupportConversationId = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getSupportConversationId...'); - var request = { - type: Constants.ConversationActionType.GET_SUPPORT_CONVERSATION_ID - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getSupportConversationId.conversationId); - } - }); - }; - - /** - * Get telephony conversation ID on the server. Empty conversation ID will be returned if telephony conversation has not been created. - * - * @param {function} cb Callback function which will be invoked with telephony conversation ID retrieved from server. - */ - this.getTelephonyConversationId = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyConversationId...'); - var request = { - type: Constants.ConversationActionType.GET_TELEPHONY_CONVERSATION_ID - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getTelephonyConversationId.conversationId); - } - }); - }; - - - /** - * Get lists of marked conversation IDs on the server. - * - * @param {function} cb Callback function which will be invoked with marked conversation IDs retrieved from server. - */ - this.getMarkedConversationsList = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getMarkedConversationsList...'); - - var request = { - type: Constants.ConversationActionType.GET_MARKED_CONVERSATIONS_LIST - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getMarkedConversationsList); - } - }); - }; - - /** - * Get lists of favorite conversation IDs on the server. - * - * @param {function} cb Callback function which will be invoked with favorite conversation IDs retrieved from server. - */ - this.getFavoriteConversationIds = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getFavoriteConversationIds...'); - - var request = { - type: Constants.ConversationActionType.GET_FAVORITE_CONVERSATION_IDS - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getFavoriteConversationIdResult.favoriteConversationIds); - } - }); - }; - - /** - * Mark a conversation as Muted, Favorite, etc. - * - * @param {function} cb Callback function which will be invoked when reponse received from server. - */ - this.markConversation = function (data, cb) { - cb = cb || NOP; - - var request = { - type: Constants.ConversationActionType.MARK_CONVERSATION, - markConversation: { - convId: data.convId, - filter: data.markType - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - /** - * Unmark a conversation as Muted, Favorite, etc. - * - * @param {function} cb Callback function which will be invoked when reponse received from server. - */ - this.unmarkConversation = function (data, cb) { - cb = cb || NOP; - - var request = { - type: Constants.ConversationActionType.UNMARK_CONVERSATION, - unmarkConversation: { - convId: data.convId, - filter: data.markType - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /** - * Get conversation userData(read info) object on the server specifying conversation ID(s) - * - * @param {(string|string[])} convIds Conversation ID or an array of conversation IDs. - * @param {function} cb Callback function which will be invoked with conversation user data retrieved from server. - */ - this.getConversationUserData = function (convIds, userDataToInclude, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationUserData...'); - if (!(convIds instanceof Array)) { - convIds = [convIds]; - } - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_USER_DATA, - getConversationUserData: { - conversationId: convIds, - userDataToInclude: userDataToInclude || undefined - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConversationUserData.userData); - } - }); - }; - - this.getConversationItems = function (conversationId, modificationDate, creationDate, searchDirection, maxResultsLimit, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationItems...'); - var request = { - type: Constants.ConversationActionType.GET_ITEMS_BY_CONVERSATION, - getItemsByConversation: { - convId: conversationId, - modificationDate: modificationDate || undefined, - creationDate: creationDate || undefined, - direction: searchDirection || undefined, - number: maxResultsLimit || undefined - } - }; - - // Set keysToOmitFromResponse to obfuscate the required fields, but also to reduce - // the amount of information that is logged. - var keysToOmitFromResponse = [ - 'response.conversation.getItemsByConversation.items.[].attachments', - 'response.conversation.getItemsByConversation.items.[].externalAttachments', - 'response.conversation.getItemsByConversation.items.[].rtc', - 'response.conversation.getItemsByConversation.items.[].text', - 'response.conversation.getItemsByConversation.items.[].system' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getItemsByConversation.items); - } - }, null, keysToOmitFromResponse); - }; - - this.getConversationAttachments = function (convId, queryData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationAttachments...'); - - var request = { - type: Constants.ConversationActionType.GET_ATTACHMENTS, - getAttachments: { - conversationId: convId, - startPage: queryData.page || undefined, // should be skipped for first time - pageSize: queryData.size || undefined, // default = 10 - orderType: queryData.type || undefined, // default = 'CREATION' - orderDirection: queryData.direction || undefined // default = 'DSC' - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var data = rsp.conversation.getAttachments.attachments || []; - var lastIndex = rsp.conversation.getAttachments.nextPage.hasMoreAttachments ? rsp.conversation.getAttachments.nextPage.lastIndex : null; - cb(null, data, lastIndex); - } - }); - }; - - /** - * Get the item by itemId, together with n items before or after it, or n items before and after it - * This can be used in search scenario when user click up/down arrow to just to next matched item, and - * the UI needs to also display some items around the matched one. - * - * @param {String} conversationId The conversation's id. - * @param {String} itemId The item's id. - * @param {String} searchDirection The search direction, can be BEFORE, AFTER or BOTH. - * @param {Number} number The number of items before or/and after the specified item - * @param {String} orderType The orderType, can be MODIFICATION or CREATION (default MODIFICATION) - * @param {Function} cb The callback function when the client receives response - */ - this.getItemsByConversationItemId = function (conversationId, itemId, searchDirection, number, orderType, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getItemsByConversationItemId...'); - var request = { - type: Constants.ConversationActionType.GET_ITEMS_BY_CONVERSATION_ITEM_ID, - getItemsByConversationItemId: { - convId: conversationId, - itemId: itemId, - direction: searchDirection || undefined, - number: number || undefined, - orderType: orderType || undefined - } - }; - - // Set keysToOmitFromResponse to obfuscate the required fields, but also to reduce - // the amount of information that is logged. - var keysToOmitFromResponse = [ - 'response.conversation.getItemsByConversationItemId.items.[].attachments', - 'response.conversation.getItemsByConversationItemId.items.[].externalAttachments', - 'response.conversation.getItemsByConversationItemId.items.[].rtc', - 'response.conversation.getItemsByConversationItemId.items.[].text', - 'response.conversation.getItemsByConversationItemId.items.[].system' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getItemsByConversationItemId.items); - } - }, null, keysToOmitFromResponse); - }; - - /** - * Get the conversation by conversationId. - * - * @param {String} convId The conversation's id. - * @param {Function} cb The callback function when client receives response - */ - this.getConversationById = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationById...'); - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_BY_ID, - getConversationById: {convId: convId} - }; - var keysToOmitFromResponse = [ - 'response.conversation.getConversationById.conversation.participants', - 'response.conversation.getConversationById.conversation.topic', - 'response.conversation.getConversationById.conversation.description', - 'response.conversation.getConversationById.conversation.topLevelItem' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var conv = rsp.conversation.getConversationById.conversation; - if (conv && conv.participants) { - logger.info('[ClientApiHandler]: Retrieved conversation has ' + conv.participants.length + ' participant(s)'); - } - cb(null, conv); - } - }, null, keysToOmitFromResponse); - }; - - /** - * Get the conversations by conversationIds. - * - * @param {Array} convIds The conversations ids. - * @param {Function} cb The callback function when client receives response - */ - this.getConversationsByIds = function (convIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationsByIds...'); - var request = { - type: Constants.ConversationActionType.GET_CONVERSATIONS_BY_IDS, - getConversationsByIds: { - conversationId: convIds - } - }; - var keysToOmitFromResponse = [ - 'response.conversation.getConversationsByIdsResult.conversations' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getConversationsByIdsResult.conversations); - } - }, null, keysToOmitFromResponse); - }; - - this.getItemById = function (itemId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getItemById...'); - var request = { - type: Constants.ConversationActionType.GET_ITEM_BY_ID, - getItemById: {itemId: itemId} - }; - var keysToOmitFromResponse = [ - 'response.conversation.getItemById.item.attachments', - 'response.conversation.getItemById.item.externalAttachments', - 'response.conversation.getItemById.item.rtc', - 'response.conversation.getItemById.item.text', - 'response.conversation.getItemById.item.system' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getItemById.item); - } - }, null, keysToOmitFromResponse); - }; - - this.getItemsByIds = function (itemIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getItemsByIds...'); - var request = { - type: Constants.ConversationActionType.GET_ITEMS_BY_IDS, - getItemsByIds: {itemIds: itemIds} - }; - var keysToOmitFromResponse = [ - 'response.conversation.getItemsByIds.items.[].attachments', - 'response.conversation.getItemsByIds.items.[].externalAttachments', - 'response.conversation.getItemsByIds.items.[].rtc', - 'response.conversation.getItemsByIds.items.[].text', - 'response.conversation.getItemsByIds.items.[].system' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getItemsByIds.items); - } - }, null, keysToOmitFromResponse); - }; - - this.setReadPointer = function (convId, lastReadTimestamp, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setReadPointer...'); - var request = { - type: Constants.ConversationActionType.SET_READ_POINTER, - setReadPointer: { - convId: convId, - lastReadTimestamp: lastReadTimestamp - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getConversationByUser = function (userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationByUser...'); - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_BY_USER, - getConversationByUser: { - userId: userId - } - }; - var keysToOmitFromResponse = [ - 'response.conversation.getConversationByUser.conversation.topic', - 'response.conversation.getConversationByUser.conversation.description', - 'response.conversation.getConversationByUser.conversation.topLevelItem' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConversationByUser.conversation); - } - }, null, keysToOmitFromResponse); - }; - - this.getConversationsByFilter = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationsByFilter...'); - - var request = { - type: Constants.ConversationActionType.GET_CONVERSATIONS_BY_FILTER, - getConversationsByFilter: { - conversationsFilter: data.filterConnector, - timestamp: data.timestamp || undefined, - number: data.number, - numberOfParticipants: data.numberOfParticipants, - retrieveAction: data.retrieveAction || undefined - } - }; - - var keysToOmitFromResponse = [ - 'response.conversation.getConversationsByFilterResult.conversations' - ]; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConversationsByFilterResult || {}); - } - }, null, keysToOmitFromResponse); - }; - - this.subscribe = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: subscribe...'); - var request = { - type: Constants.ConversationActionType.SUBSCRIBE, - subscribe: { - conversationId: convId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.unsubscribe = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unsubscribe...'); - var request = { - type: Constants.ConversationActionType.UNSUBSCRIBE, - unsubscribe: { - conversationId: convId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.typing = function (convId, isTyping, parentItemId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: typing...'); - var request = { - type: Constants.ConversationActionType.TYPING, - typing: { - conversationId: convId, - isTyping: isTyping, - parentItemId: parentItemId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.addParticipant = function (addParticipant, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addParticipant...'); - var request = { - type: Constants.ConversationActionType.ADD_PARTICIPANT, - addParticipant: { - convId: addParticipant.convId, - userId: addParticipant.userId, - invitations: addParticipant.emailAddresses, - locale: addParticipant.locale - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.addParticipant); - } - }); - }; - - this.removeParticipant = function (convId, userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeParticipant...'); - if (userId && !(userId instanceof Array)) { - userId = [userId]; - } - var request = { - type: Constants.ConversationActionType.REMOVE_PARTICIPANT, - removeParticipant: { - convId: convId, - userId: userId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.removeParticipant.conversation); - } - }); - }; - - /** - * Get the conversation participants. Support filtering by name and type. - * - * @param {String} convId The conversation's id. - * @param {Object} queryData Object literal containing query parameters - * @param {Object[]} [queryData.searchCriteria] A list of search criterias used for filtering. - * @param {String} [queryData.searchPointer] The search pointer used for paging. - * @param {Number} queryData.pageSize The page size used for paging. - * @param {Function} cb The callback function when client receives response - */ - this.getConversationParticipants = function (convId, queryData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationParticipants...'); - queryData = queryData || {}; - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_PARTICIPANTS, - getConversationParticipants: { - convId: convId, - searchCriterias: queryData.searchCriterias || undefined, - searchPointer: queryData.searchPointer || undefined, - pageSize: queryData.pageSize || 25, - includePresence: !!queryData.includePresence - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConversationParticipantsResult); - } - }); - }; - - this.getJoinDetails = function (convId, recreate, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getJoinDetails...'); - var request = { - type: Constants.ConversationActionType.GET_JOIN_DETAILS, - getJoinDetails: { - conversationId: convId, - recreate: !!recreate - } - }; - var keysToOmitFromResponse = [ - 'response.conversation.getJoinDetails.tenantPIN', - 'response.conversation.getJoinDetails.sessionPIN' - ]; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getJoinDetails); - } - }, null, keysToOmitFromResponse); - }; - - this.setFlagItem = function (requestProperties, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setFlagItem...'); - if (!requestProperties) { - logger.warn('[ClientApiHandler]: No request parameters were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - var request = { - type: Constants.ConversationActionType.SET_FLAG_ITEM, - setFlagItem: { - conversationId: requestProperties.convId, - conversationItemId: requestProperties.itemId, - conversationItemCreationTime: requestProperties.creationTime, - conversationParentId: requestProperties.parentId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.clearFlagItem = function (convId, itemId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: clearFlagItem...'); - var request = { - type: Constants.ConversationActionType.CLEAR_FLAG_ITEM, - clearFlagItem: { - conversationId: convId, - conversationItemId: itemId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getFlaggedItems = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getFlaggedItems...'); - var request = { - type: Constants.ConversationActionType.GET_FLAGGED_ITEMS - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getFlaggedItem.flaggedItems); - } - }); - }; - - this.likeTextItem = function (itemId, userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: likeTextItem...'); - var request = { - type: Constants.ConversationActionType.LIKE_TEXT_ITEM, - likeTextItem: { - conversationItemId: itemId, - userId: userId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.unlikeTextItem = function (itemId, userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unlikeTextItem...'); - var request = { - type: Constants.ConversationActionType.UNLIKE_TEXT_ITEM, - unlikeTextItem: { - conversationItemId: itemId, - userId: userId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.setConvAvatar = function (convId, avatars, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setConvAvatar...'); - var request = { - type: Constants.ConversationActionType.SET_CONVERSATION_AVATAR, - setConversationAvatar: { - convId: convId, - imageS: avatars.s, - imageL: avatars.b - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.setConversationAvatar.conversation); - } - }); - }; - - this.deleteConvAvatar = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteConvAvatar...'); - var request = { - type: Constants.ConversationActionType.SET_CONVERSATION_AVATAR, - setConversationAvatar: { - convId: convId, - imageS: '', - imageL: '' - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.setConversationAvatar.conversation); - } - }); - }; - - this.moderateConversation = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: moderateConversation...'); - var request = { - type: Constants.ConversationActionType.MODERATE_CONVERSATION, - moderateConversation: { - convId: convId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.moderateResult); - } - }); - }; - - this.unmoderateConversation = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unmoderateConversation...'); - var request = { - type: Constants.ConversationActionType.UNMODERATE_CONVERSATION, - unmoderateConversation: { - convId: convId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.moderateResult); - } - }); - }; - - this.updateGuestAccess = function (convId, disabled, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateGuestAccess...'); - var request = { - type: Constants.ConversationActionType.UPDATE_GUEST_ACCESS, - updateGuestAccess: { - convId: convId, - guestAccessDisabled: !!disabled - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.updateGuestAccessResult); - } - }); - }; - - this.getConversationSummary = function (convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConversationSummary...'); - var request = { - type: Constants.ConversationActionType.GET_CONVERSATION_SUMMARY, - getConversationSummary: { - convId: convId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConversationSummary.conversationSummary); - } - }); - }; - - this.grantModeratorRights = function (convId, userId, cb) { - cb = cb || NOP; - if (!(userId instanceof Array)) { - userId = [userId]; - } - logger.debug('[ClientApiHandler]: grantModeratorRights...'); - var request = { - type: Constants.ConversationActionType.GRANT_MODERATOR_RIGHTS, - grantModeratorRights: { - convId: convId, - userId: userId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.grantModeratorRightsResult); - } - }); - }; - - this.dropModeratorRights = function (convId, userId, cb) { - cb = cb || NOP; - if (!(userId instanceof Array)) { - userId = [userId]; - } - logger.debug('[ClientApiHandler]: dropModeratorRights...'); - var request = { - type: Constants.ConversationActionType.DROP_MODERATOR_RIGHTS, - dropModeratorRights: { - convId: convId, - userId: userId - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.dropModeratorRightsResult); - } - }); - }; - - this.addJournalEntry = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addJournalEntry...'); - - var request = { - type: Constants.ConversationActionType.ADD_JOURNAL_ENTRY, - addJournalEntry: { - convId: data.convId, - starter: data.starter, - source: data.source, - destination: data.destination, - startTime: data.startTime, - duration: data.duration, - type: data.type, - participants: data.participants, - missedReason: data.missedReason - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.setFavoritePosition = function (convId, position, cb) { - cb = cb || NOP; - - logger.debug('[ClientApiHandler]: setFavoritePosition...'); - - var request = { - type: Constants.ConversationActionType.SET_FAVORITE_POSITION, - setFavoritePosition: { - convId: convId, - position: position - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp); - } - }); - }; - - this.addLabels = function (labels, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addLabels...'); - - var request = { - type: Constants.ConversationActionType.ADD_LABELS, - addLabels: { - labels: labels - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.addLabelsResult.labels); - } - }); - }; - - this.assignLabels = function (convId, labelIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: assignLabels...'); - - var request = { - type: Constants.ConversationActionType.ASSIGN_LABELS, - assignLabels: { - convId: convId, - labelIds: labelIds - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.assignLabelsResult.labelIds); - } - }); - }; - - this.unassignLabels = function (convId, labelIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unassignLabels...'); - - var request = { - type: Constants.ConversationActionType.UNASSIGN_LABELS, - unassignLabels: { - convId: convId, - labelIds: labelIds - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.unassignLabelsResult.labelIds); - } - }); - }; - - this.getAllLabels = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAllLabels...'); - - var request = { - type: Constants.ConversationActionType.GET_LABELS - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getLabelsResult.labels); - } - }); - }; - - this.removeLabels = function (labelIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeLabels...'); - - var request = { - type: Constants.ConversationActionType.REMOVE_LABELS, - removeLabels: { - labelIds: labelIds - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.removeLabelsResult.labelIds); - } - }); - }; - - this.editLabel = function (label, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: editLabel...'); - - var request = { - type: Constants.ConversationActionType.EDIT_LABEL, - editLabel: { - label: label - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.editLabelResult.label); - } - }); - }; - - this.addFilter = function (filterName, connector, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addFilter...'); - - var request = { - type: Constants.ConversationActionType.ADD_FILTER, - addFilter: { - filterName: filterName, - root: connector - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.addFilterResult); - } - }); - }; - - this.removeFilter = function (filterIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeFilters...'); - - var request = { - type: Constants.ConversationActionType.REMOVE_FILTERS, - removeFilters: { - filterIds: filterIds - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.removeFiltersResult.filterIds); - } - }); - }; - - this.getAllFilters = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAllFilters...'); - - var request = { - type: Constants.ConversationActionType.GET_FILTERS, - getFilters: {} - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.conversation.getFiltersResult.filters); - } - }); - }; - - this.editFilter = function (filter, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: editFilter...'); - - var request = { - type: Constants.ConversationActionType.EDIT_FILTER, - editFilter: { - filter: filter - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.editFilterResult); - } - }); - }; - - this.getUserDataSince = function (sinceTime, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUserDataSince...'); - - var request = { - type: Constants.ConversationActionType.GET_USER_DATA_SINCE, - getUserDataSince: { - sinceTime: sinceTime - } - }; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var changedUserData = (rsp.conversation.getUserDataSinceResult && rsp.conversation.getUserDataSinceResult.changedUserData) || []; - cb(null, changedUserData); - } - }); - }; - - this.getConferenceInvitationText = function (locale, convId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConferenceInvitationText...'); - - var request = { - type: Constants.ConversationActionType.GET_CONFERENCE_INVITATION_TEXT, - getConferenceInvitationText: { - conversationId: convId, - language: locale - } - }; - - var keysToOmitFromResponse = ['response.conversation.getConferenceInvitationResult.invitationMessage']; - - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConferenceInvitationResult.invitationMessage); - } - }, null, keysToOmitFromResponse); - }; - - this.getConferenceInvitationExample = function (locale, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConferenceInvitationExample...'); - - var request = { - type: Constants.ConversationActionType.GET_CONFERENCE_INVITATION_EXAMPLE, - getConferenceInvitationExample: { - language: locale - } - }; - sendRequest(Constants.ContentType.CONVERSATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.conversation.getConferenceInvitationResult.invitationMessage); - } - }, null, null, tenantContext); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public RTCCall related interfaces - /////////////////////////////////////////////////////////////////////////////// - - this.joinRtcCall = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: joinRtcCall...'); - - var request = { - type: Constants.RTCCallActionType.JOIN, - join: createJoinData(data) - }; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.answerRtcCall = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: answerRtcCall...'); - - var request = { - type: Constants.RTCCallActionType.ANSWER, - answer: createJoinData(data) - }; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.sendIceCandidates = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: sendIceCandidates...'); - - var request = { - type: Constants.RTCCallActionType.ICE_CANDIDATES, - iceCandidates: { - rtcSessionId: data.rtcSessionId, - userId: data.userId, - origin: data.origin, - candidates: data.candidates - } - }; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.leaveRtcCall = function (rtcSessionId, disconnectCause, cb) { - disconnectCause = disconnectCause || {}; - cb = cb || NOP; - logger.debug('[ClientApiHandler]: leaveRtcCall...'); - var request = { - type: Constants.RTCCallActionType.LEAVE, - leave: { - rtcSessionId: rtcSessionId, - disconnectCause: disconnectCause.cause, - disconnectReason: disconnectCause.reason - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.terminateRtcCall = function (rtcSessionId, disconnectCause, cb) { - disconnectCause = disconnectCause || {}; - cb = cb || NOP; - logger.debug('[ClientApiHandler]: terminateRtcCall...'); - var request = { - type: Constants.RTCCallActionType.TERMINATE, - terminate: { - rtcSessionId: rtcSessionId, - disconnectCause: disconnectCause.cause, - disconnectReason: disconnectCause.reason - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.declineRtcCall = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: declineRtcCall...'); - var request = { - type: Constants.RTCCallActionType.INVITE_REJECT, - inviteReject: { - convId: data.convId, - rtcSessionId: data.rtcSessionId, - cause: data.cause, - transactionId: data.transactionId - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.changeMediaType = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: changeMediaType...'); - var request = { - type: Constants.RTCCallActionType.CHANGE_MEDIA_TYPE, - changeMediaType: { - rtcSessionId: data.rtcSessionId, - sdp: data.sdp, - mediaTypes: convertMediaType(data.mediaType), - transactionId: data.transactionId, - screenSharePointerSupported: data.screenSharePointerSupported - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.changeMediaAccept = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: changeMediaAccept...'); - var request = { - type: Constants.RTCCallActionType.CHANGE_MEDIA_ACCEPT, - changeMediaAccept: { - rtcSessionId: data.rtcSessionId, - sdp: data.sdp, - transactionId: data.transactionId - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.changeMediaReject = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: changeMediaReject...'); - var request = { - type: Constants.RTCCallActionType.CHANGE_MEDIA_REJECT, - changeMediaReject: { - rtcSessionId: data.rtcSessionId, - transactionId: data.transactionId - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.prepareSession = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: prepareSession... '); - var request = { - type: Constants.RTCCallActionType.PREPARE, - prepare: { - convId: data.convId, - rtcSessionId: data.rtcSessionId, - ownerId: data.ownerId, - mediaNode: data.mediaNode, - isTelephonyConversation: data.isTelephonyConversation, - replaces: data.replaces - } - }; - - var keysToOmitFromResponse = ['response.rtcCall.prepare.servers.[].password']; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.rtcCall.prepare.servers, rsp.rtcCall.prepare.newRtcSessionId); - } - }, null, keysToOmitFromResponse); - }; - - this.renewTurnCredentials = function (rtcSessionId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: renewTurnCredentials... '); - var request = { - type: Constants.RTCCallActionType.RENEW_TURN_CREDENTIALS, - renewTurnCredentials: { - rtcSessionId: rtcSessionId - } - }; - - var keysToOmitFromResponse = ['response.rtcCall.renewTurnCredentials.servers.[].password']; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.rtcCall.renewTurnCredentials.servers); - } - }, null, keysToOmitFromResponse); - }; - - this.sendProgress = function (rtcSessionId, invitingUserId, localUserId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: sendProgress... '); - var request = { - type: Constants.RTCCallActionType.SEND_PROGRESS, - sendProgress: { - rtcSessionId: rtcSessionId, - userId: localUserId, - invitingUser: invitingUserId || '', - type: Constants.RTCProgressType.ALERTING // We currently only send alerting progress msgs - } - }; - - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.submitCallQualityRating = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: submitCallQualityRating...'); - - var request = { - type: Constants.RTCCallActionType.SUBMIT_RTC_QUALITY_RATING, - submitRtcQualityRating: { - rating: data - } - }; - sendRequest(Constants.ContentType.RTC_CALL, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public RTCSession related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.lockRtcSession = function (rtcSessionId, locked, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: lockRtcSession...'); - var request = { - type: Constants.RTCSessionActionType.LOCK, - lock: { - rtcSessionId: rtcSessionId, - locked: !!locked - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, resp) { - if (isResponseValid(err, resp, cb)) { - cb(null); - } - }); - }; - - this.muteRtcSession = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: muteRtcSession...'); - var request = { - type: Constants.RTCSessionActionType.MUTE_SESSION, - muteSession: { - rtcSessionId: data.rtcSessionId, - muted: !!data.muted, - excludedUserIds: data.excludedUserIds || undefined - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.muteParticipant = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: muteParticipant...'); - var request = { - type: Constants.RTCSessionActionType.MUTE, - mute: { - rtcSessionId: data.rtcSessionId, - muted: !!data.muted, - userIds: data.usersIds || undefined - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.addParticipantToRtcSession = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addParticipantToRtcCall...'); - var request = { - type: Constants.RTCSessionActionType.ADD_PARTICIPANT, - addParticipant: { - rtcSessionId: data.rtcSessionId, - userId: data.userId, - from: data.from, - to: data.to, - mediaType: convertMediaType(data.mediaType) - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.removeSessionParticipant = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeSessionParticipant...'); - var request = { - type: Constants.RTCSessionActionType.REMOVE_PARTICIPANT, - removeParticipant: { - rtcSessionId: data.rtcSessionId, - userId: data.userId - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getActiveSessions = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getActiveSessions...'); - var request = { - type: Constants.RTCSessionActionType.GET_ACTIVE_SESSIONS - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.rtcSession.activeSessions.session); - } - }); - }; - - this.getSession = function (rtcSessionId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getSession...'); - var request = { - type: Constants.RTCSessionActionType.GET_SESSION, - getSession: { - rtcSessionId: rtcSessionId - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.rtcSession.getSession.session); - } - }); - }; - - this.moveRtcSession = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: moveRtcSession...'); - var request = { - type: Constants.RTCSessionActionType.MOVE, - move: { - oldRtcSessionId: data.sessionId, - oldConversationId: data.conversationId, - newConversationId: data.newConversationId, - newRtcSessionId: data.newSessionId - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.startRecording = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: startRecording...'); - var request = { - type: Constants.RTCSessionActionType.START_RECORDING, - startRecording: { - convId: data.convId, - rtcSessionId: data.rtcSessionId, - mediaTypes: convertMediaType(data.mediaTypes) - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.stopRecording = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: stopRecording...'); - var request = { - type: Constants.RTCSessionActionType.STOP_RECORDING, - stopRecording: { - convId: data.convId, - rtcSessionId: data.rtcSessionId - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Raise Hand APIs - /////////////////////////////////////////////////////////////////////////////// - this.raiseQuestion = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: raiseQuestion...'); - var request = { - type: Constants.RTCSessionActionType.RAISE_QUESTION, - raiseQuestion: data - }; - var keysToOmitFromRequest = [ - 'request.rtcSession.raiseQuestion.question' - ]; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.rtcSession.raiseQuestion.questionNumber); - } - }, keysToOmitFromRequest); - }; - - this.inviteToStage = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: inviteToStage...'); - data.allowedMediaTypes = convertMediaType(); // AUDIO - var request = { - type: Constants.RTCSessionActionType.INVITE_TO_STAGE, - inviteToStage: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.inviteToStageAnswer = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: inviteToStageAnswer...'); - var request = { - type: Constants.RTCSessionActionType.INVITE_TO_STAGE_ANSWER, - inviteToStageAnswer: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.inviteToStageCancel = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: inviteToStageCancel...'); - var request = { - type: Constants.RTCSessionActionType.INVITE_TO_STAGE_CANCEL, - inviteToStageCancel: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.removeFromStage = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeFromStage...'); - var request = { - type: Constants.RTCSessionActionType.REMOVE_FROM_STAGE, - removeFromStage: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getQuestions = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getQuestions...'); - var request = { - type: Constants.RTCSessionActionType.GET_QUESTIONS, - getQuestions: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.rtcSession.getQuestions.questions); - } - }); - }; - - this.updateQuestionState = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateQuestionState...'); - var request = { - type: Constants.RTCSessionActionType.UPDATE_QUESTION_STATE, - updateQuestionState: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Curtain related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.openCurtain = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: openCurtain...'); - var request = { - type: Constants.RTCSessionActionType.OPEN_CURTAIN, - openCurtain: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.closeCurtain = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: closeCurtain...'); - var request = { - type: Constants.RTCSessionActionType.CLOSE_CURTAIN, - closeCurtain: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Get Node State API - /////////////////////////////////////////////////////////////////////////////// - this.getNodeState = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getNodeState...'); - - var request = { - type: Constants.RTCSessionActionType.GET_NODE_STATE, - getNodeState: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.rtcSession.getNodeState.nodeData); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Whiteboarding API - /////////////////////////////////////////////////////////////////////////////// - this.enableWhiteboard = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: enableWhiteboard...'); - var request = { - type: Constants.RTCSessionActionType.ENABLE_WHITEBOARD, - enableWhiteboard: { - rtcSessionId: data.rtcSessionId, - viewbox: data.viewbox - } - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.disableWhiteboard = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: disableWhiteboard...'); - - var request = { - type: Constants.RTCSessionActionType.DISABLE_WHITEBOARD, - disableWhiteboard: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.addWhiteboardElement = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addWhiteboardElement...'); - - var request = { - type: Constants.RTCSessionActionType.ADD_WHITEBOARD_ELEMENT, - addWhiteboardElement: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.removeWhiteboardElement = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeWhiteboardElement...'); - - var request = { - type: Constants.RTCSessionActionType.REMOVE_WHITEBOARD_ELEMENT, - removeWhiteboardElement: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.updateWhiteboardElement = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateWhiteboardElement...'); - - var request = { - type: Constants.RTCSessionActionType.UPDATE_WHITEBOARD_ELEMENT, - updateWhiteboardElement: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.clearWhiteboard = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: clearWhiteboard...'); - - var request = { - type: Constants.RTCSessionActionType.CLEAR_WHITEBOARD, - clearWhiteboard: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null); - } - }); - }; - - this.getWhiteboard = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getWhiteboard...'); - - var request = { - type: Constants.RTCSessionActionType.GET_WHITEBOARD, - getWhiteboard: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var data = rsp.rtcSession.getWhiteboard.whiteboard; - cb(null, data); - } - }); - }; - - this.setWhiteboardBackground = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setWhiteboardBackground...'); - - if (_clientApiVersion < SP86_API_VERSION) { - logger.warn('[ClientApiHandler]: The setWhiteboardBackground operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ThirdPartyError.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.RTCSessionActionType.SET_WHITEBOARD_BACKGROUND, - setWhiteboardBackground: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.clearWhiteboardBackground = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: clearWhiteboardBackground...'); - - if (_clientApiVersion < SP86_API_VERSION) { - logger.warn('[ClientApiHandler]: The clearWhiteboardBackground operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ThirdPartyError.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.RTCSessionActionType.CLEAR_WHITEBOARD_BACKGROUND, - clearWhiteboardBackground: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.toggleWhiteboardOverlay = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: toggleWhiteboardOverlay...'); - - if (_clientApiVersion < SP86_API_VERSION) { - logger.warn('[ClientApiHandler]: The toggleWhiteboardOverlay operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ThirdPartyError.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.RTCSessionActionType.TOGGLE_WHITEBOARD_OVERLAY, - toggleWhiteboardOverlay: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.undoWhiteboard = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: undoWhiteboard...'); - - if (_clientApiVersion < SP86_API_VERSION) { - logger.warn('[ClientApiHandler]: The undoWhiteboard operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ThirdPartyError.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.RTCSessionActionType.UNDO_WHITEBOARD, - undoWhiteboard: data - }; - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.sendClientInfo = function (clientInfo, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: sendClientInfo...'); - - var request = { - type: Constants.RTCSessionActionType.SEND_CLIENT_INFO, - sendClientInfo: { - clientInfo: clientInfo - } - }; - - sendRequest(Constants.ContentType.RTC_SESSION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Instrumentation related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.submitClientData = function (instrumentationData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: submitClientData...'); - var request = { - type: Constants.InstrumentationActionType.SUBMIT_CLIENT_DATA, - submitClientData: { - instrumentationData: instrumentationData - } - }; - sendRequest(Constants.ContentType.INSTRUMENTATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var res = rsp.instrumentation && rsp.instrumentation.submitClientData && - rsp.instrumentation.submitClientData.submitDataResult; - cb(null, res || Constants.SubmitDataResult.OK); - } - }); - }; - - this.submitQOSData = function (qosData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: submitQOSData...'); - var request = { - type: Constants.InstrumentationActionType.SUBMIT_QOS_DATA, - submitQOSData: { - clientTime: Date.now(), // Used by the Access Server to adjust the timestamps (if necessary) - dataSets: qosData - } - }; - sendRequest(Constants.ContentType.INSTRUMENTATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public ActivityStream related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.getNotifications = function (timestamp, numberOfItems, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getNotifications...'); - - var request = { - type: Constants.ActivityStreamActionType.GET_ACTIVITIES_BY_USER, - getActivitiesByUser: { - timestamp: timestamp, - numberOfItems: numberOfItems - } - }; - - sendRequest(Constants.ContentType.ACTIVITYSTREAM, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var data = rsp.activityStream.getActivitiesByUserResult; - cb(null, data); - } - }); - }; - - this.toggleReadUnread = function (activityItemId, isRead, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: toggleReadUnread...'); - - var request = { - type: Constants.ActivityStreamActionType.UPDATE_ACTIVITY_READ, - updateActivityReadState: { - activityId: activityItemId, - read: isRead - } - }; - - sendRequest(Constants.ContentType.ACTIVITYSTREAM, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var data = rsp.activityStream.updateResult; - cb(null, data.updatedItem); - } - }); - }; - - this.markAllNotificationsAsRead = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: markAllNotificationsAsRead...'); - - var request = { - type: Constants.ActivityStreamActionType.MARK_ALL_READ - }; - - sendRequest(Constants.ContentType.ACTIVITYSTREAM, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var data = rsp.activityStream.markAllReadResult; - cb(null, data); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Administration related Interfaces - ///////////////////////////////////////////////////////////////////////////////; - this.getTenant = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTenant...'); - var request = { - type: Constants.AdministrationActionType.GET_TENANT - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTenant.tenant); - } - }); - }; - - this.addUser = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addUser...'); - var request = { - type: Constants.AdministrationActionType.ADD_USER, - addUser: { - userAttributes: { - emailAddress: data.emailAddress, - lastName: data.lastName, - firstName: data.firstName, - locale: data.locale - }, - password: data.password, - packageId: data.packageId, - accountTemplateId: data.accountTemplateId - } - }; - var keysToOmitFromRequest = [ - 'request.administration.addUser.password' - ]; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.addUserResult.user); - } - }, keysToOmitFromRequest); - }; - - this.addMeetingPointV2 = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addMeetingPointV2...'); - var request = { - type: Constants.AdministrationActionType.ADD_MEETING_POINT_V2, - addMeetingPoint: { - userAttributes: { - emailAddress: data.emailAddress, - lastName: data.lastName, - firstName: data.firstName, - locale: data.locale, - location: data.location, - registrationCodeTtl: data.timeout - }, - packageId: data.packageId - } - }; - - var keysToOmitFromResponse = [ - 'response.administration.addMeetingPointV2Result.registrationCode' - ]; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.addMeetingPointV2Result); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - this.updateMeetingPointV2 = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateMeetingPointV2...'); - - var request = { - type: Constants.AdministrationActionType.UPDATE_MEETING_POINT_V2, - updateMeetingPoint: { - userId: data.id, - registrationCodeTtl: data.timeout - } - }; - - var keysToOmitFromResponse = [ - 'response.administration.updateMeetingPointV2Result.registrationCode' - ]; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.updateMeetingPointV2Result.registrationCode); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - this.regenerateApiKey = function (id, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: regenerateApiKey...'); - - var request = { - type: Constants.AdministrationActionType.REGENERATE_API_KEY, - regenerateApiKey: { - userId: id - } - }; - - var keysToOmitFromResponse = [ - 'response.administration.regenerateApiKey.apiKey' - ]; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.regenerateApiKey.apiKey); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - this.deleteUser = function (id, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteUser...'); - var request = { - type: Constants.AdministrationActionType.DELETE_USER, - deleteUser: { - userId: id - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.inviteUser = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: inviteUser...'); - var request = { - type: Constants.AdministrationActionType.INVITE_USER, - inviteUser: { - userAttributes: { - emailAddress: data.userAttributes.emailAddress, - lastName: data.userAttributes.lastName, - firstName: data.userAttributes.firstName - }, - packageId: data.packageId, - accountTemplateId: data.accountTemplateId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.inviteUser.resultType); - } - }, null, null, tenantContext); - }; - - this.inviteMultipleUsers = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: inviteMultipleUsers...'); - var request = { - type: Constants.AdministrationActionType.INVITE_MULTIPLE_USERS, - inviteMultipleUsers: data - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.invitePartnerUsers = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: invitePartnerUsers...'); - var request = { - type: Constants.AdministrationActionType.INVITE_MULTIPLE_PARTNER_USERS, - inviteMultiplePartnerUsers: { - userAttributes: data - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.resendInvite = function (id, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: resendInvite...'); - var request = { - type: Constants.AdministrationActionType.RESEND_USER_INVITATION, - resendUserInvitation: { - userId: id - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.resendUserInvitation.resultType); - } - }); - }; - - this.checkInvalidTenant = function (tenantIds, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: checkInvalidTenant...'); - var request = { - type: Constants.AdministrationActionType.CHECK_INVALID_TENANTS, - checkInvalidTenants: { - tenantIds: tenantIds - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.administration.checkInvalidTenants.tenantIds); - } - }); - }; - - this.setTelephonyTrunkData = function (defaultCallerId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setTelephonyTrunkData...'); - var request = { - type: Constants.AdministrationActionType.SET_TELEPHONY_TRUNK_DATA, - setTelephonyTrunkData: { - defaultCallerId: defaultCallerId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(); - } - }); - }; - - this.getTelephonyTrunkStatus = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyTrunkStatus...'); - var request = { - type: Constants.AdministrationActionType.GET_TELEPHONY_TRUNK_STATUS - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTelephonyTrunkStatus); - } - }, null, null, tenantContext); - }; - - this.getTelephonyTrunkGroups = function (queryData, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyTrunkGroups...'); - - if (!queryData) { - logger.warn('[ClientApiHandler]: No query data provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.GET_TELEPHONY_TRUNK_GROUPS, - getTelephonyTrunkGroups: { - pageSize: queryData.pageSize, - pagePointer: queryData.pagePointer || undefined - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTelephonyTrunkGroupsResult); - } - }, null, null, tenantContext); - }; - - this.getTelephonyTrunkGroup = function (trunkGroupId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyTrunkGroup...'); - - if (!trunkGroupId) { - logger.warn('[ClientApiHandler]: No trunk group id provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.GET_TELEPHONY_TRUNK_GROUP, - getTelephonyTrunkGroup: { - telephonyTrunkGroupId: trunkGroupId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTelephonyTrunkGroupResult); - } - }); - }; - - this.addTelephonyTrunkGroup = function (trunkGroupData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addTelephonyTrunkGroup...'); - - if (!trunkGroupData) { - logger.warn('[ClientApiHandler]: No trunk group data provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.ADD_TELEPHONY_TRUNK_GROUP, - addTelephonyTrunkGroup: { - telephonyTrunkGroupName: trunkGroupData.telephonyTrunkGroupName, - telephonyTrunkId: trunkGroupData.telephonyTrunkIds - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.addTelephonyTrunkGroupResult); - } - }); - }; - - this.removeTelephonyTrunkGroup = function (trunkGroupId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeTelephonyTrunkGroup...'); - - if (!trunkGroupId) { - logger.warn('[ClientApiHandler]: No trunk group id provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.REMOVE_TELEPHONY_TRUNK_GROUP, - removeTelephonyTrunkGroup: { - telephonyTrunkGroupId: trunkGroupId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.removeTelephonyTrunkGroupResult.responseCode); - } - }); - }; - - this.updateTelephonyTrunkGroup = function (trunkGroupData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTelephonyTrunkGroup...'); - - if (!trunkGroupData) { - logger.warn('[ClientApiHandler]: No trunk group data provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.UPDATE_TELEPHONY_TRUNK_GROUP, - updateTelephonyTrunkGroup: { - telephonyTrunkGroupId: trunkGroupData.telephonyTrunkGroupId, - telephonyTrunkGroupName: trunkGroupData.telephonyTrunkGroupName, - telephonyTrunkId: trunkGroupData.telephonyTrunkIds - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.updateTelephonyTrunkGroupResult); - } - }); - }; - - this.getTenantBridgeNumbers = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTenantBridgeNumbers...'); - var request = { - type: Constants.AdministrationActionType.GET_TENANT_BRIDGE_NUMBERS - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var resp = rsp.administration.getTenantBridgeNumbers; - cb(null, (resp && resp.bridgeNumbers) || []); - } - }, null, null, tenantContext); - }; - - this.setTenantBridgeNumbers = function (bridgeNumbers, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setTenantBridgeNumbers...'); - var request = { - type: Constants.AdministrationActionType.SET_TENANT_BRIDGE_NUMBERS, - setTenantBridgeNumbers: { - bridgeNumbers: bridgeNumbers - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(); - } - }, null, null, tenantContext); - }; - - this.exportUserList = function (formats, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: exportUserList...'); - if (formats && !Array.isArray(formats)) { - formats = [formats]; - } - - var request = { - type: Constants.AdministrationActionType.EXPORT_TENANT, - exportTenant: { - exportFormats: formats - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.addAutoConfigurableTrunk = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addAutoConfigurableTrunk...'); - - var request = { - type: Constants.AdministrationActionType.ADD_TRUNK_WITHOUT_PARAMETERS - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.addTrunk.configuration); - } - }, null, null, tenantContext); - }; - - this.getTenantTelephonyConfiguration = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTenantTelephonyConfiguration...'); - - var request = { - type: Constants.AdministrationActionType.GET_TENANT_TELEPHONY_CONFIGURATION - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTenantTelephonyConfiguration.configuration); - } - }, tenantContext); - }; - - this.getTelephonyRoutingRules = function (queryData, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTelephonyRoutingRules...'); - - var request = { - type: Constants.AdministrationActionType.GET_TELEPHONY_ROUTING_RULES, - getTelephonyRoutingRules: { - pageSize: queryData.pageSize, - pagePointer: queryData.pagePointer - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getTelephonyRoutingRulesResult); - } - }, null, null, tenantContext); - }; - - this.addTelephonyRoutingRule = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addTelephonyRoutingRule...'); - - var request = { - type: Constants.AdministrationActionType.ADD_TELEPHONY_ROUTING_RULE, - addTelephonyRoutingRule: { - telephonyRoutingRule: { - routePosition: data.routePosition, - condition1: data.condition1, - gtcUserId: data.gtcUserId, - gtcUserGroupId: data.gtcUserGroupId, - operator: data.operator || undefined, - condition2: data.condition2 || undefined, - application: data.application || undefined - } - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.updateTelephonyRoutingRule = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTelephonyRoutingRule...'); - - var request = { - type: Constants.AdministrationActionType.UPDATE_TELEPHONY_ROUTING_RULE, - updateTelephonyRoutingRule: { - telephonyRoutingRule: { - routePosition: data.routePosition, - condition1: data.condition1, - gtcUserId: data.gtcUserId, - gtcUserGroupId: data.gtcUserGroupId, - routeId: data.routeId || undefined, - operator: data.operator || undefined, - condition2: data.condition2 || undefined, - application: data.application || undefined - } - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.removeTelephonyRoutingRule = function (ruleId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeTelephonyRoutingRule...'); - - var request = { - type: Constants.AdministrationActionType.REMOVE_TELEPHONY_ROUTING_RULE, - removeTelephonyRoutingRule: { - telephonyRoutingRuleId: ruleId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.testTelephonyRoutingRules = function (ruleData, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: testTelephonyRoutingRule...'); - - var request = { - type: Constants.AdministrationActionType.TEST_TELEPHONY_ROUTING_RULES, - testTelephonyRoutingRules: { - destination: ruleData.destination, - origin: ruleData.origin || undefined, - application: ruleData.application || undefined, - considerTelephonyConnectorsStates: !!ruleData.considerTelephonyConnectorsStates - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.testTelephonyRoutingRulesResult); - } - }, null, null, tenantContext); - }; - - this.updateTenantTelephonyConfiguration = function (defaultRouting, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTenantTelephonyConfiguration...'); - - var request = { - type: Constants.AdministrationActionType.UPDATE_TENANT_TELEPHONY_CONFIGURATION, - updateTenantTelephonyConfiguration: { - defaultRouting: defaultRouting - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.setTrunkPassword = function (trunkId, password, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setTrunkPassword...'); - var request = { - type: Constants.AdministrationActionType.SET_TRUNK_PASSWORD, - setTrunkPassword: { - trunkId: trunkId, - password: password - } - }; - - var keysToOmitFromRequest = [ - 'request.administration.setTrunkPassword.password' - ]; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, keysToOmitFromRequest); - }; - - this.updateTrunk = function (trunk, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTrunk...'); - - if (!this.isUpdateTrunkSupported()) { - logger.warn('[ClientApiHandler]: The update trunk operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AdministrationActionType.UPDATE_TRUNK, - updateTrunk: { - trunkId: trunk.gtcUserId, - displayName: trunk.gtcTrunkName, - defaultCallerId: trunk.defaultCallerId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.setTenantSettings = function (tenantSettings, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setTenantSettings...'); - - var request = { - type: Constants.AdministrationActionType.SET_TENANT_SETTINGS, - setTenantSettings: { - tenantSettings: tenantSettings - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, tenantContext); - }; - - this.updateTenantName = function (companyName, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTenantName...'); - - var request = { - type: Constants.AdministrationActionType.UPDATE_TENANT, - updateTenant: { - companyName: companyName - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getTenantLoginProviders = function (tenantId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTenantLoginProviders...'); - - var request = { - type: Constants.AdministrationActionType.GET_ALL_TENANT_LOGIN_PROVIDERS, - getAllTenantLoginProviders: { - tenantId: tenantId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.administration.getAllTenantLoginProvidersResult.tenantLoginProvidersList || []); - } - }); - }; - - this.addTenantLoginProvider = function (tenantLoginProvider, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addTenantLoginProvider...'); - - var request = { - type: Constants.AdministrationActionType.SAVE_TENANT_LOGIN_PROVIDER, - saveTenantLoginProvider: { - loginProvider: tenantLoginProvider - - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.saveTenantLoginProviderResult.storedProvider); - - } - }); - }; - - this.updateTenantLoginProvider = function (tenantLoginProvider, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateTenantLoginProvider...'); - - var request = { - type: Constants.AdministrationActionType.UPDATE_TENANT_LOGIN_PROVIDER, - updateTenantLoginProvider: { - loginProvider: tenantLoginProvider - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.updateTenantLoginProviderResult.updatedLoginProvider); - } - }); - }; - - this.deleteTenantLoginProvider = function (tenantLoginProvider, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteTenantLoginProvider...'); - - var request = { - type: Constants.AdministrationActionType.DELETE_TENANT_LOGIN_PROVIDER, - deleteTenantLoginProvider: { - providerId: tenantLoginProvider.providerId, - tenantId: tenantLoginProvider.tenantId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.deleteTenantLoginProviderResult); - } - }); - }; - - this.getOpenScapeUser = function (dn, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getOpenScapeUser...'); - - if (!dn) { - logger.warn('[ClientApiHandler]: No dn was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var keysToOmitFromResponse = [ - 'response.administration.getOpenScapeUserResult.user.cloudPin', - 'response.administration.getOpenScapeUserResult.user.securityPin' - ]; - - var request = { - type: Constants.AdministrationActionType.GET_OPENSCAPE_USER, - getOpenScapeUser: { - directoryNumber: dn - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getOpenScapeUserResult); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - this.getOpenScapeUserV2 = function (dn, circuitUserId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getOpenScapeUserV2...'); - - if (!this.isDeskphoneSupported()) { - logger.warn('[ClientApiHandler]: The getOpenScapeUserV2 operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - if (!dn) { - logger.warn('[ClientApiHandler]: No dn was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - if (!circuitUserId) { - logger.warn('[ClientApiHandler]: No circuitUserId was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var keysToOmitFromResponse = [ - 'response.administration.getOpenScapeUserResult.user.cloudPin', - 'response.administration.getOpenScapeUserResult.user.securityPin' - ]; - - var request = { - type: Constants.AdministrationActionType.GET_OPENSCAPE_USER_V2, - getV2OpenScapeUser: { - directoryNumber: dn, - circuitUserId: circuitUserId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getOpenScapeUserResult); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - this.getVacantHomeDirectoryNumbers = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getVacantHomeDirectoryNumbers...'); - - var request = { - type: Constants.AdministrationActionType.GET_VACANT_HOME_DIRECTORY_NUMBERS, - getVacantHomeDirectoryNumbers: {} - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getVacantHomeDirectoryNumbersResult.directoryNumbers); - } - }, null, null, tenantContext); - }; - - this.createOpenScapeUser = function (openScapeUser, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: createOpenScapeUser...'); - - if (!openScapeUser) { - logger.warn('[ClientApiHandler]: No user data were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.CREATE_OPENSCAPE_USER, - createOpenScapeUser: { - circuitUserId: openScapeUser.circuitUserId, - directoryNumber: openScapeUser.directoryNumber, - extension: openScapeUser.extension, - dialingPermission: openScapeUser.dialingPermission, - announcementsLanguage: openScapeUser.announcementsLanguage, - deviceDisplayLanguage: openScapeUser.deviceDisplayLanguage, - timeZone: openScapeUser.timeZone, - hasDevice: openScapeUser.hasDevice - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.createOpenScapeUserResult); - } - }, null, null, tenantContext); - }; - - this.updateOpenScapeUser = function (openScapeUser, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateOpenScapeUser...'); - - if (!openScapeUser) { - logger.warn('[ClientApiHandler]: No user data were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.UPDATE_OPENSCAPE_USER, - updateOpenScapeUser: { - directoryNumber: openScapeUser.directoryNumber, - extension: openScapeUser.extension, - dialingPermission: openScapeUser.dialingPermission, - announcementsLanguage: openScapeUser.announcementsLanguage, - deviceDisplayLanguage: openScapeUser.deviceDisplayLanguage, - timeZone: openScapeUser.timeZone, - hasDevice: openScapeUser.hasDevice, - circuitUserId: openScapeUser.circuitUserId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.updateOpenScapeUserResult); - } - }, null, null, tenantContext); - }; - - this.deleteOpenScapeUser = function (data, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteOpenScapeUser...'); - - if (!data || !data.userId || !data.dn) { - logger.warn('[ClientApiHandler]: User id or dn was not provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.DELETE_OPENSCAPE_USER, - deleteOpenScapeUser: { - userId: data.userId, - directoryNumber: data.dn - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.deleteOpenScapeUserResult); - } - }, null, null, tenantContext); - }; - - this.createOpenScapeSite = function (openScapeSite, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: createOpenScapeSite...'); - - if (!openScapeSite) { - logger.warn('[ClientApiHandler]: No site data were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.CREATE_OPENSCAPE_SITE, - createOpenScapeSite: { - site: openScapeSite - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.createOpenScapeSiteResult.site); - } - }, null, null, tenantContext); - }; - - this.getOpenScapeSite = function (siteId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getOpenScapeSite...'); - - if (!siteId) { - logger.warn('[ClientApiHandler]: No siteId was provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.GET_OPENSCAPE_SITE, - getOpenScapeSite: { - siteId: siteId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getOpenScapeSiteResult.site); - } - }, null, null, tenantContext); - }; - - this.updateOpenScapeSite = function (openScapeSite, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: updateOpenScapeSite...'); - - if (!openScapeSite) { - logger.warn('[ClientApiHandler]: No site data were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.UPDATE_OPENSCAPE_SITE, - updateOpenScapeSite: { - site: openScapeSite - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.updateOpenScapeSiteResult.site); - } - }, null, null, tenantContext); - }; - - this.deleteOpenScapeSites = function (openScapeSites, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteOpenScapeSites...'); - - var request = { - type: Constants.AdministrationActionType.DELETE_OPENSCAPE_SITES, - deleteOpenScapeSites: { - sites: openScapeSites - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.deleteOpenScapePublicRange = function (siteId, rangeId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteOpenScapePublicRange...'); - - var request = { - type: Constants.AdministrationActionType.DELETE_PUBLIC_NUMBER_RANGE, - deletePublicNumberRange: { - siteId: siteId, - rangeId: rangeId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.deleteOpenScapePrivateRange = function (siteId, rangeId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: deleteOpenScapePrivateRange...'); - - var request = { - type: Constants.AdministrationActionType.DELETE_PRIVATE_NUMBER_RANGE, - deletePrivateNumberRange: { - siteId: siteId, - rangeId: rangeId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.getOpenScapeTenant = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getOpenScapeTenant...'); - - var request = { - type: Constants.AdministrationActionType.GET_OPENSCAPE_TENANT, - getOpenScapeTenant: {} - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.getOpenScapeTenantResult.tenant); - } - }, null, null, tenantContext); - }; - - this.getAutomatedAttendantConfiguration = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAutomatedAttendantConfiguration...'); - - var request = { - type: Constants.AdministrationActionType.GET_AUTOMATEDATTENDANT_CONFIGURATION - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - var result = rsp.administration.getAutomatedAttendantConfigurationResult; - cb(null, result.configuration || []); - } - }, null, null, tenantContext); - }; - - this.setAutomatedAttendantConfiguration = function (config, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setAutomatedAttendantConfiguration...'); - - if (!config) { - logger.warn('[ClientApiHandler]: No configuration were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.SET_AUTOMATEDATTENDANT_CONFIGURATION, - setAutomatedAttendantConfiguration: { - configuration: config - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.getManageableTenants = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getManageableTenants...'); - - if (_clientApiVersion < SP88_API_VERSION) { - logger.warn('[ClientApiHandler]: The getManageableTenants operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ThirdPartyError.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AdministrationActionType.GET_PARTNER_MANAGEABLE_TENANTS - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.administration.getPartnerManageableTenantResult.tenants); - } - }); - }; - - this.getCMRSettings = function (userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getCMRSettings...'); - - if (!this.isCMRSettingsSupported()) { - logger.warn('[ClientApiHandler]: The getCMRSettings operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AdministrationActionType.GET_CMR_SETTINGS, - getCMRSettings: { - userId: userId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(err, rsp.administration.getCMRSettingsResult.settings); - } - }); - }; - - this.setCMRSettings = function (userId, settings, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: setCMRSettings...'); - - if (!this.isCMRSettingsSupported()) { - logger.warn('[ClientApiHandler]: The setCMRSettings operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AdministrationActionType.SET_CMR_SETTINGS, - setCMRSettings: { - userId: userId, - settings: settings - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Developer Console - /////////////////////////////////////////////////////////////////////////////// - this.getApplicationsList = function (tenantId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getApplicationsList...'); - - var request = { - type: Constants.AdministrationActionType.GET_CUSTOM_APPS, - getCustomApps: { - tenantId: tenantId - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb, true)) { - cb(null, rsp.administration.getCustomApps.customApps); - } - }); - }; - - this.createApplication = function (customApp, smallLogo, largeLogo, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: createApplication...'); - - if (!customApp) { - logger.warn('[ClientApiHandler]: No application data were provided'); - sendAsyncResp(cb, Constants.ReturnCode.INVALID_MESSAGE); - return; - } - - var request = { - type: Constants.AdministrationActionType.ADD_CUSTOM_APP, - addCustomApp: { - customApp: customApp, - smallLogo: smallLogo, - largeLogo: largeLogo - } - }; - - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.administration.addCustomAppResult); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Third Party related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.getExchangeSettings = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getExchangeSettings...'); - - var request = { - type: Constants.ThirdPartyConnectorsActionType.GET_SETTINGS, - getSettings: { - type: Constants.ThirdPartyConnectorType.EXCHANGE_CONNECTOR - } - }; - - var keysToOmitFromResponse = ['response.thirdpartyconnectors.getSettings.exchange']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.getSettings.exchange); - } - }, null, keysToOmitFromResponse); - }; - - this.saveExchangeSettings = function (settings, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: saveExchangeSettings...'); - - if (_developmentMode) { - // Create an encryption key, since the Mock server won't create one for us - settings.encryptionKey = String(Date.now()); - } - - var request = { - type: Constants.ThirdPartyConnectorsActionType.SAVE_SETTINGS, - saveSettings: { - type: Constants.ThirdPartyConnectorType.EXCHANGE_CONNECTOR, - exchange: settings - } - }; - - var keysToOmitFromRequest = ['request.thirdpartyconnectors.saveSettings.exchange']; - var keysToOmitFromResponse = ['response.thirdpartyconnectors.saveSettings.exchange']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.saveSettings.exchange); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.saveOpenXchangeSettings = function (settings, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: saveOpenXchangeSettings...'); - - var request = { - type: Constants.ThirdPartyConnectorsActionType.SAVE_SETTINGS, - saveSettings: { - type: Constants.ThirdPartyConnectorType.OPEN_XCHANGE, - openXchange: settings - } - }; - - var keysToOmitFromRequest = ['request.thirdpartyconnectors.saveSettings.openXchange']; - var keysToOmitFromResponse = ['response.thirdpartyconnectors.saveSettings.openXchange']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.saveSettings.openXchange); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.getThirdpartyProviderSettings = function (data, cb) { - cb = cb || NOP; - data = data || {}; - logger.debug('[ClientApiHandler]: getThirdpartyProviderSettings...'); - - if (_developmentMode) { - // Create an encryption key, since the Mock server won't create one for us - sendAsyncResp(cb, null, {encryptionKey: data.emailAddress}); - return; - } - - var request = { - type: Constants.ThirdPartyConnectorsActionType.GET_SETTINGS, - getSettings: { - type: Constants.ThirdPartyConnectorType.CLIENT_THIRDPARTY - } - }; - - var keysToOmitFromResponse = ['response.thirdpartyconnectors.getSettings.clientThirdparty']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.getSettings.clientThirdparty); - } - }, null, keysToOmitFromResponse); - }; - - this.saveThirdpartyProviderSettings = function (settings, cb) { - cb = cb || NOP; - settings = settings || {}; - logger.debug('[ClientApiHandler]: saveThirdpartyProviderSettings...'); - - if (_developmentMode) { - // Create an encryption key, since the Mock server won't create one for us - sendAsyncResp(cb, null, {encryptionKey: settings.emailAddress}); - return; - } - - var request = { - type: Constants.ThirdPartyConnectorsActionType.SAVE_SETTINGS, - saveSettings: { - type: Constants.ThirdPartyConnectorType.CLIENT_THIRDPARTY, - clientThirdparty: { - encryptionKey: '' - } - } - }; - - var keysToOmitFromRequest = ['request.thirdpartyconnectors.saveSettings.clientThirdparty.encryptionKey']; - var keysToOmitFromResponse = ['response.thirdpartyconnectors.saveSettings.clientThirdparty.encryptionKey']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.saveSettings.clientThirdparty); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.connectToThirdParty = function (provider, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: connectToThirdParty ' + provider + '...'); - - var request; - switch (provider) { - case Constants.ThirdPartyConnectorType.BOX: - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - case Constants.ThirdPartyConnectorType.GOOGLE_DRIVE: - case Constants.ThirdPartyConnectorType.ONE_DRIVE: - request = { - type: Constants.ThirdPartyConnectorsActionType.CONNECT, - connect: { - type: provider - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromResponse = ['response.thirdpartyconnectors.connect.authorizeUrl']; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - if (rsp.thirdpartyconnectors.connect) { - cb(null, rsp.thirdpartyconnectors.connect.authorizeUrl); - } else { - cb(Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - } - } - }, null, keysToOmitFromResponse); - }; - - this.refreshConnectionToThirdParty = function (provider, refreshToken, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: refreshConnectionToThirdParty ' + provider + '...'); - - var request; - switch (provider) { - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - request = { - type: Constants.ThirdPartyConnectorsActionType.REFRESH_CONNECTION, - refreshConnection: { - type: provider, - refreshToken: refreshToken - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromRequest = [ - 'request.thirdpartyconnectors.refreshConnection.refreshToken' - ]; - var keysToOmitFromResponse = [ - 'response.thirdpartyconnectors.refreshConnection.accessToken', - 'response.thirdpartyconnectors.refreshConnection.refreshToken' - ]; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - if (rsp.thirdpartyconnectors.refreshConnectionResult) { - cb(null, rsp.thirdpartyconnectors.refreshConnectionResult); - } else { - cb(Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - } - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.getFolderItems = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getFolderItems for ', data.provider); - - var request; - - switch (data.provider) { - case Constants.ThirdPartyConnectorType.BOX: - case Constants.ThirdPartyConnectorType.OPEN_XCHANGE: - request = { - type: Constants.ThirdPartyConnectorsActionType.GET_FOLDER_ITEMS, - getFolderItems: { - type: data.provider, - id: data.folderId, - index: data.indexCursor, - pageSize: data.pageSize - } - }; - break; - case Constants.ThirdPartyConnectorType.GOOGLE_DRIVE: - case Constants.ThirdPartyConnectorType.ONE_DRIVE: - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - request = { - type: Constants.ThirdPartyConnectorsActionType.GET_FOLDER_ITEMS_WITH_CURSOR, - getFolderItemsWithCursor: { - type: data.provider, - syncpointId: data.syncpointId || undefined, - id: data.folderId, - cursor: data.indexCursor, - pageSize: data.pageSize, - accessToken: data.accessToken || undefined - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromRequest = [ - 'request.thirdpartyconnectors.getFolderItemsWithCursor.accessToken' - ]; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - // Make sure to reply with folder id in order to avoid issues of fast successive requests - cb(null, { - folder: data.folderId, - items: rsp.thirdpartyconnectors.getFolderItemsResult - }); - } - }, keysToOmitFromRequest); - }; - - this.getConnectionStatus = function (provider, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getConnectionStatus ' + provider + '...'); - - var request = { - type: Constants.ThirdPartyConnectorsActionType.GET_CONNECTION_STATUS, - getConnectionStatus: { - type: provider - } - }; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.getConnectionStatus.connected); - } - }); - }; - - this.disconnectFromThirdParty = function (provider, accessToken, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: disconnectFromThirdParty ' + provider + '...'); - - var request; - - switch (provider) { - case Constants.ThirdPartyConnectorType.BOX: - case Constants.ThirdPartyConnectorType.OPEN_XCHANGE: - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - case Constants.ThirdPartyConnectorType.GOOGLE_DRIVE: - case Constants.ThirdPartyConnectorType.ONE_DRIVE: - request = { - type: Constants.ThirdPartyConnectorsActionType.DISCONNECT, - disconnect: { - type: provider, - accessToken: accessToken || undefined - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromRequest = [ - 'request.thirdpartyconnectors.disconnect.accessToken' - ]; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, keysToOmitFromRequest); - }; - - this.shareFolderItems = function (requestData, cb) { - cb = cb || NOP; - if (!requestData) { - sendAsyncResp(cb, Constants.ReturnCode.MISSING_REQUIRED_PARAMETER); - return; - } - var provider = requestData.provider; - var itemsData = requestData.itemsData; - logger.debug('[ClientApiHandler]: shareFolderItems ' + provider + '...'); - - var request; - - switch (provider) { - case Constants.ThirdPartyConnectorType.BOX: - case Constants.ThirdPartyConnectorType.OPEN_XCHANGE: - case Constants.ThirdPartyConnectorType.GOOGLE_DRIVE: - case Constants.ThirdPartyConnectorType.ONE_DRIVE: - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - request = { - type: Constants.ThirdPartyConnectorsActionType.SHARE_FOLDER_ITEMS, - shareFolderItems: { - type: provider, - shareItems: itemsData, - conversationId: requestData.convId, - accessToken: requestData.accessToken || undefined - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromRequest = [ - 'request.thirdpartyconnectors.shareFolderItems.accessToken' - ]; - var keysToOmitFromResponse = [ - 'response.thirdpartyconnectors.shareFolderItems.items.[].shareLinkPassword', - 'response.thirdpartyconnectors.shareFolderItems.items.[].downloadUrl', - 'response.thirdpartyconnectors.shareFolderItems.items.[].previewUrl', - 'response.thirdpartyconnectors.shareFolderItems.appKey', - 'response.thirdpartyconnectors.shareFolderItems.accessToken' - ]; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.shareFolderItems); - } - }, keysToOmitFromRequest, keysToOmitFromResponse); - }; - - this.unshareItems = function (data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: ushareItems ' + data.provider + '...'); - - var request; - - // ANS-3467 When unsharing a box file in a conversation, - // it gets unshared in all conversations this file exists - switch (data.provider) { - case Constants.ThirdPartyConnectorType.OPEN_XCHANGE: - case Constants.ThirdPartyConnectorType.SYNCPLICITY: - request = { - type: Constants.ThirdPartyConnectorsActionType.UNSHARE_FOLDER_ITEMS, - unshareFolderItems: { - type: data.provider, - fileId: data.itemIds, - accessToken: data.accessToken || undefined - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var keysToOmitFromRequest = [ - 'request.thirdpartyconnectors.unshareFolderItems.accessToken' - ]; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, keysToOmitFromRequest); - }; - - this.getEditUrl = function (file, cb) { - logger.debug('[ClientApiHandler]: getEditUrl ' + file.provider); - - var request; - - switch (file.provider) { - case Constants.ThirdPartyConnectorType.OPEN_XCHANGE: - request = { - type: Constants.ThirdPartyConnectorsActionType.GET_EDIT_URL, - getEditUrl: { - externalAttachment: { - type: file.provider, - downloadLocation: file.uri, - previewLocation: file.uri, - attachment: { - fileId: file.fileId, - fileName: file.fileName, - size: file.size, - mimeType: file.mimeType - } - } - } - }; - break; - default: - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.getEditUrlResult.url); - } - }); - }; - - this.getStorageAuthenticationData = function (provider, endpointUrls, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getStorageAuthenticationData...'); - - if (provider !== Constants.ThirdPartyConnectorType.SYNCPLICITY) { - sendAsyncResp(cb, Constants.ThirdPartyError.UNSUPPORTED_PROVIDER); - return; - } - - var request = { - type: Constants.ThirdPartyConnectorsActionType.CONNECT_STORAGE, - connectStorage: { - type: Constants.ThirdPartyConnectorType.SYNCPLICITY, - endpointUrls: endpointUrls - } - }; - - sendRequest(Constants.ContentType.THIRDPARTYCONNECTORS, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.thirdpartyconnectors.connectStorageResult.storageAuthenticationData); - } - }); - - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public System related Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.getGlobalProperty = function (name, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getGlobalProperty...'); - var request = { - type: Constants.SystemActionType.GET_GLOBAL_PROPERTY, - getGlobalProperty: { - globalPropertyName: name - } - }; - sendRequest(Constants.ContentType.SYSTEM, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.system.getGlobalPropertyResult.property); - } - }); - }; - - this.getGlobalProperties = function (names, cb) { - cb = cb || NOP; - if (!(names instanceof Array)) { - names = [names]; - } - logger.debug('[ClientApiHandler]: getGlobalProperties...'); - var request = { - type: Constants.SystemActionType.GET_GLOBAL_PROPERTIES, - getGlobalProperties: { - globalPropertyNames: names - } - }; - sendRequest(Constants.ContentType.SYSTEM, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.system.getGlobalPropertiesResult.property); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Session Guest related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.validateSessionInvite = function (token, textLanguage, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: validateSessionInvite...'); - var request = { - type: Constants.GuestActionType.VALIDATE_SESSION_INVITE_TOKEN, - validateSessionInviteToken: { - token: {token: token}, - configurableTextsLanguage: textLanguage || undefined - } - }; - - var keysToOmitFromResponse = ['response.guest.validateSessionInviteToken.convTopic']; - - sendRequest(Constants.ContentType.GUEST, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.guest.validateSessionInviteToken); - } - }, null, keysToOmitFromResponse); - }; - - this.registerSessionGuest = function (guestData, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: registerSessionGuest...'); - var request = { - type: Constants.GuestActionType.REGISTER_SESSION_GUEST, - registerSessionGuest: { - token: {token: guestData.token}, - firstName: guestData.firstName, - lastName: guestData.lastName, - forTestCall: guestData.testCall, - locale: guestData.locale, - userId: _userId || undefined, - // The following fields are used by the access server and are not - // passed to the application node. - oldClientId: _clientId || undefined, - previousServer: _connectedBackend || undefined - } - }; - - var keysToOmitFromResponse = ['response.guest.registerSessionGuestResult.guestContext']; - - sendRequest(Constants.ContentType.GUEST, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - var result = rsp.guest.registerSessionGuestResult; - - // Save the Client API version that we are connected to - _clientApiVersion = Utils.convertVersionToNumber(result.apiVersion); - _connectedBackend = result.connectedBackend; - - // Save the connected clientId and userId - _clientId = result.clientId; - _userId = result.userId; - - cb(null, result); - } - }, null, keysToOmitFromResponse); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public User to User related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.sendUserToUserRequest = function (type, data, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: sendUserToUserRequest, type: ' + type + ', data: ', data); - - var request = { - type: Constants.ContentType.USER_TO_USER, - userToUser: { - type: type, - routing: { - destUserId: data.destUserId, - srcUserId: _userId, - destClientId: data.destClientId, - srcClientId: _clientId - }, - data: JSON.stringify(data.content) - } - }; - - var keysToOmitFromRequest = ['request.userToUser.userToUser.data']; - - sendRequest(Constants.ContentType.USER_TO_USER, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.userToUser); - } - }, keysToOmitFromRequest); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public Account related interfaces - /////////////////////////////////////////////////////////////////////////////// - this.getPackages = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getPackages...'); - var request = { - type: Constants.AccountActionType.GET_ASSIGNED_PACKAGES - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.account.getAssignedPackages.packages || []); - } - }, null, null, tenantContext); - }; - - /** - * The replacement of getUsers - * @param queryData - * @param cb - */ - this.getAccounts = function (queryData, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAccounts...'); - - var request = { - type: Constants.AccountActionType.GET_ACCOUNTS, - getAccounts: { - searchCriterias: queryData.searchCriterias, - sorting: queryData.sorting, - ordering: queryData.ordering, - searchPointer: queryData.searchPointer, - pageSize: queryData.pageSize, - excludedTags: queryData.excludedTags - } - }; - - var keysToOmitFromResponse = [ - 'response.account.getAccounts.accounts' - ]; - - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.account.getAccounts); - } - }, null, keysToOmitFromResponse, tenantContext); - }; - - /** - * Get account by user id - * @param user id - * @param cb - */ - this.getAccountByUserId = function (userId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getAccountByUserId...'); - - var request = { - type: Constants.AccountActionType.GET_ACCOUNT_BY_USER_ID, - getAccountByUserId: { - userId: userId - } - }; - - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.account.getAccountByUserId.account); - } - }); - }; - - this.migrateAccount = function (params, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: migrateAccount...'); - - var request = { - type: Constants.AccountActionType.MIGRATE_ACCOUNT, - migrateAccount: { - accountId: params.accountId, - accountTemplateId: params.accountTemplateId - } - }; - - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.account.migrateAccount); - } - }, null, null, tenantContext); - }; - - this.migrateMultipleUsers = function (params, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: migrateMultipleUsers...'); - - var request = { - type: Constants.AccountActionType.MIGRATE_MULTIPLE_USERS, - migrateMultipleUsers: { - accountIds: params.accountIds, - accountTemplateId: params.accountTemplateId - } - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp); - } - }); - }; - - this.suspendAccount = function (accountId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: suspendAccount...'); - var request = { - type: Constants.AccountActionType.SUSPEND_ACCOUNT, - suspendAccount: { - accountId: accountId - } - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.unsuspendAccount = function (accountId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unsuspendAccount...'); - var request = { - type: Constants.AccountActionType.UNSUSPEND_ACCOUNT, - unsuspendAccount: { - accountId: accountId - } - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.suspendUser = function (userId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: suspendUser...'); - var request = { - type: Constants.AdministrationActionType.SUSPEND_USER, - suspendUser: { - userId: userId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.unsuspendUser = function (userId, cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: unsuspendUser...'); - var request = { - type: Constants.AdministrationActionType.UNSUSPEND_USER, - unsuspendUser: { - userId: userId - } - }; - sendRequest(Constants.ContentType.ADMINISTRATION, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, null, null, tenantContext); - }; - - this.assignTelephonyConfiguration = function (accountId, configuration, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: assignTelephonyConfiguration...'); - - var request = { - type: Constants.AccountActionType.ASSIGN_TELEPHONY_CONFIGURATION, - assignTelephonyConfiguration: { - accountId: accountId, - configuration: { - phoneNumber: Utils.cleanPhoneNumber(configuration.phoneNumber), - callerId: Utils.cleanPhoneNumber(configuration.callerId), - reroutingPhoneNumber: Utils.cleanPhoneNumber(configuration.reroutingPhoneNumber), - onsSipAuthenticationHash: configuration.onsSipAuthenticationHash || '', - ondSipAuthenticationHash: configuration.ondSipAuthenticationHash || '', - associatedTelephonyUserID: configuration.associatedTelephonyUserID || '', - associatedTelephonyTrunkGroupId: configuration.associatedTelephonyTrunkGroupId || '', - associatedTelephonyUserType: configuration.associatedTelephonyUserType || undefined - } - } - }; - - var keysToOmitFromRequest = [ - 'request.account.assignTelephonyConfiguration.configuration.onsSipAuthenticationHash', - 'request.account.assignTelephonyConfiguration.configuration.ondSipAuthenticationHash' - ]; - - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }, keysToOmitFromRequest); - }; - - this.renewAssociatedTelephonyUser = function (associatedTelephonyUserId, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: renewAssociatedTelephonyUser...'); - - var request = { - type: Constants.AccountActionType.RENEW_ASSOCIATED_TELEPHONY_USER, - renewAssociatedTelephonyUser: { - currentAssociatedTelephonyUserId: associatedTelephonyUserId - } - }; - - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.getTechnicalAdminUserId = function (cb, tenantContext) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getTechnicalAdminUserId...'); - - var request = { - type: Constants.AccountActionType.GET_TECHNICAL_ADMIN_USER_ID - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.account.getTechnicalAdminUserIdResult.technicalAdminUserId); - } - }, null, null, tenantContext); - }; - - this.addAccountPermission = function (accountId, permissionName, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: addAccountPermission...'); - - if (!this.isPartnerAdminSupported()) { - logger.warn('[ClientApiHandler]: The addAccountPermission operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AccountActionType.ADD_ACCOUNT_PERMISSION, - addAccountPermission: { - accountId: accountId, - permissionName: permissionName - } - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - this.removeAccountPermission = function (accountId, permissionName, cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: removeAccountPermission...'); - - if (!this.isPartnerAdminSupported()) { - logger.warn('[ClientApiHandler]: The removeAccountPermission operation is not supported by the backend'); - sendAsyncResp(cb, Constants.ReturnCode.OPERATION_NOT_SUPPORTED); - return; - } - - var request = { - type: Constants.AccountActionType.REMOVE_ACCOUNT_PERMISSION, - removeAccountPermission: { - accountId: accountId, - permissionName: permissionName - } - }; - sendRequest(Constants.ContentType.ACCOUNT, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null); - } - }); - }; - - /////////////////////////////////////////////////////////////////////////////// - // Public UserData request - /////////////////////////////////////////////////////////////////////////////// - this.getUserData = function (cb) { - cb = cb || NOP; - logger.debug('[ClientApiHandler]: getUserData...'); - - var request = { - type: Constants.UserDataActionType.GET_USER_DATA - }; - sendRequest(Constants.ContentType.USER_DATA, request, function (err, rsp) { - if (isResponseValid(err, rsp, cb)) { - cb(null, rsp.userData.getUserData); - } - }); - }; - } - - ClientApiHandler.prototype.constructor = ClientApiHandler; - ClientApiHandler.prototype.name = 'ClientApiHandler'; - - // Exports - circuit.ClientApiHandler = ClientApiHandler; - - return circuit; -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - circuit.ClientApiHandlerSingleton = (function () { - var _clientApiHandler; - return { - getInstance: function () { - if (!_clientApiHandler) { - _clientApiHandler = new circuit.ClientApiHandler(); - } - return _clientApiHandler; - } - }; - })(); - - return circuit; -})(Circuit); - -// Define external globals for JSHint -/*global window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var ClientApiHandler = circuit.ClientApiHandlerSingleton; - var Constants = circuit.Constants; - var logger = circuit.logger; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////////////////// - var AtcMessage = Object.freeze({ - CSTA: 'CSTA', - PRIMARYCLIENT: 'PRIMARYCLIENT', - INFO: 'INFO', - SETTINGS: 'SETTINGS' - }); - - var AtcInfoMessage = Object.freeze({ - GET_GTC_VERSION_REQUEST: 'GET_GTC_VERSION_REQUEST', - GET_GTC_VERSION_RESPONSE: 'GET_GTC_VERSION_RESPONSE', - SET_ROUTE_TO_DESK: 'SET_ROUTE_TO_DESK', - ENABLE_PBX_CALL_LOG: 'ENABLE_PBX_CALL_LOG' - }); - - var ContactCardMessage = Object.freeze({ - CLICK_TO_CALL_REQUEST: 'CLICK_TO_CALL_REQUEST', - CLICK_TO_CALL_RESPONSE: 'CLICK_TO_CALL_RESPONSE', - MUTE_DEVICE_REQUEST: 'MUTE_DEVICE_REQUEST', - MUTE_DEVICE_RESPONSE: 'MUTE_DEVICE_RESPONSE', - JOIN_CONFERENCE_REQUEST: 'JOIN_CONFERENCE_REQUEST', - JOIN_CONFERENCE_RESPONSE: 'JOIN_CONFERENCE_RESPONSE', - CLICK_TO_ANSWER_REQUEST: 'CLICK_TO_ANSWER_REQUEST', - CLICK_TO_ANSWER_RESPONSE: 'CLICK_TO_ANSWER_RESPONSE' - }); - - var ContactCardResponseCode = Object.freeze({ - OK: 'OK', - BUSY: 'BUSY', - TELEPHONY_NOT_AVAILABLE: 'TELEPHONY_NOT_AVAILABLE', - USER_NOT_FOUND: 'USER_NOT_FOUND', - CONVERSATION_NOT_FOUND: 'CONVERSATION_NOT_FOUND', - CALL_DECLINED: 'CALL_DECLINED', - CALL_FAILED: 'CALL_FAILED', - CALL_NOT_FOUND: 'CALL_NOT_FOUND', - MUTE_DEVICE_FAILED: 'MUTE_DEVICE_FAILED', - JOIN_CONFERENCE_FAILED: 'JOIN_CONFERENCE_FAILED' - }); - - var MeetingPointMessage = Object.freeze({ - INVITE_REQUEST: 'INVITE_REQUEST', - INVITE_RESPONSE: 'INVITE_RESPONSE', - MUTE_REQUEST: 'MUTE_REQUEST', - MUTE_RESPONSE: 'MUTE_RESPONSE', - UNMUTE_REQUEST: 'UNMUTE_REQUEST', - UNMUTE_RESPONSE: 'UNMUTE_RESPONSE', - GET_MEETINGPOINT_INVITER_REQUEST: 'GET_MEETINGPOINT_INVITER_REQUEST', - GET_MEETINGPOINT_INVITER_RESPONSE: 'GET_MEETINGPOINT_INVITER_RESPONSE', - TOGGLE_VIDEO_REQUEST: 'TOGGLE_VIDEO_REQUEST', - TOGGLE_VIDEO_RESPONSE: 'TOGGLE_VIDEO_RESPONSE', - START_HD_VIDEO_REQUEST: 'START_HD_VIDEO_REQUEST', - START_HD_VIDEO_RESPONSE: 'START_HD_VIDEO_RESPONSE' - }); - - var MeetingPointResponseCode = Object.freeze({ - OK: 'OK', - FAILED: 'FAILED', - MISSING_REQUIRED_PARAMETER: 'MISSING_REQUIRED_PARAMETER', - NOT_FOUND: 'NOT_FOUND', - NOT_CMP_USER: 'NOT_CMP_USER', - NOT_SUPPORTED: 'NOT_SUPPORTED', - INVITE_FAILED: 'INVITE_FAILED', - MUTE_FAILED: 'MUTE_FAILED', - UNMUTE_FAILED: 'UNMUTE_FAILED', - INVALID_PIN: 'INVALID_PIN', - MEETINGPOINT_BUSY: 'MEETINGPOINT_BUSY', - TOGGLE_VIDEO_FAILED: 'START_VIDEO_FAILED' - }); - - var MobileBreakoutMessage = Object.freeze({ - DIAL: 'DIAL' - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // UserToUserHandler - /////////////////////////////////////////////////////////////////////////////////////// - - function UserToUserHandler(clientApiHandler) { - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var RESPONSE_TIMEOUT = 20000; - var _reqCallBacks = {}; - var _evtCallBacks = {}; - - var _clientApiHandler = clientApiHandler || ClientApiHandler.getInstance(); - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function addReqCallBack(requestId, cb) { - if (!cb) { - return; - } - - var responseTimer = window.setTimeout(function (reqId) { - logger.error('[UserToUserHandler]: Timeout waiting for response. RequestId:', reqId); - if (_reqCallBacks[reqId]) { - delete _reqCallBacks[reqId]; - cb(Constants.ReturnCode.REQUEST_TIMEOUT); - - logger.debug('[UserToUserHandler]: Remaining callbacks pending:', Object.keys(_reqCallBacks).length); - } - - }, RESPONSE_TIMEOUT, requestId); - - _reqCallBacks[requestId] = { - cb: cb, - timer: responseTimer - }; - } - - function handleInvalidMessage(cb) { - if (cb) { - window.setTimeout(function () { - cb(Constants.ReturnCode.INVALID_MESSAGE); - }, 0); - } - } - - function sendUserToUserRequest(type, data, cb) { - if (!data || !data.content || !data.content.type) { - logger.error('[UserToUserHandler]: Invalid UserToUser request data - ', data); - handleInvalidMessage(cb); - return; - } - - logger.debug('[UserToUserHandler]: Sending request ' + type + '.' + data.content.type); - data.content.requestId = data.content.requestId || Utils.createTransactionId(); - - if (_reqCallBacks[data.content.requestId]) { - // There is already a pending request with the same ID - logger.error('[UserToUserHandler]: UserToUser request has duplicate requestId'); - handleInvalidMessage(cb); - return; - } - - _clientApiHandler.sendUserToUserRequest(type, data, function (err) { - if (err) { - logger.error('[UserToUserHandler]: Failed to send request: ', err); - cb && cb(err); - return; - } - // For SDK UserToUser messages the application is supposed to handle requests/response. - if (type === Constants.UserRoutingMessageType.SDK) { - cb && cb(); - } else { - addReqCallBack(data.content.requestId, cb); - } - }); - } - - function handleUserToUserEvent(type, evt) { - try { - // Ignore own messages - if (evt.routing && evt.routing.srcClientId === _clientApiHandler.clientId) { - logger.info('[UserToUserHandler]: Ignore own UserToUser event'); - return; - } - - var data; - try { - data = JSON.parse(evt.data); - } catch (err) { - logger.error('[UserToUserHandler]: Event discarded (Cannot Parse)'); - return; - } - - if (data.requestId) { - var cbInfo = _reqCallBacks[data.requestId]; - if (cbInfo) { - // This is a response to a previous request - cbInfo.timer && window.clearTimeout(cbInfo.timer); - delete _reqCallBacks[data.requestId]; - // Invoke the registered callback (if any) - cbInfo.cb && cbInfo.cb(null, data); - logger.debug('[UserToUserHandler]: Remaining callbacks pending:', Object.keys(_reqCallBacks).length); - return; - } - } - // Check if there is a registered callback for this event - var evtType = type + '.' + data.type; - var cbList = _evtCallBacks[evtType]; - if (!cbList || cbList.length === 0) { - logger.info('[UserToUserHandler]: There is no registered event handler for ', evtType); - } else { - if (type === Constants.UserRoutingMessageType.SDK) { - data = data.message; - } - cbList.forEach(function (cbInfo) { - cbInfo.cb(data, evt.routing); - }); - } - } catch (e) { - logger.error('[UserToUserHandler]: Exception handling UserToUser event. ', e); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Client API Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - _clientApiHandler.on('UserToUser.ATC', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.ATC event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.ATC, evt); - }); - - _clientApiHandler.on('UserToUser.DESKTOP_APP', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.DESKTOP_APP event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.DESKTOP_APP, evt); - }); - - _clientApiHandler.on('UserToUser.CONTACT_CARD', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.CONTACT_CARD event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.CONTACT_CARD, evt); - }); - - _clientApiHandler.on('UserToUser.CMP', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.CMP event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.CMP, evt); - }); - - _clientApiHandler.on('UserToUser.MOBILE_BREAKOUT', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.MOBILE_BREAKOUT event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.MOBILE_BREAKOUT, evt); - }); - - _clientApiHandler.on('UserToUser.SDK', function (evt) { - logger.debug('[UserToUserHandler]: Received UserToUser.SDK event: ', evt.data); - handleUserToUserEvent(Constants.UserRoutingMessageType.SDK, evt); - }); - - /////////////////////////////////////////////////////////////////////////////// - // Public Interfaces - /////////////////////////////////////////////////////////////////////////////// - this.cancelReqCallback = function (reqId) { - var cbInfo = _reqCallBacks[reqId]; - if (cbInfo) { - // Clear the callback function, but keep the registration to handle the response - cbInfo.cb = null; - } - }; - - this.sendAtcRequest = function (data, cb) { - sendUserToUserRequest(Constants.UserRoutingMessageType.ATC, data, cb); - }; - - this.sendContactCardRequest = function (data, cb) { - sendUserToUserRequest(Constants.UserRoutingMessageType.CONTACT_CARD, data, cb); - }; - - this.sendDesktopAppRequest = function (data, cb) { - sendUserToUserRequest(Constants.UserRoutingMessageType.DESKTOP_APP, data, cb); - }; - - this.sendMeetingPointRequest = function (data, cb) { - sendUserToUserRequest(Constants.UserRoutingMessageType.CMP, data, cb); - }; - - this.sendMobileBreakoutEvent = function (data, cb) { - sendUserToUserRequest(Constants.UserRoutingMessageType.MOBILE_BREAKOUT, data, cb); - }; - - this.sendSdkRequest = function (data, cb) { - // The applications are not allowed to set the requestId for the UsertoUser messages - if (data && data.content) { - delete data.content.requestId; - } - sendUserToUserRequest(Constants.UserRoutingMessageType.SDK, data, cb); - }; - - this.on = function (msgType, cb) { - if (msgType && (typeof cb === 'function')) { - _evtCallBacks[msgType] = _evtCallBacks[msgType] || []; - // Make sure this is not a duplicate registration - var isDuplicate = _evtCallBacks[msgType].some(function (cbInfo) { - return cbInfo.cb === cb; - }); - if (!isDuplicate) { - _evtCallBacks[msgType].push({cb: cb}); - } - } - }; - - this.off = function (msgType, cb) { - if (!msgType && !cb) { - // Unregister all event handlers - _evtCallBacks = {}; - } else if (!cb) { - // Unregister the event handlers for the given message type - delete _evtCallBacks[msgType]; - } else if (msgType && (typeof cb === 'function')) { - // Unregister the given event handler - var cbList = _evtCallBacks[msgType]; - cbList && cbList.some(function (cbInfo, idx) { - if (cbInfo.cb === cb) { - cbList.splice(idx, 1); - return true; - } - }); - } - }; - } - - UserToUserHandler.prototype.constructor = UserToUserHandler; - UserToUserHandler.prototype.name = 'UserToUserHandler'; - - // Exports - circuit.UserToUserHandler = UserToUserHandler; - - circuit.Enums = circuit.Enums || {}; - circuit.Enums.AtcInfoMessage = AtcInfoMessage; - circuit.Enums.AtcMessage = AtcMessage; - circuit.Enums.ContactCardMessage = ContactCardMessage; - circuit.Enums.ContactCardResponseCode = ContactCardResponseCode; - circuit.Enums.MeetingPointMessage = MeetingPointMessage; - circuit.Enums.MeetingPointResponseCode = MeetingPointResponseCode; - circuit.Enums.MobileBreakoutMessage = MobileBreakoutMessage; - - return circuit; -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - circuit.UserToUserHandlerSingleton = (function () { - var _userToUserHandler; - return { - getInstance: function () { - if (!_userToUserHandler) { - _userToUserHandler = new circuit.UserToUserHandler(); - } - return _userToUserHandler; - } - }; - })(); - - return circuit; -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var logger = Circuit.logger; - - function CstaParser() { - - ///////////////////////////////////////////////////////////////////////////// - // Public interfaces - ///////////////////////////////////////////////////////////////////////////// - this.genCstaRequest = function (data) { - try { - switch (data.request) { - case 'AlternateCall': - return genAlternateRequest(data); - case 'ConferenceCall': - return genConferenceRequest(data); - case 'AnswerCall': - return genAnswerRequest(data); - case 'CallBackCallRelated': - return genCallBackCallRelated(data); - case 'CallBackNonCallRelated': - return genCallBackNonCallRelated(data); - case 'CancelCallBack': - return genCancelCallBack(data); - case 'ClearConnection': - return genClearConnectionRequest(data); - case 'ConsultationCall': - return genConsultationCall(data); - case 'DeflectCall': - return genDeflectRequest(data); - case 'GenerateDigits': - return genGenerateDigitsRequest(data); - case 'GetDoNotDisturb': - return genGetDoNotDisturb(data); - case 'GetForwarding': - return genGetForwarding(data); - case 'GetMessageWaitingIndicator': - return genGetMessageWaiting(data); - case 'HoldCall': - return genHoldRequest(data); - case 'ReconnectCall': - return genReconnectRequest(data); - case 'RetrieveCall': - return genRetrieveRequest(data); - case 'SetDoNotDisturb': - return genSetDoNotDisturb(data); - case 'SetForwarding': - return genSetForwarding(data); - case 'SingleStepTransferCall': - return genSSTRequest(data); - case 'SnapshotCall': - return genSnapshotCall(data); - case 'SnapshotDevice': - return genSnapshotDevice(data); - case 'TransferCall': - return genTransferCall(data); - case 'GenGetConfiguration': - return genGetConfiguration(); - case 'SetMicrophoneMute': - return genSetMicrophoneMute(data); - case 'GetMicrophoneMute': - return genGetMicrophoneMute(data); - case 'MakeCall': - return genMakeCallRequest(data); - case 'CallLogSnapShot': - return genCallLogSnapShot(data); - case 'AcceptCall': - return genAcceptCall(data); - case 'GetAgentState': - return genGetAgentState(data); - case 'SetAgentState': - return genSetAgentState(data); - case 'GroupPickupCall': - return genGroupPickupCall(data); - } - logger.error('CSTA: Unsupported CSTA request: ' + data.request); - } catch (e) { - logger.error(e); - } - return null; - }; - - this.parseEvent = function (event) { - try { - if (!event) { - logger.error('[CstaParser]: Invalid event'); - return null; - } - - var json = JSON.parse(event); - if (!json || json.parsererror) { - logger.error('[CstaParser]: Error parsing CSTA event: ', event); - if (json && json.parsererror.div) { - logger.error('[CstaParser]: ' + json.parsererror.div); - } - return null; - } - - var eventTag = Object.keys(json)[0]; - switch (eventTag) { - case 'CBE': - return parseCallBackEvent(json); - case 'CIE': - return parseCallInformationEvent(json); - case 'CoE': - return parseConferencedEvent(json); - case 'CCEt': - return parseConnectionClearedEvent(json); - case 'DE': - return parseDeliveredEvent(json); - case 'DGE': - return parseDigitsGeneratedEvent(json); - case 'DEt': - return parseDivertedEvent(json); - case 'DNDE': - return parseDoNotDisturbEvent(json); - case 'EEt': - return parseEstablishedEvent(json); - case 'FE': - return parseFailedEvent(json); - case 'FEt': - return parseForwardingEvent(json); - case 'HE': - return parseHeldEvent(json); - case 'MWE': - return parseMessageWaitingEvent(json); - case 'OE': - return parseOfferedEvent(json); - case 'QE': - return parseQueuedEvent(json); - case 'ReE': - return parseRetrievedEvent(json); - case 'SCFE': - return parseServiceCompletionFailureEvent(json); - case 'SIE': - return parseServiceInitiatedEvent(json); - case 'TE': - return parseTransferredEvent(json); - case 'CLE': - return parseCallLogEvent(json); - case 'BISE': - return parseBackInServiceEvent(json); - case 'OOSE': - return parseOutOfServiceEvent(json); - case 'ARE': - return parseAgentReadyEvent(); - case 'ANRE': - return parseAgentNotReadyEvent(); - case 'ABE': - return parseAgentBusyEvent(); - } - logger.info('[CstaParser]: Unsupported CSTA event: ', eventTag); - } catch (e) { - logger.error('[CstaParser]: Error parsing CSTA event: ', e); - } - return null; - }; - - this.parseResponse = function (request, rs) { - try { - if (!request || !rs) { - logger.error('[CstaParser]: Invalid response'); - return null; - } - - var json = JSON.parse(rs); - if (!json || json.parsererror) { - logger.error('[CstaParser]: Error parsing CSTA response: ', rs); - if (json && json.parsererror.div) { - logger.error('[CstaParser]: ' + json.parsererror.div); - } - return null; - } - - if (json.CSTA) { - json.CSTAErrorCode = json.CSTA; - } - - if (json.CSTAErrorCode) { - logger.debug('[CstaParser]: Received error response for: ', request); - return parseErrorCode(json); - } else if (json.ISEC) { - logger.debug('[CstaParser]: Received internal service error response for: ', request); - return parseInternalServiceErrorCode(json); - } - - switch (request) { - case 'AlternateCall': - return parseAlternateResponse(json); - case 'ConferenceCall': - return parseConferenceResponse(json); - case 'AnswerCall': - return parseAnswerResponse(json); - case 'CallBackCallRelated': - return parseCallBackResponse(json); - case 'CallBackNonCallRelated': - return parseCallBackNonCallReleatedResponse(json); - case 'CancelCallBack': - return parseCancelCallBackResponse(json); - case 'ClearConnection': - return parseClearConnectionResponse(json); - case 'ConsultationCall': - return parseConsultationCallResponse(json); - case 'DeflectCall': - return parseDeflectResponse(json); - case 'GenerateDigits': - return parseGenDigitsResponse(json); - case 'GetDoNotDisturb': - return parseGetDoNotDisturbResponse(json); - case 'GetForwarding': - return parseGetForwardingResponse(json); - case 'GetMessageWaitingIndicator': - return parseGetMessageWaitingResponse(json); - case 'HoldCall': - return parseHoldResponse(json); - case 'ReconnectCall': - return parseReconnectResponse(json); - case 'RetrieveCall': - return parseRetrieveResponse(json); - case 'SetDoNotDisturb': - return parseSetDoNotDisturbResponse(json); - case 'SetForwarding': - return parseSetForwardingResponse(json); - case 'SingleStepTransferCall': - return parseSSTResponse(json); - case 'SnapshotCall': - return parseSnapshotCallResponse(json); - case 'SnapshotDevice': - return parseSnapshotDeviceResponse(json); - case 'TransferCall': - return parseTransferCallResponse(json); - case 'SetMicrophoneMute': - return parseSetMicrophoneMuteResponse(json); - case 'GetMicrophoneMute': - return parseGetMicrophoneMuteResponse(json); - case 'MakeCall': - return parseMakeCallResponse(json); - case 'CallLogSnapShot': - return parseCallLogSnapShotResponse(json); - case 'AcceptCall': - return parseAcceptCallResponse(json); - case 'GetAgentState': - return parseGetAgentStateResponse(json); - case 'SetAgentState': - return parseSetAgentStateResponse(json); - case 'GroupPickupCall': - return parseGroupPickupCallResponse(json); - } - logger.error('CSTA: Unsupported CSTA response for request: ', request); - } catch (e) { - logger.error(e); - } - return null; - }; - - ///////////////////////////////////////////////////////////////////////////// - // Internal functions - ///////////////////////////////////////////////////////////////////////////// - function parseCallData(callData) { - return { - connectionId: callData.cIr, - localCallState: callData.lCS.cCSe && callData.lCS.cCSe.lCSe - }; - } - - function parseEndpointData(endpointData) { - return { - deviceOnCall: endpointData.dOC.dIr || '', - callIdentifier: endpointData.cI, - localConnectionState: endpointData.lCI, - servicesPermitted: parseServicesPermitted(endpointData) - - }; - } - - function parseErrorCode(rs) { - if (!rs.CSTAErrorCode) { - logger.error('[CstaParser]: The given response is not an error code: ', rs); - return null; - } - - var category = null; - - var shortTag = Object.keys(rs.CSTAErrorCode)[0]; - switch (shortTag) { - case 'oprt': - case 'operation': - category = 'Operation Error'; - break; - case 'scrt': - case 'security': - category = 'Security Error'; - break; - case 'sIy': - case 'stateIncompatibility': - category = 'State Incompatibility Error'; - break; - case 'sRAy': - case 'systemResourceAvailability': - category = 'System Resource Availability Error'; - break; - case 'sRA': - case 'subscribedResourceAvailability': - category = 'Subscribed Resource Availability Error'; - break; - case 'pM': - case 'performanceManagement': - category = 'Performance Management Error'; - break; - case 'pDa': - case 'privateDataInformation': - category = 'Private Data Information Error'; - break; - } - - if (!category) { - logger.error('[CstaParser]: Unrecognized Error Category'); - return { error: true, category: 'Unknown', value: 'Unknown' }; - } - - return { - error: true, - errorCategory: category, - errorValue: rs.CSTAErrorCode[shortTag] - }; - } - - function parseInternalServiceErrorCode(rs) { - if (!rs.ISEC) { - logger.error('[CstaParser]: The given response is not an internal service error code: ', rs); - return null; - } - - var category = null; - - var shortTag = Object.keys(rs.ISEC)[0]; - switch (shortTag) { - case 'code': - category = 'Code'; - break; - } - - if (!category) { - logger.error('[CstaParser]: Unrecognized Internal Service Error Category'); - return { error: true, category: 'Unknown', value: 'Unknown' }; - } - - return { - error: true, - errorCategory: category, - errorValue: rs.ISEC[shortTag] - }; - } - - function parseEpid(data) { - var epid = null; - if (data && data.extn && data.extn.pDa && data.extn.pDa.prvt) { - epid = data.extn.pDa.prvt.zEpid; - } - return epid; - } - - function normalizeServicesPermitted(sp) { - if (sp) { - Object.keys(sp).forEach(function (key) { - sp[key] = (sp[key] === '' || sp[key] === 'true'); - }); - } - } - - function parseServicesPermitted(data) { - if (!data) { - return {}; - } - var servicesPermitted = {}; - if (data.sP) { - servicesPermitted.rSP = data.sP; - normalizeServicesPermitted(servicesPermitted.rSP.cCSs); - } - if (data.extn && data.extn.pDa && data.extn.pDa.prvt && data.extn.pDa.prvt.zxSP !== undefined) { - servicesPermitted.eSP = data.extn.pDa.prvt.zxSP; - normalizeServicesPermitted(servicesPermitted.eSP); - } - return servicesPermitted; - } - - // Parse Responses - function parseAlternateResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Alternate Call response: ', rs); - return rs.ACRe; - } - - function parseAnswerResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Answer Call response: ', rs); - return rs.AnCR; - } - - function parseAcceptCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Accept Call response: ', rs); - return rs.ACR; - } - - function parseClearConnectionResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Clear Connection response: ', rs); - return rs.ClCR; - } - - function parseDeflectResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Deflect Call response: ', rs); - return rs.DCRe; - } - - function parseGenDigitsResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Generate Digits response: ', rs); - return rs.GDR; - } - - function parseGetDoNotDisturbResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Get Do Not Disturb response: ', rs); - var data = rs.GDNDe; - return { - doNotDisturbOn: data.dNDO === 'true' - }; - } - - function parseForwardListItemData(data) { - return { - forwardingType: data.fTe, - forwardStatus: data.fS === 'true', - forwardDN: data.fDNN - }; - } - - function parseGetForwardingResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Get Forwarding response: ', rs); - // OSV sends the GetForwardingResponse with long tags OSV-3432 - if (rs.GetForwardingResponse) { - return parseGetForwardingResponseLongTags(rs.GetForwardingResponse); - } - var data = rs.GFR; - var parsedResp = { - forwardList: [] - }; - if (data.fL.fLI.fS) { - parsedResp.forwardList.push(parseForwardListItemData(data.fL.fLI)); - } else { - // Multiple forwarding items - data.fL.fLI.forEach(function (data) { - parsedResp.forwardList.push(parseForwardListItemData(data)); - }); - } - if (data.extn && data.extn.pDa && data.extn.pDa.prvt) { - parsedResp.staticOndDN = data.extn.pDa.prvt.zsOD; - parsedResp.staticOndActive = data.extn.pDa.prvt.zsOA === 'true'; - parsedResp.voicemailActive = data.extn.pDa.prvt.zvMA === 'true'; - if (data.extn.pDa.prvt.zoP) { - parsedResp.overrideProfile = data.extn.pDa.prvt.zoP === 'true'; - } - if (data.extn.pDa.prvt.zvMRD) { - parsedResp.voicemailRingDuration = parseInt(data.extn.pDa.prvt.zvMRD, 10); - } - if (data.extn.pDa.prvt.zmRD) { - parsedResp.mainRingDuration = parseInt(data.extn.pDa.prvt.zmRD, 10); - } - if (data.extn.pDa.prvt.zcRD) { - parsedResp.clientRingDuration = parseInt(data.extn.pDa.prvt.zcRD, 10); - } - if (data.extn.pDa.prvt.zcpRD) { - parsedResp.cellRingDuration = parseInt(data.extn.pDa.prvt.zcpRD, 10); - } - if (data.extn.pDa.prvt.hasOwnProperty('zcPN')) { - parsedResp.alternativeNumber = data.extn.pDa.prvt.zcPN; - } - if (data.extn.pDa.prvt.hasOwnProperty('zrTCN')) { - parsedResp.routeToCell = data.extn.pDa.prvt.zrTCN === 'true'; - } - } - return parsedResp; - } - - function parseGetForwardingResponseLongTags(data) { - var parsedResp = { - forwardList: [] - }; - if (data.forwardingList.forwardListItem.forwardStatus) { - data.forwardingList.forwardListItem.forwardStatus = data.forwardingList.forwardListItem.forwardStatus === 'true'; - parsedResp.forwardList.push(data.forwardingList.forwardListItem); - } else { - // Multiple forwarding items - data.forwardingList.forwardListItem.forEach(function (data) { - data.forwardStatus = data.forwardStatus === 'true'; - parsedResp.forwardList.push(data); - }); - } - if (data.extensions && data.extensions.privateData && data.extensions.privateData.private) { - parsedResp.staticOndDN = data.extensions.privateData.private.staticOndDN; - parsedResp.staticOndActive = data.extensions.privateData.private.staticOndActive === 'true'; - } - return parsedResp; - } - - function parseGetMessageWaitingResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Get Message Waiting response: ', rs); - var data = rs.GMWIe; - return { - messageWaitingOn: data.mWO === 'true' - }; - } - - function parseHoldResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Hold Call response: ', rs); - return rs.HCR; - } - - function parseReconnectResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Reconnect Call response: ', rs); - return rs.RCR; - } - - function parseRetrieveResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Retrieve Call response: ', rs); - return rs.RCRe; - } - - function parseSetDoNotDisturbResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Set Do Not Disturb response: ', rs); - return rs.SDNDe; - } - - function parseSetForwardingResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Set Forwarding response: ', rs); - return rs.hasOwnProperty('SFR') ? rs.SFR : rs.SetForwardingResponse; - } - - function parseConsultationCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Consultation Call response: ', rs); - return rs.CnCR; - } - - function parseCallBackResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Call Back call related response: ', rs); - return rs.CBR; - } - - function parseCallBackNonCallReleatedResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Call Back non call related response: ', rs); - return rs.CBNCe; - } - - function parseCancelCallBackResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Cancel Call Back response: ', rs); - return rs.CCBR; - } - - function parseSnapshotCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Snapshot Call response: ', rs); - var data = rs.SCR; - var parsedResp = { - endpoints: [], - callingDevice: (data.caD && data.caD.dIR) || '', - calledDevice: (data.cDe && data.cDe.dIR) || '', - servicesPermitted: {} - }; - if (data.cRID.sDa.sCRIo) { - if (data.cRID.sDa.sCRIo.dOC) { - // There is only one endpoint in the call - parsedResp.endpoints.push(parseEndpointData(data.cRID.sDa.sCRIo)); - - } else { - // There are multiple endpoints - data.cRID.sDa.sCRIo.forEach(function (endpointData) { - parsedResp.endpoints.push(parseEndpointData(endpointData)); - }); - } - } - parsedResp.servicesPermitted = parseServicesPermitted(data); - parsedResp.epid = parseEpid(data); - return parsedResp; - } - - function parseSnapshotDeviceResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Snapshot Device response: ', rs); - var data = rs.SnDR; - var parsedResp = { - activeCalls: [] - }; - if (data.cRID.sDa.sDRI) { - if (data.cRID.sDa.sDRI.cIr) { - // There is only one call - parsedResp.activeCalls.push(parseCallData(data.cRID.sDa.sDRI)); - } else { - // There are multiple calls - data.cRID.sDa.sDRI.forEach(function (callData) { - parsedResp.activeCalls.push(parseCallData(callData)); - }); - } - } - return parsedResp; - } - - function parseSSTResponse(rs) { - logger.debug('[CstaParser]: Received CSTA SST Call response: ', rs); - var data = rs.SSTCe; - var parsedResp = {transferredCall: data.trC}; - if (data.extn && data.extn.pDa && data.extn.pDa.prvt) { - parsedResp.seamlessHandover = data.extn.pDa.prvt.zseHo === 'true'; - } - return parsedResp; - } - - function parseConferenceResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Conference Call response: ', rs); - var data = rs.CoCR; - return {conferenceCall: data.coC}; - } - - function parseTransferCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA TransferCall response: ', rs); - var data = rs.TCR; - return {transferredCall: data.trC}; - } - - function parseSetMicrophoneMuteResponse(rs) { - logger.debug('[CstaParser]: Received CSTA SetMicrophoneMute response', rs); - var data = rs.SMMR; - return {mute: data.mML.mMI.mMO === 'true'}; - } - - function parseGetMicrophoneMuteResponse(rs) { - logger.debug('[CstaParser]: Received CSTA GetMicrophoneMute response', rs); - var data = rs.GMMR; - return {mute: data.mML.mMI.mMO === 'true'}; - } - - function parseMakeCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA MakeCall response', rs); - return rs.MCR; - } - - function parseCallLogSnapShotResponse(rs) { - logger.debug('[CstaParser]: Received CSTA Call Log Snap Shot response: ', rs); - return rs.CLSR; - } - - function parseGetAgentStateResponse(rs) { - logger.debug('[CstaParser]: Received CSTA GetAgentState response', rs); - var data = rs.GASR; - return { - loggedOnState: (data.aSL && data.aSL.aSE && data.aSL.aSE.lOS === 'true'), - agentState: (data.aSL && data.aSL.aSE && data.aSL.aSE.aIo && data.aSL.aSE.aIo.aII.aS === 'agentReady' ? 'ready' : 'notReady') - }; - } - - function parseSetAgentStateResponse(rs) { - logger.debug('[CstaParser]: Received CSTA SetAgentState response', rs); - return rs.SASR; - } - - function parseGroupPickupCallResponse(rs) { - logger.debug('[CstaParser]: Received CSTA GroupPickupCall response', rs); - return rs.GPCRe; - } - - // Parse Events - function parseCallInformationEvent(event) { - var data = event.CIE; - var parsedEvent = { - category: 'CallAssociatedFeature', - name: 'CallInformationEvent', - connection: data.cnncn, - device: data.dvc.dIr || '', - callingDevice: (data.caD && data.caD.dIr) || '', - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseConferencedEvent(event) { - var data = event.CoE; - var parsedEvent = { - category: 'CallControl', - name: 'ConferencedEvent', - primaryOldCall: data.pOC, - secondaryOldCall: data.sOC, - conferencingDevice: data.coD.dIr || '', - addedParty: data.aPy.dIr || '', - conferenceConnections: [], - localConnectionInfo: data.lCI, - cause: data.cs, - servicesPermitted: {} - }; - data.cnC.cLI.forEach(function (item) { - var connListItem = { - connection: item.nCn, - endpoint: item.endp && (item.endp.dID || item.endp.endp) - }; - parsedEvent.conferenceConnections.push(connListItem); - }); - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseConnectionClearedEvent(event) { - var data = event.CCEt; - var parsedEvent = { - category: 'CallControl', - name: 'ConnectionClearedEvent', - droppedConnection: data.drC, - releasingDevice: data.rD.dIr || '', - localConnectionInfo: data.lCI, - cause: data.cs, - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseDeliveredEvent(event) { - var data = event.DE; - var parsedEvent = { - category: 'CallControl', - name: 'DeliveredEvent', - connection: data.cnncn, - alertingDevice: data.aDe.dIr || '', - callingDevice: data.caD.dIr || '', - calledDevice: data.cDe.dIr || '', - localConnectionInfo: data.lCI, - cause: data.cs - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseDigitsGeneratedEvent(event) { - var data = event.DGE; - return { - category: 'CallAssociatedFeature', - name: 'DigitsGeneratedEvent', - connection: data.cnncn, - digitGeneratedList: data.dGL - }; - } - - function parseDivertedEvent(event) { - var data = event.DEt; - var parsedEvent = { - category: 'CallControl', - name: 'DivertedEvent', - connection: data.cnncn, - divertingDevice: data.dvD.dIr || '', - newDestination: data.nD.dIr || '', - callingDevice: data.caD.dIr || '', - lastRedirectionDevice: data.lRD && data.lRD.nDd || '', - localConnectionInfo: data.lCI, - cause: data.cs - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseDoNotDisturbEvent(event) { - var data = event.DNDE; - return { - category: 'LogicalDeviceFeature', - name: 'DoNotDisturbEvent', - device: data.dvc.dIr || '', - doNotDisturbOn: data.dNDO === 'true' - }; - } - - function parseEstablishedEvent(event) { - var data = event.EEt; - var parsedEvent = { - category: 'CallControl', - name: 'EstablishedEvent', - establishedConnection: data.eCn, - answeringDevice: data.anD.dIr || '', - callingDevice: data.caD.dIr || '', - calledDevice: data.cDe.dIr || '', - lastRedirectionDevice: data.lRD ? data.lRD.nDd || '' : '', - localConnectionInfo: data.lCI, - cause: data.cs, - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseFailedEvent(event) { - var data = event.FE; - var parsedEvent = { - category: 'CallControl', - name: 'FailedEvent', - failedConnection: data.fC, - failingDevice: data.fD.dIr || '', - callingDevice: data.caD.dIr || '', - localConnectionInfo: data.lCI, - calledDevice: data.cDe.dIr, - cause: data.cs, - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseForwardingEvent(event) { - var data = event.FEt; - var parsedEvent = { - category: 'LogicalDeviceFeature', - name: 'ForwardingEvent', - device: data.dvc.dIr || '', - forwardingType: data.fTe, - forwardStatus: data.fS === 'true', - forwardTo: data.fTo - }; - if (data.extn && data.extn.pDa && data.extn.pDa.prvt) { - if (data.extn.pDa.prvt.zsOD) { - parsedEvent.staticOndDN = data.extn.pDa.prvt.zsOD; - parsedEvent.staticOndActive = data.extn.pDa.prvt.zsOA === 'true'; - } - if (data.extn.pDa.prvt.zvMA) { - parsedEvent.voicemailActive = data.extn.pDa.prvt.zvMA === 'true'; - } - if (data.extn.pDa.prvt.zoP) { - parsedEvent.overrideProfile = data.extn.pDa.prvt.zoP === 'true'; - } - if (data.extn.pDa.prvt.zvMRD) { - parsedEvent.voicemailRingDuration = parseInt(data.extn.pDa.prvt.zvMRD, 10); - } - if (data.extn.pDa.prvt.zmRD) { - parsedEvent.mainRingDuration = parseInt(data.extn.pDa.prvt.zmRD, 10); - } - if (data.extn.pDa.prvt.zcRD) { - parsedEvent.clientRingDuration = parseInt(data.extn.pDa.prvt.zcRD, 10); - } - if (data.extn.pDa.prvt.zcpRD) { - parsedEvent.cellRingDuration = parseInt(data.extn.pDa.prvt.zcpRD, 10); - } - if (data.extn.pDa.prvt.hasOwnProperty('zcPN')) { - parsedEvent.alternativeNumber = data.extn.pDa.prvt.zcPN; - } - if (data.extn.pDa.prvt.hasOwnProperty('zrTCN')) { - parsedEvent.routeToCell = data.extn.pDa.prvt.zrTCN === 'true'; - } - } - return parsedEvent; - } - - function parseHeldEvent(event) { - var data = event.HE; - var parsedEvent = { - category: 'CallControl', - name: 'HeldEvent', - heldConnection: data.hCn, - holdingDevice: data.hD.dIr || '', - localConnectionInfo: data.lCI, - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseMessageWaitingEvent(event) { - var data = event.MWE; - return { - category: 'PhysicalDeviceFeature', - name: 'MessageWaitingEvent', - device: data.tD.dIr || '', - messageWaitingOn: data.mWO === 'true' - }; - } - - function parseOfferedEvent(event) { - var data = event.OE; - var parsedEvent = { - category: 'CallControl', - name: 'OfferedEvent', - offeredConnection: data.oCn, - offeredDevice: data.oDe.dIr || '', - callingDevice: data.caD.dIr || '', - calledDevice: data.cDe.dIr || '', - localConnectionInfo: data.lCI, - lastRedirectionDevice: data.lRD ? data.lRD.nDd || '' : '', - cause: data.cs - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseQueuedEvent(event) { - var data = event.QE; - var parsedEvent = { - category: 'CallControl', - name: 'QueuedEvent', - queuedConnection: data.qC, - queue: data.q.dIr || '', - callingDevice: data.caD.dIr || '', - localConnectionInfo: data.lCI, - cause: data.cs - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseRetrievedEvent(event) { - var data = event.ReE; - var parsedEvent = { - category: 'CallControl', - name: 'RetrievedEvent', - retrievedConnection: data.rCn, - retrievingDevice: data.reD.dIr || '', - localConnectionInfo: data.lCI, - servicesPermitted: {} - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseServiceCompletionFailureEvent(event) { - var data = event.SCFE; - var parsedEvent = { - category: 'CallAssociatedFeature', - name: 'ServiceCompletionFailureEvent', - primaryCall: { - connectionID: data.prC.cIDD, - localConnectionState: data.prC.lCSe - }, - cause: data.cs - }; - if (data.sC) { - parsedEvent.secondaryCall = { - connectionID: data.sC.cIDD, - localConnectionState: data.sC.lCSe - }; - } - return parsedEvent; - } - - function parseServiceInitiatedEvent(event) { - var data = event.SIE; - var parsedEvent = { - category: 'CallControl', - name: 'ServiceInitiatedEvent', - initiatedConnection: data.iCn, - initiatingDevice: data.iD.dIr || '', - cause: data.cs - }; - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseCallBackEvent(event) { - var data = event.CBE; - return { - category: 'LogicalDeviceFeature', - name: 'CallBackEvent', - originatingDevice: data.orD.dIr, - targetDevice: data.tD.dIr, - callBackSetCanceled: data.cBSC - }; - } - - function parseTransferredEvent(event) { - var data = event.TE; - var parsedEvent = { - category: 'CallControl', - name: 'TransferredEvent', - primaryOldCall: data.pOC, - secondaryOldCall: data.sOC, - transferringDevice: data.tDe.dIr || '', - transferredToDevice: data.tTDe.dIr || '', - transferredConnections: [], - localConnectionInfo: data.lCI, - cause: data.cs, - servicesPermitted: {} - }; - data.tCs.cLI.forEach(function (item) { - var connListItem = { - connection: item.nCn, - endpoint: item.endp && (item.endp.dID || item.endp.endp) - }; - parsedEvent.transferredConnections.push(connListItem); - }); - parsedEvent.servicesPermitted = parseServicesPermitted(data); - parsedEvent.epid = parseEpid(data); - return parsedEvent; - } - - function parseCallLogEntryData(data) { - return { - recordId: data.rID, - callID: data.cID, - recordType: data.rTp, - createdTime: data.cTm, - result: data.res, - callDuration: data.cDr, - callingDevice: data.clDv || '', - calledDevice: data.cldDv || '', - answeringDevice: data.aDv || '' - }; - } - - function parseCallLogEvent(event) { - var data = event.CLE; - var parsedEvent = { - category: 'LogicalDeviceFeature', - name: 'CallLogEvent', - callLogList: [] - }; - if (data.cLL && data.cLL.cLE) { - if (data.cLL.cLE.rID) { - //Only one call log entry in the callLog event - parsedEvent.callLogList.push(parseCallLogEntryData(data.cLL.cLE)); - } else { - //There are Multiple entries in the CallLog Event - data.cLL.cLE.forEach(function (entry) { - parsedEvent.callLogList.push(parseCallLogEntryData(entry)); - }); - } - } - return parsedEvent; - } - - function parseBackInServiceEvent(event) { - var data = event.BISE; - return { - category: 'DeviceMaintenance', - name: 'BackInServiceEvent', - backInService: true, - device: data.dvc - }; - } - - function parseOutOfServiceEvent(event) { - var data = event.OOSE; - return { - category: 'DeviceMaintenance', - name: 'OutOfServiceEvent', - outOfService: true, - device: data.dvc - }; - } - - function parseAgentReadyEvent() { - return { - category: 'LogicalDeviceFeature', - name: 'AgentReadyEvent', - agentReady: true - }; - } - - function parseAgentNotReadyEvent() { - return { - category: 'LogicalDeviceFeature', - name: 'AgentNotReadyEvent', - agentReady: false - }; - } - - function parseAgentBusyEvent() { - return { - category: 'LogicalDeviceFeature', - name: 'AgentBusyEvent', - agentReady: false - }; - } - - // Generate Requests - function genAlternateRequest(data) { - return { - 'ACl': { - 'hC': { - 'cID': data.heldCall.cID, - 'dID': data.heldCall.dID - }, - 'atC': { - 'cID': data.activeCall.cID, - 'dID': data.activeCall.dID - } - } - }; - } - - function genConferenceRequest(data) { - return { - 'CoC': { - 'hC': { - 'cID': data.heldCall.cID, - 'dID': data.heldCall.dID - }, - 'atC': { - 'cID': data.activeCall.cID, - 'dID': data.activeCall.dID - } - } - }; - } - - function genAnswerRequest(data) { - return { - 'AnC': { - 'cTBAd': { - 'cID': data.callToBeAnswered.cID, - 'dID': data.callToBeAnswered.dID - } - } - }; - } - - function genAcceptCall(data) { - return { - 'AC': { - 'cTBA': { - 'cID': data.callToBeAccepted.cID, - 'dID': data.callToBeAccepted.dID - } - } - }; - } - - function genGroupPickupCall(data) { - return { - 'GPC': { - 'nD': data.newDestination - } - }; - } - - function genMakeCallRequest(data) { - return { - 'MC': { - 'caD': data.callingDevice, - 'cDN': data.calledDirectoryNumber, - 'aOe': data.autoOriginate - } - }; - } - - function genClearConnectionRequest(data) { - return { - 'CCn': { - 'coTBC': { - 'cID': data.connectionToBeCleared.cID, - 'dID': data.connectionToBeCleared.dID - }, - 'rsn': data.reason - } - }; - } - - function genDeflectRequest(data) { - var shortTagJson = { - 'cTBD': { - 'cID': data.callToBeDiverted.cID, - 'dID': data.callToBeDiverted.dID - }, - 'nD': data.newDestination - }; - if (data.autoAnswer) { - shortTagJson.extn = { - 'pDa': { - 'prvt': { - 'zdT': 'Auto-Answer' - } - } - }; - } - return { - 'DCl': shortTagJson - }; - } - - function genGenerateDigitsRequest(data) { - return { - 'GD': { - 'cTSD': { - 'cID': data.connectionToSendDigits.cID, - 'dID': data.connectionToSendDigits.dID - }, - 'cTS': data.charactersToSend - } - }; - } - - function genGetDoNotDisturb(data) { - return { - 'GDND': { - 'dvc': data.device - } - }; - } - - function genGetForwarding(data) { - return { - 'GF': { - 'dvc': data.device - } - }; - } - - function genGetMessageWaiting(data) { - return { - 'GMWI': { - 'dvc': data.device - } - }; - } - - function genHoldRequest(data) { - return { - 'HC': { - 'cTBH': { - 'cID': data.callToBeHeld.cID, - 'dID': data.callToBeHeld.dID - } - } - }; - } - - function genReconnectRequest(data) { - return { - 'RC': { - 'hC': { - 'cID': data.heldCall.cID, - 'dID': data.heldCall.dID - }, - 'atC': { - 'cID': data.activeCall.cID, - 'dID': data.activeCall.dID - } - } - }; - } - - function genRetrieveRequest(data) { - return { - 'RCl': { - 'cTBRd': { - 'cID': data.callToBeRetrieved.cID, - 'dID': data.callToBeRetrieved.dID - } - } - }; - } - - function genSetDoNotDisturb(data) { - return { - 'SDND': { - 'dvc': data.device, - 'dNDO': data.doNotDisturbOn - } - }; - } - - function genSetForwarding(data) { - var shortTagJson = { - 'SF': { - 'dvc': data.device, - 'aF': data.activateForward, - 'fDNN': data.forwardDN, - 'fTe': data.forwardingType, - 'riC': data.ringCount - } - }; - var prvt = {}; - if (data.staticOndActive !== undefined) { - prvt = { - zsOA: data.staticOndActive === true ? 'true' : 'false', - zsOD: data.staticOndDN || '' - }; - } else if (data.voicemailActive !== undefined || data.voicemailRingDuration !== undefined) { - prvt = { - zvMA: data.voicemailActive === true ? 'true' : data.voicemailActive === false ? 'false' : undefined, - zvMRD: data.voicemailRingDuration ? data.voicemailRingDuration.toString() : undefined - }; - } else { - if (data.mainRingDuration !== undefined) { - prvt.zmRD = data.mainRingDuration.toString() || ''; - } - if (data.clientRingDuration !== undefined) { - prvt.zcRD = data.clientRingDuration.toString() || ''; - } - if (data.cellRingDuration !== undefined) { - prvt.zcpRD = data.cellRingDuration.toString() || ''; - } - if (data.alternativeNumber !== undefined) { - prvt.zcPN = data.alternativeNumber; - } - if (data.routeToCell !== undefined) { - prvt.zrTCN = data.routeToCell ? 'true' : 'false'; - } - } - if (Object.keys(prvt).length > 0) { - shortTagJson.SF.extn = { - 'pDa': { - 'prvt': prvt - } - }; - } - return shortTagJson; - } - - function genSetMicrophoneMute(data) { - return { - 'SMM': { - 'dvc': data.device, - 'mMO': data.mute - } - }; - } - - function genGetMicrophoneMute(data) { - return { - 'GMM': { - 'dvc': data.device - } - }; - } - - function genSnapshotCall(data) { - return { - 'SC': { - 'sO': { - 'cID': data.snapshotObject.cID, - 'dID': data.snapshotObject.dID - } - } - }; - } - - function genSnapshotDevice(data) { - var shortTagJson = { - 'sO': data.snapshotObject - }; - if (data.osmo) { - shortTagJson.extn = { - 'pDa': { - 'prvt': { - 'zxsm': data.osmo - } - } - }; - } - return { - 'SDe': shortTagJson - }; - } - - function genSSTRequest(data) { - var shortTagJson = { - 'atC': { - 'cID': data.activeCall.cID, - 'dID': data.activeCall.dID - }, - 'tTo': data.transferredTo - }; - if (data.autoAnswer || data.seamlessHandover) { - var prvt = {}; - if (data.autoAnswer) { - prvt.ztTgt = 'Auto-Answer'; - } - if (data.seamlessHandover) { - prvt.zseHo = 'true'; - } - shortTagJson.extn = { - 'pDa': { - 'prvt': prvt - } - }; - } - return { - 'SSTC': shortTagJson - }; - } - - function genConsultationCall(data) { - return { - 'CnC': { - 'cnD': data.consultedDevice, - 'eCl': { - 'cID': data.existingCall.cID, - 'dID': data.existingCall.dID - } - } - }; - } - - function genCallBackCallRelated(data) { - return { - 'CB': { - 'cC': { - 'cID': data.callBackConnection.cID, - 'dID': data.callBackConnection.dID - } - } - }; - } - - function genCallBackNonCallRelated(data) { - return { - 'CBNC': { - 'orD': data.orD, - 'tD': data.tD - } - }; - } - - function genCancelCallBack(data) { - return { - 'CCB': { - 'orD': data.orD, - 'tD': data.tD - } - }; - } - - function genTransferCall(data) { - return { - 'TC': { - 'hC': { - 'cID': data.heldCall.cID, - 'dID': data.heldCall.dID - }, - 'atC': { - 'cID': data.activeCall.cID, - 'dID': data.activeCall.dID - } - } - }; - } - - function genGetConfiguration() { - return { - 'zGCD': { - 'zcCA': 'Internet', - 'zdMO': 'Chrome', - 'zdOS': 'WebRTC' - } - }; - } - - function genCallLogSnapShot(data) { - return { - 'CLS': { - 'sub': data.device - } - }; - } - - function genGetAgentState(data) { - return { - 'GAS': { - 'dvc': data.device - } - }; - } - - function genSetAgentState(data) { - return { - 'SAS': { - 'dvc': data.device, - 'rAS': data.state - } - }; - } - } - - // Exports - circuit.CstaParser = CstaParser; - - return circuit; -})(Circuit); - -// Define external globals for JSHint -/*global CustomEvent, document, setTimeout, window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var BaseEventTarget = circuit.BaseEventTarget; - var ChromeExtension = circuit.ChromeExtension; - var logger = circuit.logger; - var Utils = circuit.Utils; - - function ExtensionConnHandler() { - // Call the base constructor - ExtensionConnHandler.parent.constructor.call(this, logger); - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var RESPONSE_TIMEOUT = 5000; - var EXCHANGE_RESPONSE_TIMEOUT = 65000; // Response wait time for requests to Exchange connector - var EXCHANGE_CONNECT_TIMEOUT = 180000; // Response wait time for connect request to Exchange connector - var HEADSET_APP_LAUNCH_TIMEOUT = 180000; // Response wait time for launch Circuit Headset App - - var _that = this; - var _reqCallbacks = {}; - var _reqId = 0; - var _extInitialized = false; - - // Variables used by electron in order to communicate directly with the Exchange connector code - var _bgExchangeResponder; - var _bgExchangeConnectorHandler; - var BgExchangeConnectorHandler = window.BgExchangeConnectorHandler; - - /////////////////////////////////////////////////////////////////////////// - // Electron initializations - /////////////////////////////////////////////////////////////////////////// - if (circuit.isElectron) { - if (BgExchangeConnectorHandler) { - // For DA call BgExchangeConnectorHandler directly since both are - // running in the main renderer - _bgExchangeConnectorHandler = BgExchangeConnectorHandler.getInstance(); - - var handleBgMessage = function (msg) { - if (msg.data && msg.data.keysToOmitFromLogging) { - msg.keysToOmitFromLogging = msg.data.keysToOmitFromLogging.map(function (key) { - return 'data.' + key; - }); - delete msg.data.keysToOmitFromLogging; - } - onMessage(msg); - }; - - // For Electron define the responder object and chrome.storage.local proxy. - // The responder provides the same interface as the "Tab" object in the Chrome extension. - _bgExchangeResponder = Object.create(logger, { - sendResponse: { - value: function (reqId, data, suppressLog) { - var msg = { - type: ChromeExtension.BgMsgType.RESPONSE, - reqId: reqId, - suppressLog: !!suppressLog, - data: data - }; - handleBgMessage(msg); - } - }, - sendInternalEvent: { - value: function (data) { - logger.error('[ExtensionConnHandler]: Unexpected internal event: ', data); - } - }, - sendExchangeConnEvent: { - value: function (data, suppressLog) { - var msg = { - type: ChromeExtension.BgMsgType.EVENT, - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - suppressLog: !!suppressLog, - data: data - }; - handleBgMessage(msg); - } - } - }); - - // Local storage proxy for bgExchangeConnectorHandler - var EXCHANGE_KEY = 'exchange'; - window.chrome = { - storage: { - local: { - set: function (obj, cb) { - window.localStorage.setItem(EXCHANGE_KEY, JSON.stringify(obj, function (key, value) { - // Object has circular structure, we should remove it. - var val = value; - if (typeof key === 'string' && key.charAt(0) === '$') { - val = undefined; - } - return val; - })); - cb && cb(); - }, - get: function (keys, cb) { - cb && cb(JSON.parse(window.localStorage.getItem(EXCHANGE_KEY))); - }, - clear: function (cb) { - window.localStorage.removeItem(EXCHANGE_KEY); - cb && cb(); - } - } - }, - runtime: {}, - // Window creation is used for permission request. We don't need it. - windows: { - create: function () {} - }, - // Providing permission for any host as electron can send requests to any. - permissions: { - contains: function (perm, cb) { - cb(true); - } - } - }; - } - - setTimeout(function () { - logger.info('[ExtensionConnHandler]: Raise extensionInitialized'); - _extInitialized = true; - var evt = _that.createEvent('extensionInitialized'); - evt.data = {version: 'electron'}; - _that.dispatch(evt); - }, 1000); - } - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function addReqCallback(cb, cbTimeout) { - cb = cb || function () {}; - - var reqId = ++_reqId; - var responseTimer = window.setTimeout(function () { - if (_reqCallbacks[reqId]) { - logger.error('[ExtensionConnHandler]: Timeout waiting for response. reqId:', reqId); - delete _reqCallbacks[reqId]; - cb(ChromeExtension.ResponseError.TIMEOUT); - } - }, cbTimeout || RESPONSE_TIMEOUT); - - _reqCallbacks[reqId] = {cb: cb, timer: responseTimer}; - - return reqId; - } - - function unregisterReqCallback(reqId) { - if (!reqId || !_reqCallbacks[reqId]) { - return false; - } - delete _reqCallbacks[reqId]; - return true; - } - - function handleExtMessage(msg) { - var newEvt; - // Internal events are supposed to be handled by the ExtensionConnHandler instance. - // All other events are published to the interested applications - if (msg.target === ChromeExtension.BgTarget.INTERNAL) { - switch (msg.data.type) { - case ChromeExtension.BgInternalMsgType.INIT_MSG: - logger.info('[ExtensionConnHandler]: Received INIT_MSG. Send an INIT_MSG_ACK to the extension.'); - _extInitialized = true; - sendMessage({ - target: ChromeExtension.BgTarget.INTERNAL, - data: { - type: ChromeExtension.BgInternalMsgType.INIT_MSG_ACK - } - }); - - logger.info('[ExtensionConnHandler]: Dispatching event: extensionInitialized'); - newEvt = _that.createEvent('extensionInitialized'); - newEvt.data = msg.data; - _that.dispatch(newEvt); - break; - case ChromeExtension.BgInternalMsgType.EXTENSION_DISCONNECTED: - logger.warning('[ExtensionConnHandler]: Received EXTENSION_DISCONNECTED from ansible-content'); - onExtensionUnregistered(); - break; - default: - logger.warning('[ExtensionConnHandler]: Received unknown INTERNAL msg type from ansible-content:', msg.data.type); - break; - } - } else if (msg.target === ChromeExtension.BgTarget.HEADSET_APP) { - switch (msg.data.type) { - case ChromeExtension.BgHeadsetAppMsgType.HEADSET_APP_LAUNCHED: - logger.info('[ExtensionConnHandler]: Dispatching event: headsetAppLaunched'); - newEvt = _that.createEvent('headsetAppLaunched'); - newEvt.data = msg.data; - _that.dispatch(newEvt); - break; - case ChromeExtension.BgHeadsetAppMsgType.HEADSET_APP_UNINSTALLED: - logger.info('[ExtensionConnHandler]: Dispatching event: headsetAppUninstalled'); - newEvt = _that.createEvent('headsetAppUninstalled'); - newEvt.data = msg.data; - _that.dispatch(newEvt); - break; - default: - logger.warning('[ExtensionConnHandler]: Received unknown HEADSET_APP msg type from ansible-content:', msg.data.type); - break; - } - } else { - if (!msg.suppressLog) { - logger.info('[ExtensionConnHandler]: Dispatching event:', msg.target); - } - newEvt = _that.createEvent(msg.target); - newEvt.data = msg.data; - newEvt.suppressLog = msg.suppressLog; - // Just add the request id for an Extension request - if (msg.reqId && msg.type === ChromeExtension.BgMsgType.REQUEST) { - newEvt.reqId = msg.reqId; - } - _that.dispatch(newEvt); - } - } - - function handleExtResponse(resp) { - if (_reqCallbacks[resp.reqId]) { - var cbInfo = _reqCallbacks[resp.reqId]; - if (cbInfo.timer) { - window.clearTimeout(cbInfo.timer); - } - delete _reqCallbacks[resp.reqId]; - - // Invoke the callback function (error is always the 1st parameter) - if (!resp.data || !resp.data.error) { - cbInfo.cb(null, resp.data); - } else { - cbInfo.cb(resp.data.error); - - if (resp.data.error === ChromeExtension.ResponseError.UNREGISTERED) { - onExtensionUnregistered(); - } - } - - if (!resp.suppressLog) { - logger.debug('[ExtensionConnHandler]: Remaining callbacks pending:', - Object.keys(_reqCallbacks).length); - } - } else { - logger.info('[ExtensionConnHandler]: No registered callback for reqId =', resp.reqId); - } - } - - // Send Messages (requests) to Chrome Extension - function sendMessage(data, cb, cbTimeout, suppressLog) { - if (!data) { - logger.error('[ExtensionConnHandler]: Cannot send message. Invalid message.'); - cb && window.setTimeout(function () { cb('Invalid message'); }, 0); - return false; - } - - if (!_that.isExtensionRunning()) { - logger.info('[ExtensionConnHandler]: Chrome extension is not running'); - cb && window.setTimeout(function () { cb('Extension not running'); }, 0); - return false; - } - return dispatchEvent(data, cb, cbTimeout, suppressLog); - } - - function dispatchEvent(data, cb, cbTimeout, suppressLog) { - if (data.type === ChromeExtension.BgMsgType.REQUEST) { - data.reqId = addReqCallback(cb, cbTimeout); - } - - if (!suppressLog) { - logger.msgSend('[ExtensionConnHandler]: ', data); - } - data.suppressLog = suppressLog; - - // For electron the exchange connector is running in the same renderer, so - // call it directly, passing the responder object for responses and events. - if (circuit.isElectron && - _bgExchangeConnectorHandler && - data.type === ChromeExtension.BgMsgType.REQUEST && - data.target === ChromeExtension.BgTarget.EXCHANGE_CONNECTOR) { - - window.setTimeout(function () { - _bgExchangeConnectorHandler.onWebClientMsg(_bgExchangeResponder, data, circuit.__server); - }, 0); - return data.reqId; - } - - var msg = new CustomEvent(ChromeExtension.DOMEvent.TO_EXTENSION, {detail: JSON.stringify(data)}); - try { - document && document.dispatchEvent(msg); - } catch (e) { - logger.warning('[ExtensionConnHandler]: Error sending message: ' + msg, e); - return false; - } - return data.reqId; - } - - function onExtensionUnregistered() { - logger.warning('[ExtensionConnHandler]: The content script has unregistered itself. Chrome extension is no longer running.'); - _extInitialized = false; - logger.info('[ExtensionConnHandler]: Dispatching event: extensionUnregistered'); - var newEvt = _that.createEvent('extensionUnregistered'); - _that.dispatch(newEvt); - } - - /////////////////////////////////////////////////////////////////////////// - // Event handlers - /////////////////////////////////////////////////////////////////////////// - function onMessage(msgObj) { - if (msgObj.target === ChromeExtension.BgTarget.LOG) { - logger.logMsg(msgObj.data.log.level, msgObj.data.log.messages); - return; - } - - if (!msgObj.suppressLog) { - logger.msgRcvd('[ExtensionConnHandler]: ', msgObj); - } - - switch (msgObj.type) { - case ChromeExtension.BgMsgType.REQUEST: - case ChromeExtension.BgMsgType.EVENT: - handleExtMessage(msgObj); - break; - case ChromeExtension.BgMsgType.RESPONSE: - handleExtResponse(msgObj); - break; - default: - logger.msgRcvd('[ExtensionConnHandler]: Unexpected message type: ', msgObj.type); - break; - } - } - - function onExtensionMessage(msg) { - if (!msg || !msg.detail) { - return; - } - try { - var msgObj = JSON.parse(msg.detail); - onMessage(msgObj); - } catch (e) { - logger.error('[ExtensionConnHandler]: Error parsing JSON object. ', e); - return; - } - } - - /////////////////////////////////////////////////////////////////////////// - // Public functions - /////////////////////////////////////////////////////////////////////////// - this.isExtensionRunning = function () { - return _extInitialized; - }; - - /****************************************************** - * Sreenshare APIs (Used by RTCSessionController - ******************************************************/ - this.getScreenShareUserMedia = function (cb) { - sendMessage({ - target: ChromeExtension.BgTarget.SCREEN_SHARE, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - type: ChromeExtension.BgScreenShareMsgType.CHOOSE_DESKTOP_MEDIA - } - }, cb); - }; - - /****************************************************** - * Exchange Connector APIs - ******************************************************/ - this.exchangeConnect = function (settings, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - method: ChromeExtension.BgExchangeMsgType.CONNECT, - object: { - settings: settings - } - }, - keysToOmitFromLogging: ['data.object.settings.exchInfo'] - }, cb, EXCHANGE_CONNECT_TIMEOUT); - }; - - this.exchangeDisconnect = function (cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - method: ChromeExtension.BgExchangeMsgType.DISCONNECT, - object: {} - } - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.exchangeGetConnectionStatus = function (key, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.GET_CONNECTION_STATUS - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.exchangeGetCapabilities = function (cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - method: ChromeExtension.BgExchangeMsgType.GET_CAPABILITIES - } - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - /** - * Cancel the callback for a specific request - * - * @function - * @param {int} Request Id to cancel - * - * @returns {bool} Returns true if callback was canceled. - */ - this.exchangeCancelReqCallback = function (reqId) { - logger.debug('[ExtensionConnHandler]: Unregistering callback for reqId:', reqId); - return unregisterReqCallback(reqId); - }; - - /** - * Execute user search in Exchange server. - * - * @function - * @param {key} Encryption key - * @param {String} The query string to search. - * @param {int} Amount of results to be retrieved from exchange server. - * @param {function} Callback function - * - * @returns {int} Returns the request id generated for this search. Used to cancel previous searches. - */ - this.exchangeSearchContacts = function (key, searchStr, resCount, cb) { - return sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.SEARCH_CONTACTS, - object: { - searchString: searchStr, - resultCount: resCount - } - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.exchangeResolveContact = function (key, contactData, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.RESOLVE_CONTACT, - object: contactData - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.exchangeGetContact = function (key, exchangeEmail, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.GET_CONTACT, - object: { - email: exchangeEmail - } - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.exchangeOnRenewedToken = function (key, reqId, token, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.RESPONSE, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.ON_RENEWED_TOKEN, - object: { - reqId: reqId, - token: token - } - }, - keysToOmitFromLogging: ['data.key', 'data.object.token'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - - }; - - this.exchangeStoreCredentials = function (credentials, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - method: ChromeExtension.BgExchangeMsgType.STORE_EXCH_CREDENTIALS, - object: credentials - }, - keysToOmitFromLogging: ['data.object'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.getAllPersonalContacts = function (key, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.GET_ALL_PERSONAL_CONTACTS - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.getAppointments = function (key, startDate, endDate, resCount, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.GET_APPOINTMENTS, - startDate: startDate, - endDate: endDate, - resCount: resCount - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.syncAllPersonalContacts = function (key, syncState, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.SYNC_ALL_PERSONAL_CONTACTS, - syncState: syncState - }, - keysToOmitFromLogging: ['data.key', 'data.syncState'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - this.getStoredCredentials = function (key, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.EXCHANGE_CONNECTOR, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - key: key, - method: ChromeExtension.BgExchangeMsgType.GET_STORED_CREDENTIALS - }, - keysToOmitFromLogging: ['data.key'] - }, cb, EXCHANGE_RESPONSE_TIMEOUT); - }; - - /****************************************************** - * Headset App Manager APIs - ******************************************************/ - this.getHeadsetIntegrationAppStatus = function (appId, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.HEADSET_APP, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - type: ChromeExtension.BgHeadsetAppMsgType.GET_HEADSET_APP_STATUS, - appId: appId - } - }, cb, RESPONSE_TIMEOUT); - }; - - this.launchHeadsetIntegrationApp = function (appId, localizedStrings, cb) { - sendMessage({ - target: ChromeExtension.BgTarget.HEADSET_APP, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - type: ChromeExtension.BgHeadsetAppMsgType.LAUNCH_HEADSET_APP, - appId: appId, - localizedStrings: localizedStrings - } - }, cb, HEADSET_APP_LAUNCH_TIMEOUT); - }; - - /****************************************************** - * Internal APIs - ******************************************************/ - this.bringToFront = function (cb) { - sendMessage({ - target: ChromeExtension.BgTarget.INTERNAL, - type: ChromeExtension.BgMsgType.REQUEST, - data: { - type: ChromeExtension.BgInternalMsgType.BRING_TO_FRONT - } - }, cb); - }; - - /////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////// - - if (Utils.getBrowserInfo().chrome) { - // Register for events from Chrome extension - document && document.addEventListener(ChromeExtension.DOMEvent.FROM_EXTENSION, onExtensionMessage); - } - } - - Utils.inherit(ExtensionConnHandler, BaseEventTarget); - ExtensionConnHandler.prototype.name = 'ExtensionConnHandler'; - - // Exports - circuit.ExtensionConnHandler = ExtensionConnHandler; - - return circuit; -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - circuit.ExtensionConnHandlerSingleton = (function () { - var _extensionConnHandler; - - return { - getInstance: function () { - if (!_extensionConnHandler) { - _extensionConnHandler = new circuit.ExtensionConnHandler(); - } - return _extensionConnHandler; - } - }; - })(); - - return circuit; -})(Circuit); - -/** - * Enum definitions. - * - * @module Circuit - * @submodule Circuit.Enums - */ -var Circuit = (function (circuit) { - 'use strict'; - - /** - * Enum for system permissions assigned to users. - * @class SystemPermission - * @static - * @final - */ - /** - * CLIENT_LOGON - * @property CLIENT_LOGON - * @type {String} - * @static - */ - /** - * INVITE_USER - * @property INVITE_USER - * @type {String} - * @static - */ - /** - * ROLE_TELEPHONY_CONNECTOR - * @property ROLE_TELEPHONY_CONNECTOR - * @type {String} - * @static - */ - /** - * ROLE_VIRTUAL_TELEPHONY_CONNECTOR - * @property ROLE_VIRTUAL_TELEPHONY_CONNECTOR - * @type {String} - * @static - */ - /** - * ROLE_TECHNICAL - * @property ROLE_TECHNICAL - * @type {String} - * @static - */ - /** - * ROLE_SESSION_GUEST - * @property ROLE_SESSION_GUEST - * @type {String} - * @static - */ - /** - * ROLE_TENANT_ADMIN - * @property ROLE_TENANT_ADMIN - * @type {String} - * @static - */ - /** - * ROLE_SUPPORT - * @property ROLE_SUPPORT - * @type {String} - * @static - */ - /** - * ROLE_SYSTEM_ADMIN - * @property ROLE_SYSTEM_ADMIN - * @type {String} - * @static - */ - /** - * MANAGE_TENANT - * @property MANAGE_TENANT - * @type {String} - * @static - */ - /** - * TELEPHONY - * @property TELEPHONY - * @type {String} - * @static - */ - /** - * RECORDING - * @property RECORDING - * @type {String} - * @static - */ - /** - * MODERATION - * @property MODERATION - * @type {String} - * @static - */ - /** - * GUEST_ACCESS - * @property GUEST_ACCESS - * @type {String} - * @static - */ - /** - * MAX_PACKAGE_ACCOUNTS - * @property MAX_PACKAGE_ACCOUNTS - * @type {String} - * @static - */ - /** - * FREE_TRIAL - * @property FREE_TRIAL - * @type {String} - * @static - */ - /** - * USER_STORAGE - * @property USER_STORAGE - * @type {String} - * @static - */ - /** - * INTEGRATIONS - * @property INTEGRATIONS - * @type {String} - * @static - */ - /** - * RTC_PARTICIPANTS - * @property RTC_PARTICIPANTS - * @type {String} - * @static - */ - /** - * ACCOUNTS - * @property ACCOUNTS - * @type {String} - * @static - */ - /** - * IE_PLUGIN_SUPPORT - * @property IE_PLUGIN_SUPPORT - * @type {String} - * @static - */ - /** - * SUPPORT_CONVERSATION - * @property SUPPORT_CONVERSATION - * @type {String} - * @static - */ - /** - * SUPPORT_FORUM - * @property SUPPORT_FORUM - * @type {String} - * @static - */ - /** - * OUTLOOK_PLUGIN - * @property OUTLOOK_PLUGIN - * @type {String} - * @static - */ - /** - * TENANT_STORAGE - * @property TENANT_STORAGE - * @type {String} - * @static - */ - /** - * PSTN_DIAL_IN - * @property PSTN_DIAL_IN - * @type {String} - * @static - */ - /** - * LDAP_AGENT - * @property LDAP_AGENT - * @type {String} - * @static - */ - /** - * Permission to invite users in a partner domain - * @property INVITE_PARTNER - * @type {String} - * @static - */ - /** - * ANALYTICS - * @property ANALYTICS - * @type {String} - * @static - */ - /** - * VIEW_OPEN_CONVERSATION - * @property VIEW_OPEN_CONVERSATION - * @type {String} - * @static - */ - /** - * SSO - * @property SSO - * @type {String} - * @static - */ - /** - * IE_PLUGIN_SUPPORT_LIMITATION - * @property IE_PLUGIN_SUPPORT_LIMITATION - * @type {String} - * @static - */ - /** - * ROLE_MEETING_POINT - * @property ROLE_MEETING_POINT - * @type {String} - * @static - */ - - - /** - * Enum for device types. - * @class DeviceType - * @static - * @final - */ - /** - * An OpenScape Desk Phone CP - * @property PHONE - * @type {String} - * @static - */ - /** - * A client running inside a browser (in any platform) - * @property WEB - * @type {String} - * @static - */ - /** - * A client deployed as an application (e.g. C4O) - * @property APPLICATION - * @type {String} - * @static - */ - /** - * A client running as a native mobile app - * @property MOBILE - * @type {String} - * @static - */ - /** - * Any client built with the SDK(s) - * @property SDK - * @type {String} - * @static - */ - - /** - * Enum for call state names. - * @class CallStateName - * @static - * @final - */ - /** - * No call (idle) - * @property Idle - * @type {String} - * @static - */ - /** - * Outgoing call initiated - * @property Initiated - * @type {String} - * @static - */ - /** - * Outgoing call connecting - * @property Connecting - * @type {String} - * @static - */ - /** - * Outgoing call delivered - * @property Delivered - * @type {String} - * @static - */ - /** - * Outgoing call, peer busy - * @property Busy - * @type {String} - * @static - */ - /** - * Regular failed call - * @property Failed - * @type {String} - * @static - */ - /** - * Missed call - * @property Missed - * @type {String} - * @static - */ - /** - * Declined call - * @property Declined - * @type {String} - * @static - */ - /** - * Not answered call - * @property NotAnswered - * @type {String} - * @static - */ - /** - * Incoming call ringing - * @property Ringing - * @type {String} - * @static - */ - /** - * Incoming call answering - * @property Answering - * @type {String} - * @static - */ - /** - * Regular Established call - * @property Active - * @type {String} - * @static - */ - /** - * Held established call - * @property Held - * @type {String} - * @static - */ - /** - * Holding established call - * @property Holding - * @type {String} - * @static - */ - /** - * Hold on hold established call - * @property HoldOnHold - * @type {String} - * @static - */ - /** - * Waiting established call - * @property Waiting - * @type {String} - * @static - */ - /** - * Remote call started - * @property Started - * @type {String} - * @static - */ - /** - * Remote call active - * @property ActiveRemote - * @type {String} - * @static - */ - /** - * Call has been terminated - * @property Terminated - * @type {String} - * @static - */ - circuit.Enums.CallStateName = {}; - Object.getOwnPropertyNames(circuit.Enums.CallState).forEach(function (name) { - circuit.Enums.CallStateName[name] = name; - }); - - - /** - * Enum for connection state. - * @class ConnectionState - * @static - * @final - */ - /** - * Disconnected - * @property Disconnected - * @type {String} - * @static - */ - /** - * Connecting - * @property Connecting - * @type {String} - * @static - */ - /** - * Reconnecting - * @property Reconnecting - * @type {String} - * @static - */ - /** - * Connected - * @property Connected - * @type {String} - * @static - */ - - - /** - * Enum for conversation item types. - * @class ConversationItemType - * @static - * @final - */ - circuit.Enums.ConversationItemType = circuit.Constants.ConversationItemType; - /** - * Text message type - * @property TEXT - * @type {String} - * @static - */ - /** - * Real-time communication item. E.g. voice, video, screenshare - * @property RTC - * @type {String} - * @static - */ - /** - * Sysyem item. E.g. ParticipantAdded, ConversationTitleChanged - * @property SYSTEM - * @type {String} - * @static - */ - - - /** - * Enum for conversation types. - * @class ConversationType - * @static - * @final - */ - circuit.Enums.ConversationType = { - DIRECT: 'DIRECT', - GROUP: 'GROUP', - LARGE: 'LARGE', - COMMUNITY: 'COMMUNITY' // Changed from OPEN to COMMUNITY for SDK - }; - - /** - * Direct Conversation (peer-to-peer) - * @property DIRECT - * @type {String} - * @static - */ - /** - * Group Conversation (private) - * @property GROUP - * @type {String} - * @static - */ - /** - * Community Conversation (public) - * @property COMMUNITY - * @type {String} - * @static - */ - - - /** - * Enum for user presence state. - * @class PresenceState - * @static - * @final - */ - circuit.Enums.PresenceState = circuit.Constants.PresenceState; - /** - * @property AVAILABLE - * @type {String} - * @static - */ - /** - * @property OFFLINE - * @type {String} - * @static - */ - /** On web this means 40 min of inactivity. There is no away on mobile. If online on mobile then state is AVAILABLE. - * @property AWAY - * @type {String} - * @static - */ - /** User is on an active RTC session. In the future calendar entries can also set a user to busy. - * @property BUSY - * @type {String} - * @static - */ - /** - * DND means 'Snooze Notifications' turned on. - * @property DND - * @type {String} - * @static - */ - - - /** - * Enum for RTC Item Type. - * @class RTCItemType - * @static - * @final - */ - circuit.Enums.RTCItemType = circuit.Constants.RTCItemType; - /** - * @property STARTED - * @type {String} - * @static - */ - /** - * @property ENDED - * @type {String} - * @static - */ - /** - * @property MISSED - * @type {String} - * @static - */ - /** - * @property PARTICIPANT_JOINED - * @type {String} - * @static - */ - /** - * @property PARTICIPANT_LEFT - * @type {String} - * @static - */ - /** - * @property MOVED - * @type {String} - * @static - */ - - - /** - * Enum for the RTC participant type. - * @class RTCParticipantType - * @static - * @final - */ - circuit.Enums.RTCParticipantType = circuit.Constants.RTCParticipantType; - /** - * Circuit users. - * @property USER - * @type {String} - * @static - */ - /** - * PSTN dial-in participants. - * @property EXTERNAL - * @type {String} - * @static - */ - /** - * Telephony connector users (dial in or dial out). - * @property TELEPHONY - * @type {String} - * @static - */ - /** - * Session guests. - * @property SESSION_GUEST - * @type {String} - * @static - */ - /** - * Conference Meeting Points. - * @property MEETING_POINT - * @type {String} - * @static - */ - - - /** - * Enum for System Item Type. - * @class SystemItemType - * @static - * @final - */ - circuit.Enums.SystemItemType = circuit.Constants.SystemItemType; - /** - * @property CONVERSATION_CREATED - * @type {String} - * @static - */ - /** - * @property PARTICIPANT_ADDED - * @type {String} - * @static - */ - /** - * @property PARTICIPANT_REMOVED - * @type {String} - * @static - */ - /** - * @property CONVERSATION_RENAMED - * @type {String} - * @static - */ - /** - * @property GUEST_JOINED - * @type {String} - * @static - */ - /** - * @property GUEST_INVITED - * @type {String} - * @static - */ - /** - * @property CONFERENCE_DETAILS_CHANGED - * @type {String} - * @static - */ - /** - * @property CONVERSATION_MODERATED - * @type {String} - * @static - */ - - - /** - * Enum for Text Content Type. - * @class TextItemContentType - * @static - * @final - */ - circuit.Enums.TextItemContentType = circuit.Constants.TextItemContentType; - /** - * Plain text message - * @property PLAIN - * @type {String} - * @static - */ - /** - * Rich text message. Allows basic Circuit HTML formatting. - * @property RICH - * @type {String} - * @static - */ - - - /** - * Enum for User Roles. - * @class UserRole - * @static - * @final - */ - circuit.Enums.UserType = circuit.Constants.UserType; - /** - * Regular Circuit user - * @property REGULAR - * @type {String} - * @static - */ - /** - * Support user - * @property SUPPORT - * @type {String} - * @static - */ - /** - * Special user for the telephony connector - * @property TELEPHONY - * @type {String} - * @static - */ - /** - * Special technical user - * @property TECHNICAL - * @type {String} - * @static - */ - /** - * Non Circuit users invited to join an RTC session - * @property SESSION_GUEST - * @type {String} - * @static - */ - /** - * Conference Meeting Points (aka MCP) - * @property MEETING_POINT - * @type {String} - * @static - */ - /** - * XMPP user - * @property XMPP - * @type {String} - * @static - */ - /** - * Reserved for Bots - * @property BOT - * @type {String} - * @static - */ - - - /** - * Enum for User States. - * @class UserState - * @static - * @final - */ - circuit.Enums.UserState = circuit.Constants.UserState; - /** - * User is just created or deleted - * @property INACTIVE - * @type {String} - * @static - */ - /** - * User is active (but maybe has to register himself) - * @property ACTIVE - * @type {String} - * @static - */ - /** - * User is suspended - * @property SUSPENDED - * @type {String} - * @static - */ - /** - * User is deleted - * @property DELETED - * @type {String} - * @static - */ - - - /** - * Enum for Search Direction. - * @class SearchDirection - * @static - * @final - */ - circuit.Enums.SearchDirection = circuit.Constants.SearchDirection; - /** - * Before a specific timestamp - * @property BEFORE - * @type {String} - * @static - */ - /** - * After a specific timestamp - * @property AFTER - * @type {String} - * @static - */ - /** - * Before and After (not applicable to regular retrieval APIs) - * @property BOTH - * @type {String} - * @static - */ - - - /** - * Enum for a Participant Search Criteria. - * @class ParticipantCriteria - * @static - * @final - */ - circuit.Enums.ParticipantCriteria = circuit.Constants.GetConversationParticipantCriteria; - /** - * The display name of the search string (e.g. participant). Searches like <Mich> are allowed and would return accounts of users with display name <Michael> and <Michaela> for example. - * @property DISPLAY_NAME - * @type {String} - * @static - */ - /** - * The participant type. Supported values are REGULAR, FORMER, MODERATOR, GUEST or ACTIVE where active means all participants which are not of type FORMER. - * @property TYPE - * @type {String} - * @static - */ - - - /** - * Enum for a Participant Type. - * @class ParticipantCriteria - * @static - * @final - */ - circuit.Enums.ConversationParticipantType = circuit.Constants.ConversationParticipantType; - /** - * Regular default participant of a conversation. - * @property REGULAR - * @type {String} - * @static - */ - /** - * Former participant, i.e. the user was memeber of the conversation but has left it or was removed. - * @property FORMER - * @type {String} - * @static - */ - /** - * Special type in case the conversation is moderaded and this participant has the moderation rights. - * @property MODERATOR - * @type {String} - * @static - */ - /** - * Guest participant, i.e. user of a different tenant compared to the tenant in which this conversation was created. - * @property GUEST - * @type {String} - * @static - */ - - - /** - * Enum for a ATC (Advanced Telephony Connector) registration state. - * @class AtcRegistrationState - * @static - * @final - */ - /** - * Client is not associated with ATC. - * @property Disconnected - * @type {String} - * @static - */ - /** - * Client is not registered with ATC. - * @property Unregistered - * @type {String} - * @static - */ - /** - * Client is registering with ATC. - * @property Registering - * @type {String} - * @static - */ - /** - * Client is registered with ATC. - * @property Registered - * @type {String} - * @static - */ - - /** - * Enum specifying the result for GetConversationsByFilter API - * @class RetrieveAction - * @static - * @final - */ - /** - * Retrieve paged, filtered conversations specified via options attributes. - * @property CONVERSATIONS - * @type {String} - * @static - */ - /** - * Retrieve all filtered conversation IDs. - * @property CONVERSATION_IDS - * @type {String} - * @static - */ - circuit.Enums.RetrieveAction = circuit.Constants.RetrieveAction; - - - /** - * Enum for the call recording state - * @class RecordingInfoState - * @static - * @final - */ - /** - * No recording was ever started - * @property INITIAL - * @type {String} - * @static - */ - /** - * Recording was started, but recorder was disabled. Recording is not running. - * @property START_PENDING - * @type {String} - * @static - */ - /** - * Recording was started and is running currently - * @property STARTED - * @type {String} - * @static - */ - /** - * Recording was stopped - * @property STOPPED - * @type {String} - * @static - */ - /** - * Recording has finished (state after STOPPED) - * @property FINISHED - * @type {String} - * @static - */ - circuit.Enums.RecordingInfoState = circuit.Constants.RecordingInfoState; - - - /** - * Enum for the call recording reason - * @class RecordingInfoReason - * @static - * @final - */ - /** - * Set if recording is not stopped or failed. - * @property NONE - * @type {String} - * @static - */ - /** - * The recording has been stopped manually. - * @property STOPPED_MANUALLY - * @type {String} - * @static - */ - /** - * The recording has been stopped automatically after a successful recording. - * @property STOPPED_AUTOMATICALLY - * @type {String} - * @static - */ - /** - * There was no input (i.e. only silence streaming data). - * @property NO_INPUT_TIMEOUT - * @type {String} - * @static - */ - /** - * The recording has been aborted due to too much input. A recorded file is available. - * @property MAX_INPUT_TIMEOUT - * @type {String} - * @static - */ - /** - * The recording has been stopped since no (more) streaming data is available. - * @property NO_STREAMING_DATA - * @type {String} - * @static - */ - /** - * The recording has been stopped because the file size limit is reached. - * @property LENGTH_LIMIT_REACHED - * @type {String} - * @static - */ - /** - * The recording didn't started because the disk partition is full. - * @property NO_MORE_DISK_SPACE_LEFT - * @type {String} - * @static - */ - /** - * The recording didn't started or has been stopped for any other reason. - * @property UNKNOWN_ERROR - * @type {String} - * @static - */ - circuit.Enums.RecordingInfoState = circuit.Constants.RecordingInfoState; - - - /** - * Enum for the search scopes - * @class SearchScope - * @static - * @final - */ - /** - * Global scope to search in all scopes. - * @property ALL - * @type {String} - * @static - */ - /** - * Search for user in a direct conversations. Only one People scoping in a - * single search and cannot be combined with Members scoping. - * @property PEOPLE - * @type {String} - * @static - */ - /** - * Search for member of a group or direct conversation. Members scoping cannot be - * combined with People scoping. Multiple Members scoping is possible, but in that case, the terms - * must match members of a single group conversation. - * @property MEMBERS - * @type {String} - * @static - */ - /** - * Search a name of a conversation. - * @property CONVERSATIONS - * @type {String} - * @static - */ - /** - * Search for a filename in a conversation. - * @property FILES - * @type {String} - * @static - */ - /** - * Search the creator of a message. - * @property SENT_BY - * @type {String} - * @static - */ - /** - * Search for messages between the given optional `startTime` and optional `endTime` dates. - * @property DATE - * @type {String} - * @static - */ - /** - * Search for conversation with a given label - * @property LABEL - * @type {String} - * @static - */ - /** - * Search based on the definition of a filter as defined in `rootConnector`. - * @property FILTER - * @type {String} - * @static - */ - circuit.Enums.SearchScope = circuit.Constants.SearchScope; - - - /** - * Enum for the search status - * @class SearchStatusCode - * @static - * @final - */ - /** - * Error. E.g. unique search ID is not valid anymore. - * @property ERROR - * @type {String} - * @static - */ - /** - * The search was canceled by the client. - * @property CANCELED - * @type {String} - * @static - */ - /** - * The search was finished by the server, i.e. there a no more results. - * @property FINISHED - * @type {String} - * @static - */ - /** - * The server stops the search because a timeout was reached. - * @property TIMEOUT - * @type {String} - * @static - */ - /** - * No results found. - * @property NO_RESULT - * @type {String} - * @static - */ - /** - * If search was started with a resultSetLimit, this indicates that more results are available. - * @property MORE_RESULT - * @type {String} - * @static - */ - circuit.Enums.SearchStatusCode = circuit.Constants.SearchStatusCode; - - - /** - * Enum for the session closed reasons - * @class SessionClosedReason - * @static - * @final - */ - /** - * New connection has been detected. This is not applicable to the SDK. - * @property NEW_CONNECTION_DETECTED - * @type {String} - * @static - */ - /** - * User has been usspended. - * @property SUSPENDED - * @type {String} - * @static - */ - /** - * Tenant has been suspended. - * @property TENANT_SUSPENDED - * @type {String} - * @static - */ - /** - * User has been deleted. - * @property DELETED - * @type {String} - * @static - */ - /** - * Tenant has been deleted. - * @property TENANT_DELETED - * @type {String} - * @static - */ - circuit.Enums.SessionClosedReason = circuit.Constants.SessionClosedReason; - - return circuit; -})(Circuit || {}); - -// Define external globals for JSHint -/*global Promise, window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - /** - * Client Idle state - * @readonly - * @enum {String} - * @property Idle - Client is idle - * @property Active - Client is active - */ - var IdleState = Object.freeze({ - Idle: 'Idle', - Active: 'Active' - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // SdkHelperSvc Implementation - // - // This service mocks the interfaces of different services which are needed by - // other services, but are not applicable for session guests. - /////////////////////////////////////////////////////////////////////////////////////// - function SdkHelperSvcImpl($rootScope, $q, LogSvc, PubSubSvc) { - - // The following imports need to be defined inside SdkHelperSvcImpl due to JS-SDK - var ConnectionState = circuit.Enums.ConnectionState; - var Constants = circuit.Constants; - var Conversation = circuit.Conversation; - var UserProfile = circuit.UserProfile; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _self = this; - var _conversations = {}; - var _telephonyConversation; - var _defaultCallerId = null; - var _telephonyData = {}; - var _clientApiHandler = circuit.ClientApiHandlerSingleton.getInstance(); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function getUserById() { - return null; - } - - // Function copied from UserProfileSvc - function updateTelephonyNumbers() { - if (!$rootScope.localUser || !$rootScope.localUser.accounts || !$rootScope.localUser.accounts[0]) { - return false; - } - var user = $rootScope.localUser; - var account = $rootScope.localUser.accounts[0]; - var currentCallerId = user.callerId; - if (account.telephonyConfiguration) { - user.phoneNumber = account.telephonyConfiguration.phoneNumber || null; - if (account.telephonyConfiguration.associatedTelephonyUserType === Constants.GtcTrunkType.ATC_TRUNK_TYPE) { - // User is assigned to an ATC - user.callerId = account.telephonyConfiguration.phoneNumber; - user.cstaNumber = Utils.cleanPhoneNumber(account.telephonyConfiguration.phoneNumber); - } else { - // User is assigned to GTC/ETC - user.callerId = account.telephonyConfiguration.callerId || user.phoneNumber || _defaultCallerId; - user.cstaNumber = null; - } - } else { - user.phoneNumber = null; - user.callerId = _defaultCallerId; - user.cstaNumber = null; - } - - return currentCallerId !== user.callerId; - } - - function getTelephonyData() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getTelephonyData(function (err, data) { - if (err) { - reject(err); - } else { - _telephonyData = data; - _defaultCallerId = _telephonyData.defaultCallerId || null; - resolve(data); - } - }); - }); - } - - function getTelephonyConversation() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getTelephonyConversationId(function (err, telephonyConvId) { - if (err || !telephonyConvId) { - reject('Telephony conversation not found'); - } else { - _self.getConversationById(telephonyConvId, null, function (err, c) { - if (err || !c) { - reject(err || 'Error retrieving telephony conversation'); - } else { - _telephonyConversation = c; - c.isTelephonyConv = true; - resolve(c); - } - }); - } - }); - }); - } - - - /////////////////////////////////////////////////////////////////////////////////////// - // Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - - // Copied from UserProfileSvc - PubSubSvc.subscribe('/telephony/data', function (data) { - var userUpdated = false; - // If the defaultCallerID changed, then re-evaluate the user's caller ID - if (_defaultCallerId !== data.defaultCallerId) { - _defaultCallerId = data.defaultCallerId; - if (Utils.updateTelephonyNumbers($rootScope.localUser, data.defaultCallerId)) { - userUpdated = true; - } - } - if ($rootScope.localUser.telephonyAvailable !== data.telephonyAvailableForUser) { - $rootScope.localUser.telephonyAvailable = data.telephonyAvailableForUser; - userUpdated = true; - } - if (userUpdated) { - PubSubSvc.publish('/localUser/update', [$rootScope.localUser]); - } - }); - - // Copied from UserProfileSvc (and slightly modified) - _clientApiHandler.on('Account.TELEPHONY_CONFIGURATION_UPDATED', function (evt) { - if (!evt.configuration || !$rootScope.localUser) { - return; - } - var account = $rootScope.localUser.accounts && $rootScope.localUser.accounts[0]; - if (!account) { - return; - } - account.telephonyConfiguration = evt.configuration; - _self.telephonyConfigChanged(); - PubSubSvc.publish('/localUser/update', [$rootScope.localUser]); - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Functions - /////////////////////////////////////////////////////////////////////////////////////// - this.addConversationToCache = function (conv) { - if (conv && conv.convId) { - _conversations[conv.convId] = Conversation.extend(conv); - } - }; - - this.setLocalUser = function (user) { - if (user) { - $rootScope.localUser = user; - updateTelephonyNumbers(); - UserProfile.syncTelephonyConfig(user); - } - }; - - this.telephonyConfigChanged = function () { - updateTelephonyNumbers(); - UserProfile.syncTelephonyConfig($rootScope.localUser); - }; - - // ConversationSvc - this.getConversationPromise = function (convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(); - return; - } - var conv = _conversations[convId]; - if (conv) { - resolve(conv); - } else { - _self.getConversationById(convId, null, function (err, c) { - if (err || !c) { - reject('Conversation not found'); - } else { - resolve(c); - } - }); - } - - }); - }; - - // ConversationSvc - this.getConversationById = function (convId, options, cb) { - if (typeof cb === 'function') { - _clientApiHandler.getConversationById(convId, function (err, conv) { - var extConv = Conversation.extend(conv); - if (extConv) { - // Add to cache - _conversations[conv.convId] = extConv; - } - cb(err, extConv); - }); - } - }; - - // ConversationSvc - this.getConversationByRtcSession = function (rtcSessionId) { - LogSvc.debug('[SdkHelperSvc]: getConversationByRtcSession - rtcSessionId = ', rtcSessionId); - for (var convId in _conversations) { - if (_conversations.hasOwnProperty(convId)) { - if (_conversations[convId].rtcSessionId === rtcSessionId) { - return _conversations[convId]; - } - } - } - return null; - }; - - // ConversationSvc - this.getConversationFromCache = function (convId) { - LogSvc.debug('[SdkHelperSvc]: getConversationFromCache - convId = ', convId); - return _conversations[convId] || null; - }; - - // ConversationSvc - this.getConversationAsGuestById = function () { - return null; - }; - - this.getTelephonyData = getTelephonyData; - - // ConversationSvc - this.getTelephonyConversationPromise = function () { - if (_telephonyConversation) { - return Promise.resolve(_telephonyConversation); - } - return getTelephonyConversation(); - }; - - // ConversationSvc - this.getTelephonyConversation = function (cb) { - if (typeof cb === 'function') { - if (_telephonyConversation) { - window.setTimeout(function () { - cb(null, _telephonyConversation); - }); - return; - } - - getTelephonyData() - .then(updateTelephonyNumbers) - .then(getTelephonyConversation) - .then(function (c) { - cb(null, c); - }) - .catch(cb); - } - }; - - // ConversationSvc - this.getCachedTelephonyConversation = function () { - return _telephonyConversation; - }; - - // ConversationSvc - this.isConversationsLoadComplete = function () { - return !!$rootScope.localUser; - }; - - // ConversationSvc - this.updateLastCallTime = function () {}; - - // InstrumentationSvc - this.sendQOSData = function (call, mediaType, stats) { - LogSvc.debug('[SdkHelperSvc]: sendQOSData - call stats: ', stats); - }; - - // RegistrationSvc - this.state = function () { - // Return a corresponding RegistrationState. - // TODO: Move RegistrationState to Circuit.Enums - switch (_clientApiHandler.getConnState()) { - case ConnectionState.Disconnected: - return 'Disconnected'; - case ConnectionState.Connecting: - return 'Connecting'; - case ConnectionState.Reconnecting: - return 'Reconnecting'; - case ConnectionState.Connected: - return 'Registered'; - default: - return ''; - } - }; - - // RegistrationSvc - this.isRegistered = function () { - return _clientApiHandler.getConnState() === ConnectionState.Connected; - }; - - // UserProfileSvc - this.getSetting = function () { - return null; - }; - - // UserProfileSvc - this.getUserIdleState = function () { - return IdleState.Active; - }; - - // UserProfileSvc - this.isAutoSnoozeOn = false; - - // UserProfileSvc - this.setPresenceWithLocation = function () {}; - - // UserProfileSvc - this.startAutoSnooze = function () {}; - this.cancelAutoSnooze = function () {}; - - // UserSvc - this.getUserFromCache = getUserById; - - // UserSvc - this.getUser = getUserById; - - // UserSvc - this.getUserById = function (userId, cb) { - cb && cb(Constants.ReturnCode.NO_RESULT); - }; - - // UserSvc - this.addUsersToCache = function () {}; - - // UserSvc - this.startReverseLookUp = function (phoneNumber, doneCb) { - doneCb && doneCb(null); - }; - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.Enums = circuit.Enums || {}; - circuit.Enums.IdleState = IdleState; - circuit.SdkHelperSvcImpl = SdkHelperSvcImpl; - - return circuit; - -})(Circuit); - -// Define global variables for JSHint - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Utils = circuit.Utils; - - // FAQ article IDs - var FaqArticle = { - AGC: '89415', - DEVICE_ACCESS: '48742', - JABRA: '112718', - LOGIN: '37428', - MODERATION: '93154', - PLANTRONICS: '115017', - TELEPHONY: '103733' - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // UtilSvc Implementation - /////////////////////////////////////////////////////////////////////////////////////// - function UtilSvcImpl($rootScope, $q, $timeout, LogSvc) { - LogSvc.debug('New Service: UtilSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal variables - /////////////////////////////////////////////////////////////////////////////////////// - var _self = this; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * Transforms callback-based function -- func(arg1, arg2 .. argN, callback) -- into - * an $q-compatible Promise. Promisify provides a default callback of the form (error, result) - * and rejects when `error` is truthy. You can also supply `this` object as the second argument. - * - * @param {function} original - The function to promisify - * @param {object} target - `this` object - * @return {function} A promisified version of `original` - */ - this.promisify = function (original, target) { - return function () { - var args = Array.prototype.slice.call(arguments); - return new $q(function (resolve, reject) { - args.push(function callback(err) { - if (err) { - return reject(err); - } - var values = Array.prototype.slice.call(arguments); - values.shift(); - switch (values.length) { - case 0: - resolve(); - break; - case 1: - resolve(values[0]); - break; - default: - resolve(values); - break; - } - }); - original.apply(target, args); - }); - }; - }; - - this.getFaqUrl = function (articleId) { - if ($rootScope.localUser && $rootScope.localUser.helpUrl && Utils.DEFAULT_HELP_URL !== $rootScope.localUser.helpUrl) { - return $rootScope.localUser.helpUrl; - } - - var url = 'https://www.circuit.com/unifyportalfaqdetail' + - (articleId ? '?articleId=' + articleId : ''); - return url; - }; - - - this.isPartnerAdmin = function (cb) { - if ($rootScope.localUser && $rootScope.localUser.isPartnerAdmin) { - return true; - } - LogSvc.debug('[UtilSvc]: Cannot invoke request. Local user is not a partner admin.'); - cb && $timeout(function () { cb('Not partner admin'); }); - return false; - }; - - this.isTenantAdmin = function (cb, tenantContext) { - if (!$rootScope.localUser) { - return false; - } - if (tenantContext && tenantContext.tenantId !== $rootScope.localUser.tenantId) { - return _self.isPartnerAdmin(cb); - } - if ($rootScope.localUser.isTenantAdmin) { - return true; - } - LogSvc.debug('[UtilSvc]: Cannot invoke request. Local user is not a tenant admin.'); - cb && $timeout(function () { cb('Not tenant admin'); }); - return false; - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.UtilSvcImpl = UtilSvcImpl; - - circuit.Enums = circuit.Enums || {}; - circuit.Enums.FaqArticle = FaqArticle; - - return circuit; - -})(Circuit); - -// Define global variables for JSHint - -var Circuit = (function (circuit) { - 'use strict'; - - // Import - var BaseCall = circuit.BaseCall; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Implementation - /////////////////////////////////////////////////////////////////////////////////////// - function PubSubSvcImpl(LogSvc) { - LogSvc.debug('New Service: PubSubSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _cache = {}; - var _cacheMobile = {}; - var _cacheOnce = {}; - var _cacheAll = []; - - function send(item, args) { - try { - args = (args !== undefined && args !== null) ? (Array.isArray(args) ? args : [args]) : []; - item.apply(null, args); - } catch (e) { - LogSvc.error(e); - } - } - - function publish(cache, topic, args) { - // Loop through a copy of the array in case the - // app unsubscribes while publishing the events. - cache[topic] && cache[topic].slice(0).forEach(function (item) { - send(item, args); - }); - } - - function publishMobile(topic, args) { - if (_cacheMobile[topic]) { - args = (args !== undefined && args !== null) ? (Array.isArray(args) ? args : [args]) : []; - args = args.map(function (arg) { - if (arg && typeof arg === 'object') { - // Return lightweight conversation and call objects without data that - // is not needed by the mobile applications. - if (arg.isConversationObject) { - return Utils.trimConvForMobile(arg); - } - if (arg instanceof BaseCall) { - return Utils.trimCallForMobile(arg); - } - } - return arg; - }); - - // Loop through a copy of the array in case the - // app unsubscribes while publishing the events. - _cacheMobile[topic].slice(0).forEach(function (item) { - send(item, args); - }); - } - } - - function publishAll(topic, args) { - args = args ? Array.prototype.slice.call(args) : []; - args.unshift(topic); - // Loop through a copy of the array in case the - // app unsubscribes while publishing the events. - _cacheAll.slice(0).forEach(function (item) { - send(item, args); - }); - } - - function subscribe(cache, topic, callback) { - if (!cache[topic]) { - cache[topic] = [callback]; - } else if (!cache[topic].includes(callback)) { - cache[topic].push(callback); - } - } - - function unsubscribe(cache, topic, callback) { - if (cache[topic]) { - var idx = cache[topic].indexOf(callback); - if (idx >= 0) { - cache[topic].splice(idx, 1); - } - } - } - - function subscribeAll(callback) { - if (!_cacheAll.includes(callback)) { - _cacheAll.push(callback); - } - } - - function unsubscribeAll(callback) { - var idx = _cacheAll.indexOf(callback); - if (idx >= 0) { - _cacheAll.splice(idx, 1); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - this.publish = function (topic, args) { - if (!_cache[topic] && !_cacheMobile[topic] && !_cacheOnce[topic] && !_cacheAll.length) { - return; - } - - LogSvc.debug('[PubSubSvc]: Publishing ' + topic + ' event'); - publish(_cache, topic, args); - publishMobile(topic, args); - publish(_cacheOnce, topic, args); - publishAll(topic, args); - - delete _cacheOnce[topic]; - }; - - this.subscribe = function (topic, callback) { - subscribe(_cache, topic, callback); - }; - - this.unsubscribe = function (topic, callback) { - unsubscribe(_cache, topic, callback); - }; - - this.subscribeOnce = function (topic, callback) { - subscribe(_cacheOnce, topic, callback); - }; - - this.unsubscribeOnce = function (topic, callback) { - unsubscribe(_cacheOnce, topic, callback); - }; - - this.subscribeMobile = function (topic, callback) { - subscribe(_cacheMobile, topic, callback); - }; - - this.unsubscribeMobile = function (topic, callback) { - unsubscribe(_cacheMobile, topic, callback); - }; - - this.subscribeAll = function (callback) { - subscribeAll(callback); - }; - - this.unsubscribeAll = function (callback) { - unsubscribeAll(callback); - }; - - this.resetSubscriptions = function () { - _cache = {}; - _cacheMobile = {}; - _cacheOnce = {}; - _cacheAll = []; - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.PubSubSvcImpl = PubSubSvcImpl; - - return circuit; - -})(Circuit); - -// Define external globals for JSHint -/*global chrome, require*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var ExtensionConnHandlerSingleton = circuit.ExtensionConnHandlerSingleton; - var ScreenSharingController = circuit.ScreenSharingController; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////// - // ExtensionSvc Implementation - /////////////////////////////////////////////////////////////////////////////////////// - function ExtensionSvcImpl($rootScope, $timeout, $window, $q, LogSvc, PubSubSvc, PopupSvc) { - LogSvc.debug('New Service: ExtensionSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////////////////// - var SANDBOX_HOSTNAME = 'circuitsandbox.net'; - var BETA_HOSTNAME = 'beta.circuit.com'; - var T_SYSTEMS_HOSTNAME = 'de.circuit.com'; - var T_AND_I_HOSTNAME = 'tandi.circuitsandbox.net'; - var HEIDELBERG_HOSTNAME = 'circuit.uni-heidelberg.de'; - - // This defines the minimum extension version supported by the webclient. - // Update this as old extension versions no longer work with the webclient. - var MIN_EXTENSION_VERSION_STR = '1.1.9000'; - var MIN_EXTENSION_VERSION = Utils.convertVersionToNumber(MIN_EXTENSION_VERSION_STR); - - // Desktop app has extension emulation. To skip validation of the emulator we use ELECTRON_KEY. - var ELECTRON_KEY = 'electron'; - - var ExtensionInstallErrorCodes = Object.freeze({ - UNSUPPORTED_BROWSER: 'UNSUPPORTED_BROWSER', - INSTALL_ALREADY_IN_PROGRESS: 'INSTALL_ALREADY_IN_PROGRESS', - UNSUPPORTED_EXT_VERSION: 'UNSUPPORTED_EXT_VERSION', - USER_CANCELLED_INSTALL: 'USER_CANCELLED_INSTALL', - INSTALL_FAILED: 'INSTALL_FAILED', - INTERNAL_ERROR: 'INTERNAL_ERROR' - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _self = this; - var _extConnHandler = (typeof ExtensionConnHandlerSingleton === 'object') ? ExtensionConnHandlerSingleton.getInstance() : null; - - var _extensionVersion = ''; - var _extensionVersionSupported = true; - var _extensionUrl = ''; - var _hasInlineInstall = false; - - var _inlineInstallInProgress = false; - var _inlineInstallFailed = false; - - // Pathing extension info to Controller for Screen Sharing - ScreenSharingController && ScreenSharingController.injectExtensionSvc(this, _extConnHandler); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function getExtensionInstallUrl() { - if (!$rootScope.browser || !$rootScope.browser.chrome) { - return; - } - - _extensionUrl = 'https://chrome.google.com/webstore/detail/'; - - if ($rootScope.siemensAGUser) { - // Siemens AG Extension: we can't trigger an inline installation, so just - // open the webstore link in a new tab - _hasInlineInstall = false; - _extensionUrl += 'siemens-circuit-by-unify/mnkbplodbhpkiphfeldjmaoickanlanc'; - LogSvc.info('[ExtensionSvc]: Set URL to Siemens AG Circuit extension webstore link'); - return; - } - - _hasInlineInstall = true; - - var hostname = $window.location.hostname; - switch (hostname) { - case BETA_HOSTNAME: - // Beta Extension - _extensionUrl += 'jafbhekoolndfieffjkikfbmekjppnec'; - LogSvc.info('[ExtensionSvc]: Set URL to Beta-Circuit extension. ', _extensionUrl); - break; - case SANDBOX_HOSTNAME: - // Sandbox Extension - _extensionUrl += 'ogdfpjnhdggplglemglldmioiblhkadm'; - LogSvc.info('[ExtensionSvc]: Set URL to Sandbox-Circuit extension. ', _extensionUrl); - break; - case T_AND_I_HOSTNAME: - // T&I Extension - _extensionUrl += 'gdgfgnopapmocggkadgfkjekdlcohmdl'; - LogSvc.info('[ExtensionSvc]: Set URL to T&I-Circuit extension. ', _extensionUrl); - break; - case T_SYSTEMS_HOSTNAME: - // T-Systems extension - _extensionUrl += 'mdgbjijndgkkllljdaoohenhhaenilam'; - LogSvc.info('[ExtensionSvc]: Set URL to T-Systems extension. ', _extensionUrl); - break; - case HEIDELBERG_HOSTNAME: - // Heidelberg Univerity extension (no inline installation available) - _hasInlineInstall = false; - _extensionUrl += 'universit%C3%A4t-heidelberg-ci/nnlgegdgijgicheaipembfeaggifmalb'; - LogSvc.info('[ExtensionSvc]: Set URL to Heidelberg Univerity extension webstore link ', _extensionUrl); - return; - default: - _extensionUrl += 'mhkbaognlahkdimlfcfhbeihldmjofgg'; - LogSvc.info('[ExtensionSvc]: Set URL to Circuit extension. ', _extensionUrl); - break; - } - } - - function init() { - $rootScope.isChromeExtRunning = _self.isExtensionRunning(); - getExtensionInstallUrl(); - } - - function invokeHandlerApi(apiName, params, cb) { - LogSvc.debug('[ExtensionSvc]: Invoke ExtensionConnHandler API: ', apiName); - if (!_extConnHandler) { - LogSvc.debug('[ExtensionSvc]: ExtensionConnHandler is not loaded'); - cb && $timeout(function () { cb('No ExtensionConnHandler'); }, 0); - return; - } - if (!_extConnHandler.isExtensionRunning()) { - LogSvc.debug('[ExtensionSvc]: Chrome extension is not running'); - cb && $timeout(function () { cb('Extension not running'); }, 0); - return; - } - if (cb) { - params = params || []; - // Push a callback wrapper that applies the digest cycle - params.push(function (err, data) { - $rootScope.$apply(function () { - cb(err, data); - }); - }); - } - - return _extConnHandler[apiName].apply(_extConnHandler, params); - } - - function validateExtensionVersion() { - LogSvc.info('[ExtensionSvc]: Validate the installed extension version: ', _extensionVersion); - if (_extensionVersion === ELECTRON_KEY) { - return; - } - - _extensionVersionSupported = Utils.convertVersionToNumber(_extensionVersion) >= MIN_EXTENSION_VERSION; - if (!_extensionVersionSupported) { - LogSvc.warn('[ExtensionSvc]: The installed extension version (' + _extensionVersion + - ') is not supported. Minimum version is ' + MIN_EXTENSION_VERSION_STR); - } - } - - function extensionInstallPreChecks(showErrorPopup) { - if (!$rootScope.browser || !$rootScope.browser.chrome) { - return ExtensionInstallErrorCodes.UNSUPPORTED_BROWSER; - } - - if (_inlineInstallInProgress || _inlineInstallFailed) { - // Something went wrong: the extension has been just installed and hasn't sent the init message yet. - LogSvc.warn('[ExtensionSvc]: The first extension installation attempt failed. Redirect the user to web store page. Url: ', _extensionUrl); - if (showErrorPopup && PopupSvc) { - PopupSvc.error({message: 'res_ExtensionCannotBeInstalled', messageParams: [_extensionUrl]}); - } - return _inlineInstallInProgress ? ExtensionInstallErrorCodes.INSTALL_ALREADY_IN_PROGRESS : - ExtensionInstallErrorCodes.INSTALL_FAILED; - } - - // Check if the extension needs to be updated - if (!_extensionVersionSupported) { - LogSvc.warn('[ExtensionSvc]: We are running an outdated extension:', _extensionVersion); - if (showErrorPopup && PopupSvc) { - PopupSvc.error({message: 'res_UnsupportedExtensionVersion'}); - } - return ExtensionInstallErrorCodes.UNSUPPORTED_EXT_VERSION; - } - - // No errors - return null; - } - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////////////////// - // Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - _extConnHandler && _extConnHandler.addEventListener('extensionInitialized', function (evt) { - $rootScope.$apply(function () { - _extensionVersion = evt.data.version; - validateExtensionVersion(); - if (_extensionVersionSupported) { - _inlineInstallInProgress = false; - $rootScope.isChromeExtRunning = true; - LogSvc.setExtensionVersion(_extensionVersion); - LogSvc.debug('[ExtensionSvc]: Publish /chromeExt/initialized event'); - PubSubSvc.publish('/chromeExt/initialized'); - } - }); - }); - - _extConnHandler && _extConnHandler.addEventListener('extensionUnregistered', function () { - $rootScope.$apply(function () { - $rootScope.isChromeExtRunning = false; - LogSvc.debug('[ExtensionSvc]: Publish /chromeExt/unregistered event'); - PubSubSvc.publish('/chromeExt/unregistered'); - }); - }); - - _extConnHandler && _extConnHandler.addEventListener('headsetAppLaunched', function (evt) { - $rootScope.$apply(function () { - var data = evt.data && evt.data.data; - LogSvc.debug('[ExtensionSvc]: Publish /chromeExt/headsetAppLaunched event: ', data); - PubSubSvc.publish('/chromeExt/headsetAppLaunched', [data]); - }); - }); - - _extConnHandler && _extConnHandler.addEventListener('headsetAppUninstalled', function (evt) { - $rootScope.$apply(function () { - var data = evt.data && evt.data.data; - LogSvc.debug('[ExtensionSvc]: Publish /chromeExt/headsetAppUninstalled event: ', data); - PubSubSvc.publish('/chromeExt/headsetAppUninstalled', [data]); - }); - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * Checks if Chrome Extension is installed and running. - * - * @returns {bool} true is extension is installed and running. - */ - this.isExtensionRunning = function () { - return _extConnHandler && _extConnHandler.isExtensionRunning() && _extensionVersionSupported; - }; - - /** - * Returns extension version - * - * @returns {String} Extension version - */ - this.getExtensionVersion = function () { - return _extensionVersion; - }; - - /** - * Returns whether the version of the installed extension is supported or not - * - * @returns {Boolean} true if supported, false otherwise - */ - this.isExtensionVersionSupported = function () { - return _extensionVersionSupported; - }; - - /*** - * This method opens up a modal asking for a confirmation from the user before actually installing the extension - * - * @param {Boolean} showErrorPopup Indicates whether this API should show a popup in case of error. - * @returns {Promise} A promise that is resolved once the extension is installed. - */ - this.installExtension = function (showErrorPopup) { - return new $q(function (resolve, reject) { - if (_self.isExtensionRunning()) { - // Extension is already installed - resolve(); - return; - } - - if (!PopupSvc) { - LogSvc.error('[ExtensionSvc]: installExtension API requires PopupSvc'); - reject(ExtensionInstallErrorCodes.INTERNAL_ERROR); - return; - } - - var error = extensionInstallPreChecks(showErrorPopup); - if (error) { - reject(error); - return; - } - - var options = { - message: 'res_ActionInstallExtensionText', - controller: 'ChromeExtensionInstallDialogCtrl', - title: 'res_ActionInstallExtensionTitle', - yesLabel: 'res_ActionInstall', - noLabel: 'res_Close', - backdrop: true - }; - - PopupSvc.confirm(options) - .result - .then(resolve) - .catch(function (err) { - if (showErrorPopup && err === ExtensionInstallErrorCodes.INSTALL_FAILED) { - LogSvc.warn('[ExtensionSvc]: The extension installation failed. Redirect the user to web store page. Url: ', _extensionUrl); - PopupSvc.error({message: 'res_ExtensionCannotBeInstalled', messageParams: [_extensionUrl]}); - } - reject(err || ExtensionInstallErrorCodes.USER_CANCELLED_INSTALL); - }); - }); - }; - - /*** - * This method performs the actual inline installation, skipping the modal confirmation. - * - * @param {Boolean} showErrorPopup Indicates whether this API should show a popup in case of error. - * @returns {Promise} A promise that is resolved once the extension is installed. - */ - this.inlineInstallation = function (showErrorPopup) { - return new $q(function (resolve, reject) { - try { - var error = extensionInstallPreChecks(showErrorPopup); - if (error) { - reject(error); - return; - } - - LogSvc.debug('[ExtensionSvc]: Extension URL = ', _extensionUrl); - - if (!_hasInlineInstall) { - LogSvc.debug('[ExtensionSvc]: Extension does not support inline installation. Open store link.'); - $window.open(_extensionUrl); - resolve(); - return; - } - - chrome.webstore.install(_extensionUrl, function () { - $rootScope.$apply(function () { - LogSvc.debug('[ExtensionSvc]: Circuit extension was successfully installed'); - _inlineInstallInProgress = true; - resolve(); - }); - }, function (err) { - LogSvc.error('[ExtensionSvc]: There was an error installing the Circuit extension. ', err); - $rootScope.$apply(function () { - if (err !== 'User cancelled install' /*This message should not be localized*/) { - // If the user cancelled the install, let them try again. Otherwise, mark this - // installation as complete and don't try to do an inline installation again. - _inlineInstallFailed = true; - if (showErrorPopup) { - LogSvc.warn('[ExtensionSvc]: The extension installation failed. Redirect the user to web store page. Url: ', _extensionUrl); - PopupSvc.error({message: 'res_ExtensionCannotBeInstalled', messageParams: [_extensionUrl]}); - } - reject(ExtensionInstallErrorCodes.INSTALL_FAILED); - } else { - reject(ExtensionInstallErrorCodes.USER_CANCELLED_INSTALL); - } - }); - }); - } catch (e) { - LogSvc.error('[ExtensionSvc]: There was an exception installing the Circuit extension. ', e); - _inlineInstallFailed = true; - reject(ExtensionInstallErrorCodes.INSTALL_FAILED); - } - }); - }; - - this.addEventListener = function (target, cb) { - _extConnHandler && _extConnHandler.addEventListener(target, cb); - }; - - /****************************************************** - * Exchange Connector APIs - ******************************************************/ - this.exchangeGetConnectionStatus = function (key, cb) { - return invokeHandlerApi('exchangeGetConnectionStatus', [key], cb); - }; - - this.exchangeConnect = function (settings, cb) { - return invokeHandlerApi('exchangeConnect', [settings], cb); - }; - - this.exchangeSearchContacts = function (key, searchStr, resCount, cb) { - return invokeHandlerApi('exchangeSearchContacts', [key, searchStr, resCount], cb); - }; - - this.exchangeGetCapabilities = function (cb) { - return invokeHandlerApi('exchangeGetCapabilities', [], cb); - }; - - this.exchangeResolveContact = function (key, contactData, cb) { - return invokeHandlerApi('exchangeResolveContact', [key, contactData], cb); - }; - - this.exchangeGetContact = function (key, exchangeEmail, cb) { - return invokeHandlerApi('exchangeGetContact', [key, exchangeEmail], cb); - }; - - this.exchangeDisconnect = function (cb) { - return invokeHandlerApi('exchangeDisconnect', [], cb); - }; - - this.exchangeOnRenewedToken = function (key, reqId, token, cb) { - return invokeHandlerApi('exchangeOnRenewedToken', [key, reqId, token], cb); - }; - - this.storeExchCredentials = function (credentials, cb) { - return invokeHandlerApi('exchangeStoreCredentials', [credentials], cb); - }; - - this.getStoredCredentials = function (key, cb) { - return invokeHandlerApi('getStoredCredentials', [key], cb); - }; - - this.getAllPersonalContacts = function (key, cb) { - return invokeHandlerApi('getAllPersonalContacts', [key], cb); - }; - - this.syncAllPersonalContacts = function (key, syncState, cb) { - return invokeHandlerApi('syncAllPersonalContacts', [key, syncState], cb); - }; - - this.exchangeCancelReqCallback = function (reqId) { - return invokeHandlerApi('exchangeCancelReqCallback', [reqId]); - }; - - this.getAppointments = function (key, startDate, endDate, resCount, cb) { - return invokeHandlerApi('getAppointments', [key, startDate, endDate, resCount], cb); - }; - - this.supportsGetAppointments = function () { - if (!_self.isExtensionRunning()) { - // Assume the feature will be supported once the user installs the Chrome extension - return !!($rootScope.browser && $rootScope.browser.chrome); - } - return true; - }; - - this.supportsExchangeAutodiscover = function () { - return _self.isExtensionRunning(); - }; - - this.supportsExchangeAutodiscoverSvc = function () { - return _self.isExtensionRunning(); - }; - - /****************************************************** - * Headset App Manager APIs - ******************************************************/ - this.launchHeadsetIntegrationApp = function (appId, localizedStrings, cb) { - return invokeHandlerApi('launchHeadsetIntegrationApp', [appId, localizedStrings], cb); - }; - - this.getHeadsetIntegrationAppStatus = function (appId, cb) { - return invokeHandlerApi('getHeadsetIntegrationAppStatus', [appId], cb); - }; - - /****************************************************** - * Internal APIs - ******************************************************/ - this.bringToFront = function (cb) { - if (circuit.isElectron) { - var ipcRenderer = require('electron').ipcRenderer; - ipcRenderer.send('focus-window'); - } else { - return invokeHandlerApi('bringToFront', [], cb); - } - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Initializations - /////////////////////////////////////////////////////////////////////////////////////// - init(); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.ExtensionSvcImpl = ExtensionSvcImpl; - - return circuit; - -})(Circuit); - -// Define external globals for JSHint -/*global RegistrationState */ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var AtcInfoMessage = circuit.Enums.AtcInfoMessage; - var AtcMessage = circuit.Enums.AtcMessage; - var AtcRegistrationState = circuit.Enums.AtcRegistrationState; - var ClientApiHandler = circuit.ClientApiHandlerSingleton; - var Constants = circuit.Constants; - var DefaultAvatars = circuit.DefaultAvatars; - var PhoneNumberFormatter = circuit.PhoneNumberFormatter; - var RoutingOptions = circuit.RoutingOptions; - var UserToUserHandler = circuit.UserToUserHandlerSingleton; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////// - // AtcRegistrationSvc Implementation - /////////////////////////////////////////////////////////////////////////////////////// - function AtcRegistrationSvcImpl($rootScope, $timeout, LogSvc, PubSubSvc, RegistrationSvc, ConversationSvc) { - LogSvc.debug('New Service: AtcRegistrationSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////////////////// - var ATC_REGISTRATION_RETRY_TIMER = 30; // Retry timer in seconds - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _clientApiHandler = ClientApiHandler.getInstance(); - var _userToUserHandler = UserToUserHandler.getInstance(); - - var _atcRegistrationData = {}; - var _atcRegState = AtcRegistrationState.Disconnected; - var _configurationUpdated = false; - var _trunkState = null; // Must be null initially - var _presenceSubscribedTrunkId = null; - var _telephonyTrunkSubscriptions = {}; - var _retryTimer = null; - - // Telephony Data (state and default caller ID) - var _telephonyData = { - state: Constants.TrunkState.DOWN - }; - - var _telephonyConversation = null; - var _reRegister = false; - - var _savedRoutingOption = null; - - // Device type registering with ATC - var _deviceType = Utils.isMobile() ? Constants.DeviceType.MOBILE : - (circuit.isElectron ? Constants.DeviceType.APPLICATION : Constants.DeviceType.WEB); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function setAtcState(newState) { - if (newState !== _atcRegState) { - _atcRegState = newState; - LogSvc.info('[AtcRegistrationSvc]: Publish /atcRegistration/state event. State =', newState); - PubSubSvc.publish('/atcRegistration/state', [newState]); - - if (!!$rootScope.localUser.isATC && newState !== AtcRegistrationState.Registering) { - processTelephonyData(_telephonyData); - } - } - } - - function isPhoneCallsAvailable(data) { - if (data.state === Constants.TrunkState.DOWN || _trunkState === Constants.TrunkState.DOWN) { - return false; - } - return true; - } - - function isTelephonyAvailableForUserUpdated() { - var telephonyAvailable = !!$rootScope.localUser.telephonyAvailable; - var phoneConfigured = !!$rootScope.localUser.callerId || !!$rootScope.localUser.phoneNumber; - return telephonyAvailable !== phoneConfigured; - } - - function retryOnError(err) { - return !!err && (err === Constants.ReturnCode.FAILED_TO_SEND || - err === Constants.ReturnCode.REQUEST_TIMEOUT || err.code === 503 || err.code === 408); - } - - function onAtcRegistrationError(err) { - if (retryOnError(err) && RegistrationSvc.isRegistered() && _atcRegState === AtcRegistrationState.Registering) { - LogSvc.info('[AtcRegistrationSvc]: Retry ATC registratio in ' + ATC_REGISTRATION_RETRY_TIMER + ' seconds'); - _retryTimer = $timeout(function () { - _retryTimer = null; - atcRegister(); - }, ATC_REGISTRATION_RETRY_TIMER * 1000); - } - setAtcState(AtcRegistrationState.Unregistered); - } - - function atcRegister() { - var data = { - content: { - type: 'REGISTER', - phoneNumber: $rootScope.localUser.cstaNumber, - reroutingPhoneNumber: Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber), - onsHash: $rootScope.localUser.onsSipAuthenticationHash || undefined, - ondHash: $rootScope.localUser.ondSipAuthenticationHash || undefined, - routeToDesk: $rootScope.localUser.selectedRoutingOption === RoutingOptions.DeskPhone.name, - tcPool: $rootScope.localUser.associatedTelephonyTrunkGroupId, - pbxCallLog: true, - deviceType: _deviceType, - userId: $rootScope.localUser.userId, - conversationId: _telephonyConversation.convId, - rtcSessionId: _telephonyConversation.rtcSessionId, - lastItemTime: _telephonyConversation.lastItemModificationTime - - }, - destUserId: $rootScope.localUser.associatedTelephonyUserID, - keysToOmitFromLogging: [ - 'content.onsHash', - 'content.ondHash' - ] - }; - - $timeout.cancel(_retryTimer); - _retryTimer = null; - - _atcRegistrationData.phoneNumber = $rootScope.localUser.cstaNumber; - _atcRegistrationData.associatedTelephonyUserID = $rootScope.localUser.associatedTelephonyUserID; - _atcRegistrationData.associatedTelephonyTrunkGroupId = $rootScope.localUser.associatedTelephonyTrunkGroupId; - _atcRegistrationData.reroutingPhoneNumber = Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber); - _atcRegistrationData.onsSipAuthenticationHash = $rootScope.localUser.onsSipAuthenticationHash; - _atcRegistrationData.ondSipAuthenticationHash = $rootScope.localUser.ondSipAuthenticationHash; - _savedRoutingOption = $rootScope.localUser.selectedRoutingOption; - // Initialize to false when registering - $rootScope.localUser.noCallLog = false; - - setAtcState(AtcRegistrationState.Registering); - - LogSvc.info('[AtcRegistrationSvc]: Registering with ATC, phoneNumber: ' + _atcRegistrationData.phoneNumber + ', associatedTelephonyUserID: ' + _atcRegistrationData.associatedTelephonyUserID); - - _userToUserHandler.sendAtcRequest(data, function (err, registerResult) { - $rootScope.$apply(function () { - if (_configurationUpdated && !_telephonyConversation.call) { - LogSvc.info('[AtcRegistrationSvc]: There was a configuration update while registering. Need to register again'); - _configurationUpdated = false; - if (!err && registerResult && !registerResult.err) { - _reRegister = true; - atcUnregister(); - } else { - atcRegister(); - } - return; - } - - if (err || (registerResult && registerResult.err)) { - err = err || registerResult.err; - LogSvc.error('[AtcRegistrationSvc]: Register with ATC failed with error: ', err); - onAtcRegistrationError(err); - return; - } - - // Successfully registered with the platform - if (registerResult && registerResult.configurationData) { - LogSvc.info('[AtcRegistrationSvc]: Received ATC configuration data: ', registerResult.configurationData); - _atcRegistrationData.configurationData = registerResult.configurationData; - _atcRegistrationData.reroutingPhoneNumber = registerResult.configurationData.cell; - _atcRegistrationData.capabilities = registerResult.capabilities; - $rootScope.localUser.reroutingPhoneNumber = PhoneNumberFormatter.format(registerResult.configurationData.cell); - $rootScope.localUser.vmNumber = registerResult.configurationData.vm; - $rootScope.localUser.isOSV = registerResult.configurationData.pbx && registerResult.configurationData.pbx === 'OSV'; - $rootScope.localUser.PBXCallLogSupported = _atcRegistrationData.capabilities && _atcRegistrationData.capabilities.includes(Constants.AtcCapabilities.PBX_CALL_LOG); - setAtcState(AtcRegistrationState.Registered); - if ($rootScope.localUser.PBXCallLogSupported && !$rootScope.localUser.noCallLog) { - sendEnablePBXCallLogToATC(true); - } - LogSvc.debug('[AtcRegistrationSvc]: Registration refresh will be handled by access server'); - - } else { - setAtcState(AtcRegistrationState.Unregistered); - } - }); - }); - } - - function atcUnregister() { - LogSvc.info('[AtcRegistrationSvc]: Unregister with ATC'); - - if (_atcRegState === AtcRegistrationState.Unregistered || _atcRegState === AtcRegistrationState.Disconnected) { - LogSvc.debug('[AtcRegistrationSvc]: The client is not registered with ATC'); - return; - } - - var data = { - content: { - type: 'UNREGISTER', - phoneNumber: _atcRegistrationData.phoneNumber - }, - destUserId: _atcRegistrationData.associatedTelephonyUserID - }; - setAtcState(AtcRegistrationState.Unregistered); - _atcRegistrationData.associatedTelephonyUserID = ''; - - _userToUserHandler.sendAtcRequest(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[AtcRegistrationSvc]: Unregister with ATC failed with: ', err); - } else { - LogSvc.info('[AtcRegistrationSvc]: Unregistered successfully with ATC'); - } - if (_reRegister) { - atcRegister(); - _reRegister = false; - } - }); - }); - } - - function handlePresenceState(newTrunkState) { - if (newTrunkState !== _trunkState) { - $rootScope.$apply(function () { - _trunkState = newTrunkState; - LogSvc.debug('[AtcRegistrationSvc]: Set trunk state to ', _trunkState); - var data = Utils.shallowCopy(_telephonyData); - data.state = newTrunkState; - handleTelephonyDataChange(data); - }); - } - } - - function handleTelephonyDataChange(data) { - if (!data) { - return; - } - - if ($rootScope.localUser.isATC) { - if (data.state === Constants.TrunkState.DOWN) { - atcUnregister(); - } else if (_telephonyData.state === Constants.TrunkState.DOWN && data.state === Constants.TrunkState.UP) { - _telephonyData = data; - if (_atcRegState === AtcRegistrationState.Registering) { - // We need to wait for the previous registration to complete before registering again - LogSvc.debug('[AtcRegistrationSvc]: Client is registering and configuration has changed. Wait...'); - _configurationUpdated = true; - return; - } - atcRegister(); - return; - } - } - processTelephonyData(data); - } - - function processTelephonyData(data) { - if (!data) { - return; - } - - _telephonyData = data; - - if ($rootScope.localUser.isATC) { - _telephonyData.state = (_atcRegState === AtcRegistrationState.Registered) ? Constants.TrunkState.UP : Constants.TrunkState.DOWN; - _telephonyData.defaultCallerId = null; - _telephonyData.isGTCEnabled = true; - } else { - _telephonyData.defaultCallerId = _telephonyData.defaultCallerId || null; - } - _telephonyData.telephonyAvailableForTenant = !!data.isGTCEnabled; - _telephonyData.telephonyAvailableForUser = !!(data.isGTCEnabled && (!!$rootScope.localUser.callerId || !!_telephonyData.defaultCallerId)); - _telephonyData.conferenceDialOutAvailable = _telephonyData.telephonyAvailableForTenant && isPhoneCallsAvailable(_telephonyData); - _telephonyData.phoneCallsAvailable = _telephonyData.telephonyAvailableForUser && isPhoneCallsAvailable(_telephonyData); - - LogSvc.info('[AtcRegistrationSvc]: Publish /telephony/data event. data = ', _telephonyData); - PubSubSvc.publish('/telephony/data', [_telephonyData]); - - if (_telephonyData.state === Constants.TrunkState.DOWN || _telephonyData.state === Constants.TrunkState.UP) { - // Update the Phone Calls avatar according to the telephony data - ConversationSvc.getTelephonyConversationPromise().then(function (conv) { - if (conv) { - if (!isPhoneCallsAvailable(_telephonyData)) { - conv.avatar = DefaultAvatars.TELEPHONY_DISABLED; - } else { - conv.avatar = DefaultAvatars.TELEPHONY; - } - _telephonyConversation = conv; - } - }) - .catch(function () { - LogSvc.debug('[AtcRegistrationSvc]: No telephony conversation found'); - }); - } - } - - function getTelephonyData(cb) { - LogSvc.info('[AtcRegistrationSvc]: Getting telephony data'); - - if ($rootScope.telephonyEnabled) { - _clientApiHandler.getTelephonyData(function (err, data) { - $rootScope.$apply(function () { - if (!err) { - handleTelephonyDataChange(data); - cb && cb(null, _telephonyData); - } else if (err !== Constants.ReturnCode.NO_RESULT) { - LogSvc.error('[AtcRegistrationSvc]: Error getting telephony data: ', err); - cb && cb(err); - } - }); - }); - } else { - processTelephonyData({ - state: Constants.TrunkState.DOWN - }); - cb && cb(null, _telephonyData); - } - } - - function checkLocalUserConfiguration(loadComplete) { - if (_presenceSubscribedTrunkId && _presenceSubscribedTrunkId !== $rootScope.localUser.associatedTelephonyUserID) { - if (!_telephonyTrunkSubscriptions[_presenceSubscribedTrunkId]) { - _clientApiHandler.unsubscribePresence([_presenceSubscribedTrunkId]); - } - _presenceSubscribedTrunkId = null; - } - if ($rootScope.localUser.associatedTelephonyUserID && _presenceSubscribedTrunkId !== $rootScope.localUser.associatedTelephonyUserID) { - _presenceSubscribedTrunkId = $rootScope.localUser.associatedTelephonyUserID; - - if (!$rootScope.localUser.isATC || !_telephonyTrunkSubscriptions[_presenceSubscribedTrunkId]) { - // Subscribe to presence (Do this even if we are already subscribed so we can get the current state) - _clientApiHandler.subscribePresence([$rootScope.localUser.associatedTelephonyUserID], function (err, states) { - if (err) { - LogSvc.error('[AtcRegistrationSvc]: Unable to subscribe to associated TC presence'); - _presenceSubscribedTrunkId = null; - return; - } - if (states && states.length > 0 && !$rootScope.localUser.isATC) { - var newTrunkState = (states[0].state === Constants.PresenceState.AVAILABLE ? Constants.TrunkState.UP : Constants.TrunkState.DOWN); - handlePresenceState(newTrunkState); - } - }); - } - } - if (!$rootScope.localUser.isATC || !$rootScope.localUser.cstaNumber) { - if (_atcRegState === AtcRegistrationState.Registered) { - LogSvc.debug('[AtcRegistrationSvc]: Cannot register without an associated ATC or assigned phoneNumber'); - atcUnregister(); - getTelephonyData(); - } else if (loadComplete || isTelephonyAvailableForUserUpdated()) { - // Get the telephony data for non-ATC users - getTelephonyData(); - } - setAtcState(AtcRegistrationState.Disconnected); - return; - } - - if (($rootScope.localUser.associatedTelephonyUserID === _atcRegistrationData.associatedTelephonyUserID) && - ($rootScope.localUser.cstaNumber === _atcRegistrationData.phoneNumber) && - (Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber) === _atcRegistrationData.reroutingPhoneNumber) && - ($rootScope.localUser.onsSipAuthenticationHash === _atcRegistrationData.onsSipAuthenticationHash) && - ($rootScope.localUser.ondSipAuthenticationHash === _atcRegistrationData.ondSipAuthenticationHash) && - (_atcRegState === AtcRegistrationState.Registered || _atcRegState === AtcRegistrationState.Registering)) { - LogSvc.debug('[AtcRegistrationSvc]: Client is already registered or registering with ATC and the associatedTelephonyUserID is the same'); - return; - } - - if (_atcRegState === AtcRegistrationState.Registering) { - // We need to wait for the previous registration to complete before registering again - LogSvc.debug('[AtcRegistrationSvc]: Client is registering and configuration has changed. Wait...'); - _configurationUpdated = true; - return; - } - - if (_atcRegState === AtcRegistrationState.Registered) { - LogSvc.debug('[AtcRegistrationSvc]: Client is already registered and configuration has changed.'); - if (!_telephonyConversation.call) { - _reRegister = true; - atcUnregister(); - } else { - LogSvc.debug('[AtcRegistrationSvc]: There is an active telephony call, wait...'); - _configurationUpdated = true; - return; - } - } - - if (!_reRegister) { - // Make sure we have the telephony conversation - if (!_telephonyConversation) { - LogSvc.debug('[AtcRegistrationSvc]: Get telephony conversation before proceeding with ATC register'); - ConversationSvc.getTelephonyConversationPromise().then(function (conv) { - _telephonyConversation = conv; - LogSvc.debug('[AtcRegistrationSvc]: Got telephony conversation. Proceed with ATC register.'); - checkLocalUserConfiguration(); - }); - - if (!loadComplete && isTelephonyAvailableForUserUpdated()) { - // Get the telephony data for ATC users - getTelephonyData(); - } - return; - } - - atcRegister(); - } - - } - - function sendRouteToDeskInfoToAtc() { - if (_atcRegState === AtcRegistrationState.Registered) { - var status = $rootScope.localUser.selectedRoutingOption === RoutingOptions.DeskPhone.name; - LogSvc.debug('[AtcRegistrationSvc]: Send SET_ROUTE_TO_DESK event to ATC. status = '); - - var data = { - content: { - type: AtcMessage.INFO, - phoneNumber: $rootScope.localUser.cstaNumber, - info: { - type: AtcInfoMessage.SET_ROUTE_TO_DESK, - status: status - } - }, - destUserId: $rootScope.localUser.associatedTelephonyUserID - }; - // Send request - _userToUserHandler.sendAtcRequest(data); - } - } - - function sendEnablePBXCallLogToATC(status) { - var data = { - content: { - type: AtcMessage.INFO, - phoneNumber: $rootScope.localUser.cstaNumber, - info: { - type: AtcInfoMessage.ENABLE_PBX_CALL_LOG, - status: status - } - }, - destUserId: $rootScope.localUser.associatedTelephonyUserID - }; - // Send request - _userToUserHandler.sendAtcRequest(data); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - PubSubSvc.subscribe('/localUser/update', function () { - if (RegistrationSvc.isRegistered() && ConversationSvc.isConversationsLoadComplete()) { - LogSvc.debug('[AtcRegistrationSvc]: Received /localUser/update event'); - checkLocalUserConfiguration(); - } - }); - - PubSubSvc.subscribe('/conversations/loadComplete', function () { - LogSvc.debug('[AtcRegistrationSvc]: Received /conversations/loadComplete event'); - checkLocalUserConfiguration(true); - }); - - PubSubSvc.subscribe('/registration/state', function (state) { - if (!ConversationSvc.isConversationsLoadComplete()) { - return; - } - - LogSvc.debug('[AtcRegistrationSvc]: Received /registration/state event'); - if (state === RegistrationState.LoggingOut || state === RegistrationState.Reconnecting || state === RegistrationState.Disconnected) { - // Cancel retry timer (if running) - $timeout.cancel(_retryTimer); - _retryTimer = null; - - // Allow others to process the /registration/state event before changing the telephony state - $timeout($rootScope.localUser.isATC ? atcUnregister : function () { - _telephonyData.state = Constants.TrunkState.DOWN; - _presenceSubscribedTrunkId = null; - processTelephonyData(_telephonyData); - }); - } - }); - - PubSubSvc.subscribe('/conversation/update', function (conversation) { - if (conversation.isTelephonyConv) { - LogSvc.debug('[AtcRegistrationSvc]: Received /conversation/update event'); - _telephonyConversation = conversation; - if (!_configurationUpdated) { - return; - } - - if (conversation.call) { - LogSvc.debug('[AtcRegistrationSvc]: There is still an active telephony call, wait...'); - } else if (_atcRegState !== AtcRegistrationState.Registering) { - _configurationUpdated = false; - _reRegister = true; - atcUnregister(); - } - } - }); - - PubSubSvc.subscribe('/user/settings/update', function () { - if (!ConversationSvc.isConversationsLoadComplete()) { - return; - } - - LogSvc.debug('[AtcRegistrationSvc]: Received /user/settings/update event'); - - if (_savedRoutingOption !== $rootScope.localUser.selectedRoutingOption) { - _savedRoutingOption = $rootScope.localUser.selectedRoutingOption; - sendRouteToDeskInfoToAtc(); - } - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Client API Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - _clientApiHandler.on('User.TELEPHONY_DATA', function (evt) { - $rootScope.$apply(function () { - try { - LogSvc.debug('[AtcRegistrationSvc]: Received User.TELEPHONY_DATA with data: ', evt.data); - if (evt.data) { - evt.data.isGTCEnabled = true; - handleTelephonyDataChange(evt.data); - } - } catch (e) { - LogSvc.error('[AtcRegistrationSvc]: Exception handling User.TELEPHONY_DATA event.', e); - } - }); - }); - - _clientApiHandler.on('User.USER_PRESENCE_CHANGE', function (data) { - if (!data.userId) { - // No need to process this event - return; - } - if (data.userId === _presenceSubscribedTrunkId) { - LogSvc.debug('[AtcRegistrationSvc]: Received User.USER_PRESENCE_CHANGE for associated TC. newState = ', data.newState); - var newTrunkState = (data.newState.state === Constants.PresenceState.AVAILABLE ? Constants.TrunkState.UP : Constants.TrunkState.DOWN); - handlePresenceState(newTrunkState); - } - if (_telephonyTrunkSubscriptions[data.userId]) { - LogSvc.info('[AtcRegistrationSvc]: Publish /trunk/update event. newState = ', data.newState); - PubSubSvc.publish('/trunk/update', [data.newState]); - } - }); - - _clientApiHandler.on('User.ATC_REFRESH_FAILED', function () { - LogSvc.error('[AtcRegistrationSvc]: Received User.ATC_REFRESH_FAILED event.'); - if (_atcRegState === AtcRegistrationState.Registered) { - if (_telephonyConversation.call && !_telephonyConversation.call.isRemote) { - LogSvc.info('[AtcRegistrationSvc]: There is an active telephony call, ignoring ATC re-register for now. If error persists on server another event will come later.'); - } else { - $rootScope.$apply(function () { - LogSvc.info('[AtcRegistrationSvc]: New registration must be triggered.'); - _reRegister = true; - atcUnregister(); - }); - } - } else { - // Other processing has been already started on client, so just ignore - LogSvc.info('[AtcRegistrationSvc]: Client is not registered, ignoring failed refresh on server.'); - } - }); - - _userToUserHandler.on('ATC.INFO', function (data) { - LogSvc.debug('[AtcRegistrationSvc]: Received ATC.INFO with data = ', data); - if ($rootScope.localUser && data.hasOwnProperty('pbxCallLogStatus')) { - $rootScope.localUser.noCallLog = !!data.pbxCallLogStatus; - } - }); - - _userToUserHandler.on('ATC.START_REASSIGNMENT', function () { - LogSvc.debug('[AtcRegistrationSvc]: Received ATC.START_REASSIGNMENT event'); - _clientApiHandler.renewAssociatedTelephonyUser($rootScope.localUser.associatedTelephonyUserID, function (err) { - if (err) { - LogSvc.error('[AtcRegistrationSvc]: Failed to reassign telephony user. ', err); - } - }); - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Functions - /////////////////////////////////////////////////////////////////////////////////////// - - // Used by SDK to initialize service - this.initAtcForSdk = function () { - LogSvc.debug('[AtcRegistrationSvc]: initAtcForSdk'); - _deviceType = Constants.DeviceType.SDK; - checkLocalUserConfiguration(true); - }; - - this.atcState = function () { - return _atcRegState; - }; - - this.getAtcRegistrationData = function () { - return _atcRegistrationData.configurationData; - }; - - this.atcUnregister = atcUnregister; - - /** - * Retrieve the telephony data (state and default CallerID) - * @returns {undefined} - */ - this.getTelephonyData = function (cb) { - cb = cb || function () {}; - if (_atcRegState === AtcRegistrationState.Registering) { - cb('ATC Registering'); - return; - } - if (!Utils.isEmptyObject(_telephonyData)) { - cb(null, _telephonyData); - return; - } - getTelephonyData(cb); - }; - - this.isExtendedAlertingSupported = function () { - if (_atcRegistrationData && _atcRegistrationData.capabilities) { - return _atcRegistrationData.capabilities.includes(Constants.AtcCapabilities.EXTENDED_ALERTING); - } - return false; - }; - - this.isClearConnectionBusySupported = function () { - if (_atcRegistrationData && _atcRegistrationData.capabilities) { - return _atcRegistrationData.capabilities.includes(Constants.AtcCapabilities.CLEAR_CONNECTION_BUSY); - } - }; - - this.subscribeConnectorPresence = function (ids, cb) { - ids = ids.filter(function (id) { return !_telephonyTrunkSubscriptions[id]; }); - if (ids.length) { - LogSvc.debug('[AtcRegistrationSvc]: Subscribe for connectors presence. ', ids); - _clientApiHandler.subscribePresence(ids, function (err, states) { - if (err) { - LogSvc.error('[AtcRegistrationSvc]: Failed to subscribe to connectors presence. ', err); - cb && cb(err); - } else if (states) { - LogSvc.debug('[AtcRegistrationSvc]: Successfully subscribed for connectors presence'); - states.forEach(function (userPresenceState) { - _telephonyTrunkSubscriptions[userPresenceState.userId] = true; - }); - cb && cb(null, states); - } - }); - } - }; - - this.unsubscribeConnectorPresence = function (ids, cb) { - if (ids) { - ids = ids.filter(function (id) { - if (_telephonyTrunkSubscriptions[id]) { - delete _telephonyTrunkSubscriptions[id]; - return id !== _presenceSubscribedTrunkId; - } - }); - } else { - ids = Object.keys(_telephonyTrunkSubscriptions).filter(function (id) { - return id !== _presenceSubscribedTrunkId; - }); - _telephonyTrunkSubscriptions = {}; - } - if (ids.length) { - LogSvc.debug('[AtcRegistrationSvc]: Unsubscribe for connectors presence. ', ids); - _clientApiHandler.unsubscribePresence(ids, cb); - } - }; - - this.isPBXCallLogSupported = function () { - if (_atcRegistrationData && _atcRegistrationData.capabilities) { - return _atcRegistrationData.capabilities.includes(Constants.AtcCapabilities.PBX_CALL_LOG); - } - return false; - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.AtcRegistrationSvcImpl = AtcRegistrationSvcImpl; - - return circuit; - -})(Circuit); - -// Define global variables for JSHint -/*global DeviceStatistics*/ - -var Circuit = (function (circuit) { - 'use strict'; - // Imports - - var ClientApiHandler = circuit.ClientApiHandlerSingleton; - var Constants = circuit.Constants; - var Proto = circuit.Proto; - var Utils = circuit.Utils; - - /** - * Creates a new DeviceDiagnosticSvcImpl. - * - * @class - * @classdesc Device Diagnostics Service for device analytics purposes - */ - function DeviceDiagnosticSvcImpl($rootScope, $timeout, LogSvc, PubSubSvc, LocalStoreSvc) { - LogSvc.debug('New Service: DeviceDiagnosticSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var JOIN_DELAY_TIMEOUT = 3000; - - var MAX_CACHED_RTC_CLIENT_SIZE = 60000; // Maximum size of cached diagnostics data in local storage - var TRIMMED_RTC_CLIENT_SIZE = 50000; // Max size after data is trimmed - - var _clientApiHandler = ClientApiHandler.getInstance(); - var _cachedClientInfo = []; - var _supportsOfflineFailures = !!(LocalStoreSvc && !$rootScope.isSessionGuest); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////////////////// - function trimAndStoreData() { - if (!_supportsOfflineFailures) { - return; - } - if (_cachedClientInfo.length === 0) { - LocalStoreSvc.removeItem(LocalStoreSvc.keys.RTC_CLIENT_INFOS); - return; - } - var serializedData = JSON.stringify(_cachedClientInfo); - if (serializedData.length > MAX_CACHED_RTC_CLIENT_SIZE) { - LogSvc.warn('[DeviceDiagnosticSvc]: Stored device diagnostics exceeded max size. Discard old data.'); - var discardedData = ''; - var discardSize = serializedData.length - TRIMMED_RTC_CLIENT_SIZE; - _cachedClientInfo.some(function (oldInfo, idx) { - discardedData += JSON.stringify(oldInfo); - if (discardedData.length > discardSize) { - LogSvc.warn('[DeviceDiagnosticSvc]: Number of discarded records: ', idx + 1); - _cachedClientInfo.splice(0, idx + 1); - serializedData = JSON.stringify(_cachedClientInfo); - return true; - } - }); - } - LocalStoreSvc.setString(LocalStoreSvc.keys.RTC_CLIENT_INFOS, serializedData); - } - - function init() { - if (_supportsOfflineFailures) { - _cachedClientInfo = LocalStoreSvc.getObjectSync(LocalStoreSvc.keys.RTC_CLIENT_INFOS) || []; - if (!Array.isArray(_cachedClientInfo)) { - _cachedClientInfo = []; - } - trimAndStoreData(); - } - } - - function cancelJoinDelayTimer(diagnostics) { - if (diagnostics && diagnostics.joinDelayTimer) { - $timeout.cancel(diagnostics.joinDelayTimer); - diagnostics.joinDelayTimer = null; - } - } - - function startDiagnostics(call) { - if (call && !call.isRemote) { - LogSvc.info('[DeviceDiagnosticSvc]: Start diagnostics collection for callId = ', call.callId); - call.deviceDiagnostics = call.deviceDiagnostics || { - data: { - actionInfo: [] - } - }; - } - } - - function startJoinDelayTimer(call) { - if (!call || !call.deviceDiagnostics) { - return; - } - LogSvc.debug('[DeviceDiagnosticSvc]: Start join delay timer for callId = ', call.callId); - - var diagnostics = call.deviceDiagnostics; - - cancelJoinDelayTimer(diagnostics); - diagnostics.joinDelayTimer = $timeout(function () { - diagnostics.joinDelayTimer = null; - onJoinDelay(call); - }, JOIN_DELAY_TIMEOUT); - - diagnostics.joinDelayed = false; - diagnostics.waitingToSend = false; - diagnostics.iceGatheringFinished = false; - - call.deviceDiagnostics = diagnostics; - } - - function createActionInfo(call, actionType) { - LogSvc.debug('[DeviceDiagnosticSvc]: Create action info ', actionType); - - if (!call || !call.deviceDiagnostics || !actionType) { - return; - } - - var info = { - actionType: actionType - }; - - switch (actionType) { - case Constants.RtcDiagnosticsAction.SDP_ANSWER: - case Constants.RtcDiagnosticsAction.SDP_CONNECTED: - case Constants.RtcDiagnosticsAction.REMOTE_ICE_CANDIDATES: - info.type = Constants.RtcActionInfoType.EVENT; - info.timestampEvent = Date.now(); - break; - - default: - info.type = Constants.RtcActionInfoType.REQUEST_RESPONSE; - info.timestampRequest = Date.now(); - break; - } - - call.deviceDiagnostics.data.actionInfo.push(info); - return info; - } - - function hasPendingActionInfo(call) { - // Check if all action infos are complete. - if (!call || !call.deviceDiagnostics) { - return false; - } - var pending = call.deviceDiagnostics.data.actionInfo.some(function (actionInfo) { - return !actionInfo.complete; - }); - return pending || !call.deviceDiagnostics.iceGatheringFinished; - } - - function finishActionInfo(call, info) { - if (!info) { - return; - } - - LogSvc.debug('[DeviceDiagnosticSvc]: Finish action info ', info.actionType); - - info.complete = true; - if (info.type === Constants.RtcActionInfoType.REQUEST_RESPONSE) { - info.timestampResponse = Date.now(); - } - - if (call && call.deviceDiagnostics) { - if (info.isEndOfCandidates) { - call.deviceDiagnostics.iceGatheringFinished = true; - LogSvc.debug('[DeviceDiagnosticSvc]: Ice gathering finished'); - } - // Device Diagnostics won't be sent until collection is complete, or at least on terminate - if (call.deviceDiagnostics.waitingToSend && !hasPendingActionInfo(call)) { - forceFinishDeviceDiagnostics(call); - } - } - } - - function forceFinishDeviceDiagnostics(call) { - if (!call || !call.deviceDiagnostics) { - return; - } - - LogSvc.debug('[DeviceDiagnosticSvc]: Force finish device diagnostics'); - var diagnostics = call.deviceDiagnostics; - cancelJoinDelayTimer(diagnostics); - - if (diagnostics.joinDelayed) { - sendDeviceDiagnostics(call); - } - clearDeviceDiagnostics(call); - } - - function onJoinDelay(call) { - if (!call || !call.deviceDiagnostics) { - return; - } - - var diagnostics = call.deviceDiagnostics; - - LogSvc.debug('[DeviceDiagnosticSvc]: Join action delayed, preparing device diagnostics'); - cancelJoinDelayTimer(diagnostics); - diagnostics.joinDelayed = true; - - if (typeof DeviceStatistics !== 'undefined') { - var deviceStats = DeviceStatistics.getStatistics(); - - LogSvc.info('[DeviceDiagnosticSvc]: Received native device diagnostics: ', deviceStats); - diagnostics.data.usedRAMApp = deviceStats.usedRAMApp; - diagnostics.data.availableRAMApp = deviceStats.availableRAMApp; - diagnostics.data.usedRAMDevice = deviceStats.usedRAMDevice; - diagnostics.data.availableRAMDevice = deviceStats.availableRAMDevice; - diagnostics.data.virtualMemory = deviceStats.virtualMemory; - diagnostics.data.usagePerCore = deviceStats.usagePerCore; - diagnostics.data.numberOfCores = deviceStats.numberOfCores; - diagnostics.data.networkType = deviceStats.networkType; - } - } - - function finishDeviceDiagnostics(call) { - if (!call || !call.deviceDiagnostics) { - return; - } - LogSvc.debug('[DeviceDiagnosticSvc]: Finish device diagnostics'); - - var diagnostics = call.deviceDiagnostics; - cancelJoinDelayTimer(diagnostics); - - if (diagnostics.joinDelayed) { - if (!hasPendingActionInfo(call)) { - sendDeviceDiagnostics(call); - } else { - diagnostics.waitingToSend = true; - LogSvc.debug('[DeviceDiagnosticSvc]: Send device diagnostic delayed'); - } - } else { - clearDeviceDiagnostics(call); - } - } - - function clearDeviceDiagnostics(call) { - if (!call || !call.deviceDiagnostics) { - return; - } - - LogSvc.debug('[DeviceDiagnosticSvc]: Clear device diagnostics'); - cancelJoinDelayTimer(call.deviceDiagnostics); - delete call.deviceDiagnostics; - } - - function storeClientInfo(info) { - if (_supportsOfflineFailures) { - LogSvc.debug('[DeviceDiagnosticSvc]: Store device diagnostics to send when client reconnects'); - _cachedClientInfo.push(info); - trimAndStoreData(); - } - } - - function sendDeviceDiagnostics(call) { - if (!call || !call.deviceDiagnostics) { - LogSvc.debug('[DeviceDiagnosticSvc]: sendDeviceDiagnostics - Nothing to send'); - return; - } - var clientInfo = { - userId: $rootScope.localUser.userId, - convId: call.convId, - clientInfoType: Constants.RtcClientInfoType.DEVICE_DIAGNOSTICS, - reason: Constants.RtcClientInfoReason.JOIN_DELAY, - timestamp: Date.now(), - sessionId: call.callId, - deviceDiagnostics: Utils.shallowCopy(call.deviceDiagnostics.data) - }; - - if (call && call.instanceId) { - clientInfo.instanceId = call.instanceId; - } - - clearDeviceDiagnostics(call); - - LogSvc.debug('[DeviceDiagnosticSvc]: Sending device diagnostics...'); - _clientApiHandler.sendClientInfo([clientInfo], function (err) { - if (err) { - LogSvc.debug('[DeviceDiagnosticSvc]: Failed to send device diagnostics. ', err); - if (Proto.isOfflineFailure(err)) { - storeClientInfo(clientInfo); - } - } - }); - } - - function sendOfflineJoinFailures() { - if (_cachedClientInfo.length > 0) { - var clientInfos = _cachedClientInfo; - _cachedClientInfo = []; - LocalStoreSvc.removeItem(LocalStoreSvc.keys.RTC_CLIENT_INFOS); - - LogSvc.info('[DeviceDiagnosticSvc]: Send offline join failures to backend'); - _clientApiHandler.sendClientInfo(clientInfos, function (err) { - if (err) { - LogSvc.error('[DeviceDiagnosticSvc]: Failed to send offline join failures. ', err); - if (Proto.isOfflineFailure(err)) { - // The client is offline. Save the data again. - Array.prototype.unshift.apply(_cachedClientInfo, clientInfos); - trimAndStoreData(); - } - } else { - LogSvc.debug('[DeviceDiagnosticSvc]: Sent offline join failures'); - } - }); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - if (_supportsOfflineFailures) { - PubSubSvc.subscribe('/conversations/loadComplete', function () { - LogSvc.debug('[DeviceDiagnosticSvc]: Received /conversations/loadComplete event'); - sendOfflineJoinFailures(); - }); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - this.startDiagnostics = startDiagnostics; - - this.startJoinDelayTimer = startJoinDelayTimer; - - this.createActionInfo = createActionInfo; - - this.finishActionInfo = finishActionInfo; - - this.finishDeviceDiagnostics = finishDeviceDiagnostics; - - this.forceFinishDeviceDiagnostics = forceFinishDeviceDiagnostics; - - this.storeClientInfo = storeClientInfo; - - /////////////////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////////////////// - init(); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.DeviceDiagnosticSvcImpl = DeviceDiagnosticSvcImpl; - - return circuit; - -})(Circuit); - -// Define global variables for JSHint -/*global RegistrationState, require*/ - -var Circuit = (function (circuit) { - 'use strict'; - - var AtcCallInfo = circuit.AtcCallInfo; - var BusyHandlingOptions = circuit.BusyHandlingOptions; - var ClientApiHandler = circuit.ClientApiHandlerSingleton; - var Constants = circuit.Constants; - var Enums = circuit.Enums; - var LocalCall = circuit.LocalCall; - var Proto = circuit.Proto; - var RemoteCall = circuit.RemoteCall; - var RoutingOptions = circuit.RoutingOptions; - var RtcParticipant = circuit.RtcParticipant; - var RtcSessionController = circuit.RtcSessionController; - var sdpParser = circuit.sdpParser; - var UserToUserHandler = circuit.UserToUserHandlerSingleton; - var Utils = circuit.Utils; - var WebRTCAdapter = circuit.WebRTCAdapter; - var ScreenSharingController = circuit.ScreenSharingController; - - /** - * CircuitCallControlSvc Implementation. A Call control service to control and manage calls. - * - * @class - */ - function CircuitCallControlSvcImpl( - $rootScope, - $timeout, - $interval, - $window, - $q, - LogSvc, - PubSubSvc, - UserSvc, - ConversationSvc, - NotificationSvc, - InstrumentationSvc, - DeviceDiagnosticSvc) { - - // The following imports need to be defined inside CircuitCallControlSvcImpl due to JS-SDK - var Conversation = circuit.Conversation; - var UserProfile = circuit.UserProfile; - - LogSvc.debug('New Service: CircuitCallControlSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _isMobile = Utils.isMobile(); - var _isIOS = ($window.navigator.platform === 'iOS'); - var _isDotNet = ($window.navigator.platform === 'dotnet'); - - var CLOSE_CALL_DELAY = 3200; // Set the delay a little over 3 seconds to give enough time for 3 failed tones - var TURN_TTL_RECOVER_TIME = 60000; // 60 seconds - var ATC_HANDOVER_TIME = 6000; // 6 seconds - var REVERSE_LOOKUP_MAX_TIME = 2000; // 2 seconds - var MIN_TIME_CALL_ESTABLISHED = 5000; // Update lastCallTime if call lasts more than this value (the same as on the backend side) - var ATC_PICKUP_TIMER = 3000; // 3 seconds - - var _that = this; - - var _clientApiHandler = ClientApiHandler.getInstance(); - var _userToUserHandler = UserToUserHandler.getInstance(); - - // All calls, local and remote - var _calls = []; - - var _primaryLocalCall = null; - var _secondaryLocalCall = null; - // Calls established on other clients - var _activeRemoteCalls = []; - // Last ended call. Save the last ended call until a new call exists. Used for move calls scenarios. - var _lastEndedCall = null; - var _wasMuted = false; - - var _disableRemoteVideoByDefault = false; - - var _sessionHash = {}; - - // Incoming alerting calls, we only support one for now - var _incomingCalls = []; - - var _activeSpeakerPromise = null; - - var _clientDiagnosticsDisabled = false; - var _conversationLoaded = false; - var _pendingInvites = {}; - - var _handoverTimer = null; - - var _turnCredentials = null; - - var _pickupTimer = null; - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function getConversation(convId) { - return ConversationSvc.getConversationAsGuestById(convId) || - ConversationSvc.getConversationFromCache(convId); - } - - function updateTurnExpireTime() { - if (_turnCredentials) { - var ttl = (_turnCredentials.ttl || 0) * 1000; // Convert to ms - ttl = Math.max(ttl - TURN_TTL_RECOVER_TIME, 0); - _turnCredentials.expirationTimestamp = Date.now() + ttl; - } - } - - function getTurnCredentials(call) { - return new $q(function (resolve, reject) { - var expirationTimestamp = (_turnCredentials && _turnCredentials.expirationTimestamp) || 0; - if (Date.now() < expirationTimestamp) { - var expirationDate = new Date(expirationTimestamp); - LogSvc.debug('[CircuitCallControlSvc]: Existing credentials are still valid. Expiration: ' + expirationDate); - resolve(_turnCredentials); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: Get new TURN credentials'); - var renewTurnCredentialsInfo = DeviceDiagnosticSvc.createActionInfo(call, Constants.RtcDiagnosticsAction.RENEW_TURN_CREDENTIALS); - _clientApiHandler.renewTurnCredentials(call.callId, function (err, servers) { - $rootScope.$apply(function () { - if (renewTurnCredentialsInfo) { - renewTurnCredentialsInfo.data = JSON.stringify(servers); - DeviceDiagnosticSvc.finishActionInfo(call, renewTurnCredentialsInfo); - } - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error renewing TURN credentials.', err); - reject(err); - return; - } - if (!servers || !servers.length) { - LogSvc.error('[CircuitCallControlSvc]: Error renewing TURN credentials. No server(s) available.'); - reject('No TURN server(s) available'); - return; - } - servers = servers[0]; - LogSvc.info('[CircuitCallControlSvc]: Received new TURN credentials=', servers.turnServer); - _turnCredentials = servers; - updateTurnExpireTime(); - resolve(_turnCredentials); - }); - }); - }); - } - - function prepareSession(conv, localCall, options) { - return new $q(function (resolve, reject) { - var prepareSessionData = { - convId: conv.convId, - rtcSessionId: localCall.callId, - ownerId: conv.creatorId, - mediaNode: localCall.getMediaNode(), - isTelephonyConversation: conv.isTelephonyConv, - replaces: options.replaces && options.replaces.callId - }; - - DeviceDiagnosticSvc.startDiagnostics(localCall); - // Only start the join delay timer for group call here, for direct calls we start it in sdpAnswer or answerRtcCall. - if (!localCall.isDirect) { - DeviceDiagnosticSvc.startJoinDelayTimer(localCall); - } - - - var prepareActionInfo = DeviceDiagnosticSvc.createActionInfo(localCall, Constants.RtcDiagnosticsAction.PREPARE); - - _clientApiHandler.prepareSession(prepareSessionData, function (err, servers, newRtcSessionId) { - $rootScope.$apply(function () { - if (prepareActionInfo) { - prepareActionInfo.data = err ? err.toString() : JSON.stringify(servers); - DeviceDiagnosticSvc.finishActionInfo(localCall, prepareActionInfo); - } - - if (err) { - if (err === Constants.ErrorCode.RTC_CONCURRENT_INCOMING_CALL) { - LogSvc.debug('[CircuitCallControlSvc]: Concurrent incoming call, aborting own attempt.'); - } else if (err === Constants.ErrorCode.RTC_NO_MEDIA_NODES_AVAILABLE) { - LogSvc.error('[CircuitCallControlSvc]: No media nodes available, call not possible.'); - } else if (err === Constants.ErrorCode.RTC_MEDIA_NODE_UNREACHABLE) { - LogSvc.error('[CircuitCallControlSvc]: Specified media node is not reachable, call not possible.'); - } else { - LogSvc.error('[CircuitCallControlSvc]: Error retrieving start session data. ', err); - } - - reject(err); - return; - } - - if (!servers || !servers.length) { - LogSvc.error('[CircuitCallControlSvc]: Error getting TURN credentials. No server(s) available.'); - reject('No TURN server(s) available'); - return; - } - - LogSvc.info('[CircuitCallControlSvc]: Received new TURN credentials'); - servers = servers[0]; - _turnCredentials = servers; - updateTurnExpireTime(); - - if (newRtcSessionId) { - localCall.setCallIdForTelephony(newRtcSessionId); - } - resolve(_turnCredentials); - }); - }); - }); - } - - function getJoinErrorText(error) { - if (error) { - if (error.startsWith('res_')) { - return error; - } - if (error === Constants.ErrorCode.PERMISSION_DENIED) { - return 'res_JoinRTCSessionFailedPermission'; - } - if (error === Constants.SdpFailedCause.JOIN_FORBIDDEN) { - return 'res_JoinRTCSessionFailedForbidden'; - } - if (error === Constants.ErrorCode.RTC_NO_MEDIA_NODES_AVAILABLE) { - return 'res_JoinRTCSessionFailedMissingResources'; - } - if (error === Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED) { - return error; - } - if (error === Constants.ReturnCode.DISCONNECTED || - error === Constants.ReturnCode.FAILED_TO_SEND) { - return 'res_NotConnectedTryAgain'; - } - } - // Default error - return 'res_JoinRTCSessionFailed'; - } - - - /** - * Randomly generate an ACTIVE_SPEAKER and VIDEO_ACTIVE_SPEAKER events - */ - function mockActiveSpeakerEvents() { - // Randomly execute this code - if (!Utils.randomBoolean()) { - //console.log('No active speaker event'); - return; - } - - // Only get the participants that are active in the call - var activeParticipants = _primaryLocalCall.participants.filter(function (p) { - return p.pcState === Enums.ParticipantState.Active; - }); - - if (activeParticipants.length < 2) { - // Not a group call with 3+ participants - return; - } - - // Add local user - activeParticipants.push($rootScope.localUser); - - // 1) ACTIVE_SPEAKER event - var speakerEvt = {sessionId: _primaryLocalCall.callId}; - var fields = ['first', 'second', 'third']; - - var numSpeakers = Utils.randomNumber(0, 3); - // Get the active speakers - var activeSpeakers = activeParticipants.randomCopy(numSpeakers); - - // The first participant in the array is the main speaker, so we will include - // it more often. - if (activeSpeakers.indexOf(activeParticipants[0]) === -1 && Utils.randomBoolean()) { - // Add main speaker - activeSpeakers.unshift(activeParticipants[0]); - if (numSpeakers < 3) { - numSpeakers++; - } - } - - for (var idx = 0; idx < numSpeakers; idx++) { - speakerEvt[fields[idx]] = activeSpeakers[idx].userId; - } - //console.log('Speakers = ', speakerEvt); - - LogSvc.debug('[CircuitCallControlSvc]: Firing mocked ACTIVE_SPEAKER event: ', speakerEvt); - onActiveSpeakerEvent(speakerEvt); - - // 2) VIDEO_ACTIVE_SPEAKER event - - // We only simulate VIDEO_ACTIVE_SPEAKER events when testing without - // extra video channels which is the default case for iOS group calls. - // In order to simulate this with the webclient you need to set - // MAX_VIDEO_EXTRA_CHANNELS = 0 in rtcSessionController.js - if (_primaryLocalCall.sessionCtrl.getNumberOfExtraVideoChannels() > 0) { - return; - } - - if (_primaryLocalCall.hasRemoteScreenShare()) { - // For remote screenshare the media stream will always be assigned to the presenter - return; - } - - var mainSpeaker = activeSpeakers[0]; - if (!mainSpeaker || mainSpeaker.userId === $rootScope.localUser.userId) { - // No active speaker or main speaker is local user - return; - } - - if (mainSpeaker.mediaType.video && !mainSpeaker.streamId) { - // The main speaker has video and it is not currently assigned to the - // video media stream. This is our cue to raise the VIDEO_ACTIVE_SPEAKER event. - onActiveVideoSpeakerEvent({ - sessionId: _primaryLocalCall.callId, - userId: mainSpeaker.userId, - videoStreamId: 'mock' - }); - } - } - - function isActiveSpeakerSimulationNeeded() { - return (_primaryLocalCall && _primaryLocalCall.isMocked && !_primaryLocalCall.isDirect && _primaryLocalCall.participants.length > 1); - } - - function simulateActiveSpeakers() { - if (_activeSpeakerPromise || !isActiveSpeakerSimulationNeeded()) { - return; - } - - _activeSpeakerPromise = $interval(function () { - LogSvc.debug('[CircuitCallControlSvc]: Active speaker simulation interval has fired'); - if (!isActiveSpeakerSimulationNeeded()) { - LogSvc.debug('[CircuitCallControlSvc]: Active speaker simulation is no longer needed. Stop interval.'); - $interval.cancel(_activeSpeakerPromise); - _activeSpeakerPromise = null; - } else if (_primaryLocalCall.isEstablished()) { - // Mock ActiveSpeaker event - mockActiveSpeakerEvents(); - } - }, 2000, 0, false); // apply MUST be set to false here - } - - function addCallToList(call, incomingOnly) { - if (call.checkState(Enums.CallState.Ringing)) { - _incomingCalls.push(call); - if (incomingOnly) { - return; - } - } - for (var idx = 0; idx < _calls.length; idx++) { - if (call.sameAs(_calls[idx])) { - LogSvc.debug('[CircuitCallControlSvc]: Updating call with callId = ' + call.callId); - _calls[idx] = call; - return; - } - } - // This is a new call - _calls.push(call); - - if (call === _primaryLocalCall) { - simulateActiveSpeakers(); - } - _lastEndedCall = null; - } - - function removeCallFromList(call) { - // Remove it from the call list - var idx; - for (idx = 0; idx < _calls.length; idx++) { - if (call.sameAs(_calls[idx])) { - _calls.splice(idx, 1); - break; - } - } - for (idx = 0; idx < _incomingCalls.length; idx++) { - if (call.sameAs(_incomingCalls[idx]) && !call.isHandoverInProgress) { - _incomingCalls.splice(idx, 1); - break; - } - } - for (idx = 0; idx < _activeRemoteCalls.length; idx++) { - if (call.sameAs(_activeRemoteCalls[idx])) { - _activeRemoteCalls.splice(idx, 1); - break; - } - } - if (!call.isRemote) { - _lastEndedCall = call; - _wasMuted = call.sessionCtrl && call.sessionCtrl.isMuted(); - } else { - _lastEndedCall = null; - } - } - - function publishConversationUpdate(conversation) { - if (conversation) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conversation/update event. convId = ', conversation.convId); - PubSubSvc.publish('/conversation/update', [conversation]); - } - } - - function publishCallState(call) { - if (call && call.state !== Enums.CallState.Terminated) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/state event. callId = ' + call.callId + ', state = ' + call.state.name); - PubSubSvc.publish('/call/state', [call]); - } - } - - function publishIceConnectionState(call, iceConnectionState) { - if (call && call.isEstablished()) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/rtp/iceConnectionState event. callId = ' + call.callId + ', iceState = ' + iceConnectionState); - PubSubSvc.publish('/call/rtp/iceConnectionState', [call.callId, iceConnectionState]); - } - } - - function getCallReplaced(call) { - if (_primaryLocalCall && _primaryLocalCall.sameAs(call)) { - // Check if there is an incoming call replacing this call - var alertingCall = _that.getIncomingCall(); - return !!(alertingCall && alertingCall.replaces && alertingCall.replaces.sameAs(call)); - } - return !!(_primaryLocalCall && _primaryLocalCall.replaces && _primaryLocalCall.replaces.sameAs(call)); - } - - function isOfflineFailure(reason) { - switch (reason) { - case Enums.CallClientTerminatedReason.DISCONNECTED: - case Enums.CallClientTerminatedReason.FAILED_TO_SEND: - case Enums.CallClientTerminatedReason.REQUEST_TIMEOUT: - return true; - default: - return false; - } - } - - function terminateCall(call, reason, suppressEvent) { - if (!call) { - return; - } - - LogSvc.info('[CircuitCallControlSvc]: Terminating call...'); - var replaced = getCallReplaced(call); - - // Dismiss incoming call notification if present - dismissNotification(call); - - if (!call.isRemote) { - if (reason) { - call.terminateReason = reason; - if (isOfflineFailure(reason)) { - storeOfflineJoinFailure(call); - } - } - - DeviceDiagnosticSvc.forceFinishDeviceDiagnostics(call); - } - - if (call.screenShareEventHandler) { - ScreenSharingController.unregEvtHandlers(call.screenShareEventHandler); - call.screenShareEventHandler = null; - } - if (call.rejectGetScreenPromise) { - call.rejectGetScreenPromise(Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED); - call.rejectGetScreenPromise = null; - } - - unregisterSessionController(call); - removeCallFromList(call); - call.terminate(); - - $rootScope.closePopoutCallStage && $rootScope.closePopoutCallStage(call); - - // First terminate the call - if (_primaryLocalCall && _primaryLocalCall.sameAs(call)) { - // Cancel terminate timer (if running) - $timeout.cancel(_primaryLocalCall.terminateTimer); - _primaryLocalCall = _secondaryLocalCall; - _secondaryLocalCall = null; - } else if (_secondaryLocalCall && _secondaryLocalCall.sameAs(call)) { - $timeout.cancel(_secondaryLocalCall.terminateTimer); - _secondaryLocalCall = null; - } - - // Now remove it from the conversation - var conversation = getConversation(call.convId); - if (conversation && conversation.call && conversation.call.sameAs(call) && !call.isHandoverInProgress) { - if (call.isTelephonyCall) { - var otherCall = findActivePhoneCall() || findHeldPhoneCall(); - conversation.call = otherCall || null; - } else { - conversation.call = null; - } - - // If group call was ended immediately after it was started, assume it was not started - if (call.isDirect || (call.establishedTime && Date.now() - call.establishedTime >= MIN_TIME_CALL_ESTABLISHED)) { - ConversationSvc.updateLastCallTime(conversation); - } - publishConversationUpdate(conversation); - } - - // If there are no more calls, cancel the activeSpeakerPromise timer - if (_activeSpeakerPromise && !_primaryLocalCall) { - $interval.cancel(_activeSpeakerPromise); - _activeSpeakerPromise = null; - } - - if (!suppressEvent) { - // Check if _primaryLocalCall has replaced this call - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/ended event. Replaced=', replaced); - PubSubSvc.publish('/call/ended', [call, replaced]); - } - } - - function storeOfflineJoinFailure(call) { - if (!call) { - return; - } - - var failureReason = ''; - switch (call.terminateReason) { - case Enums.CallClientTerminatedReason.DISCONNECTED: - failureReason = Constants.RtcClientInfoReason.DISCONNECTED; - break; - case Enums.CallClientTerminatedReason.FAILED_TO_SEND: - failureReason = Constants.RtcClientInfoReason.FAILED_TO_SEND; - break; - case Enums.CallClientTerminatedReason.REQUEST_TIMEOUT: - failureReason = Constants.RtcClientInfoReason.REQUEST_TIMEOUT; - break; - default: - // For now we store only no network connection issues here - return; - } - - var clientInfo = { - userId: $rootScope.localUser.userId, - convId: call.convId, - clientInfoType: Constants.RtcClientInfoType.OFFLINE_JOIN_FAILURE, - reason: failureReason, - timestamp: Date.now(), - sessionId: call.callId - }; - - // We need to check the calls which are remoteActive for this session Id whether it contains instanceId - var conv = ConversationSvc.getConversationByRtcSession(call.callId); - if (conv && conv.call && conv.call.instanceId) { - clientInfo.instanceId = conv.call.instanceId; - } - - LogSvc.info('[CircuitCallControlSvc]: Store new offline join failure. ', clientInfo); - DeviceDiagnosticSvc.storeClientInfo(clientInfo); - } - - function findLocalCallByCallId(callId) { - if (_primaryLocalCall && _primaryLocalCall.callId === callId) { - return _primaryLocalCall; - } - if (_secondaryLocalCall && _secondaryLocalCall.callId === callId) { - return _secondaryLocalCall; - } - return null; - } - - function findCall(rtcSessionId) { - var idx; - for (idx = 0; idx < _calls.length; idx++) { - if (_calls[idx].callId === rtcSessionId) { - return _calls[idx]; - } - } - - // There's maybe an incoming call hidden from main call list - for (idx = 0; idx < _incomingCalls.length; idx++) { - if (_incomingCalls[idx].callId === rtcSessionId) { - return _incomingCalls[idx]; - } - } - - // We could not find the call in the call list. Let's make sure - // that we don't find the call in the conversation. - var conversation = ConversationSvc.getConversationByRtcSession(rtcSessionId); - if (conversation && conversation.call && !conversation.call.isAtcRemote && conversation.call.state.established && - conversation.call.callId === rtcSessionId) { - // Somehow we are out of synch. - // Add the call to the call list and continue. - _calls.push(conversation.call); - return conversation.call; - } - return findLocalCallByCallId(rtcSessionId); - } - - function lookupParticipant(rtcParticipant, call) { - if (rtcParticipant.participantType === Constants.RTCParticipantType.TELEPHONY && rtcParticipant.phoneNumber) { - var participant = _primaryLocalCall.getParticipant(rtcParticipant.userId); - if (participant && participant.phoneNumber && participant.phoneNumber === rtcParticipant.phoneNumber) { - // Not need to lookup and update - return; - } - UserSvc.startReverseLookUp(rtcParticipant.phoneNumber, function (user) { - if (call.isPresent() && user) { - var callParticipant = _primaryLocalCall.getParticipant(rtcParticipant.userId); - if (callParticipant) { - callParticipant.firstName = user.firstName; - callParticipant.lastName = user.lastName; - // Store the resolved userId in the resolvedUserId field, since the participant object - // already has a userId field, which is generated for TELEPHONY participants - callParticipant.resolvedUserId = user.userId; - updateParticipantInCallObj(call, callParticipant); - } - } - }); - } - } - - function findActivePhoneCall(onlyLocal) { - return _calls.find(function (call) { - return call.isTelephonyCall && call.state.established && !call.isHolding() && (!onlyLocal || !call.isRemote); - }) || null; - } - - function findHeldPhoneCall(onlyLocal) { - return _calls.find(function (call) { - return call.isTelephonyCall && call.state.established && call.isHolding() && (!onlyLocal || !call.isRemote); - }) || null; - } - - function isAtcDefaultBusyHandlingSelected() { - return !$rootScope.localUser.selectedBusyHandlingOption || $rootScope.localUser.selectedBusyHandlingOption === BusyHandlingOptions.DefaultRouting.name; - } - - function setCallPeerUser(call, phoneNumber, fqNumber, displayName, userId, cb) { - // After setting the peer user a /call/state event shall be triggered to - // update mobile call stage unless a callback has been passed. In this - // case it is assumed all proper events will be risen by the callback - cb = cb || function () { - publishCallState(call); - }; - - if (!fqNumber && call.atcCallInfo) { - fqNumber = call.atcCallInfo.peerFQN; - } - // Temporarily display whatever is available until user search is done - call.setPeerUser(phoneNumber, displayName, userId); - - if (userId || !fqNumber) { - cb(); - return; - } - - var maxWaitTime = $timeout(function () { - maxWaitTime = null; - if (call.isPresent()) { - cb(); - cb = null; - } - }, REVERSE_LOOKUP_MAX_TIME, false); - - UserSvc.startReverseLookUp(fqNumber, function (user) { - $timeout.cancel(maxWaitTime); - if (call.isPresent()) { - if (user) { - call.setPeerUser(phoneNumber, user.displayName, user.userId); - cb ? cb() : publishCallState(call); - } else { - cb && cb(); - } - } - }); - } - - function normalizeApiParticipant(apiParticipant, mediaType) { - var isCircuitUser = (!apiParticipant.participantType || - apiParticipant.participantType === Constants.RTCParticipantType.USER || - apiParticipant.participantType === Constants.RTCParticipantType.MEETING_POINT) && - (apiParticipant.userId !== 'echo_' + $rootScope.localUser.userId) && !apiParticipant.isExternal; - - var user; - if (apiParticipant.userId === $rootScope.localUser.userId) { - user = $rootScope.localUser; - } else if (isCircuitUser) { - // The participant is a regular Circuit user. Get the user from the cache. - user = UserSvc.getUserFromCache(apiParticipant.userId); - } - - if (!user) { - apiParticipant.userDisplayName = apiParticipant.userDisplayName || apiParticipant.phoneNumber || $rootScope.i18n.map.res_Unknown; - - var tmp = apiParticipant.userDisplayName.split(' '); - if (apiParticipant.participantType === Constants.RTCParticipantType.MEETING_POINT) { - apiParticipant.lastName = tmp[0]; - apiParticipant.location = tmp.splice(1).join(' ').trim().slice(1, -1); - } else { - apiParticipant.firstName = tmp[0]; - apiParticipant.lastName = tmp.splice(1).join(' '); - } - - if (isCircuitUser) { - // User is not yet cached. Create an empty user profile with noData, so the user - // information gets retrieved in addParticipantToCallObj. - user = UserProfile.extend({ - userId: apiParticipant.userId, - firstName: apiParticipant.firstName, - lastName: apiParticipant.lastName, - displayName: apiParticipant.displayName, - location: apiParticipant.location, - userType: apiParticipant.participantType === Constants.RTCParticipantType.MEETING_POINT ? Constants.UserType.MEETING_POINT : Constants.UserType.REGULAR - }); - - // Add new user object to cache IF this is not a simulated participant - if (apiParticipant.isSimulated) { - user.isSimulated = true; - } else { - user.noData = true; - UserSvc.addUsersToCache(user, true, false); - } - } else { - // Create a User object based on the participant data - if (apiParticipant.participantType === Constants.RTCParticipantType.SESSION_GUEST) { - apiParticipant.userType = Constants.UserType.SESSION_GUEST; - } - user = UserProfile.extend(apiParticipant); - if (user.isSessionGuest) { - user.initials = ((user.firstName[0] || '') + (user.lastName[0] || '')).toUpperCase(); - } - } - } - - // Create a derived object - var participant = RtcParticipant.createFromUser(user, Enums.ParticipantState.Active); - participant.streamId = apiParticipant.videoStreamId || ''; - participant.screenStreamId = apiParticipant.screenStreamId || ''; - participant.mediaType = Proto.getMediaType(apiParticipant.mediaTypes || mediaType); - participant.muted = apiParticipant.muted || !participant.mediaType.audio; - participant.participantType = apiParticipant.participantType || Constants.RTCParticipantType.USER; - participant.screenSharePointerSupported = !!apiParticipant.screenSharePointerSupported; - - return participant; - } - - function putSessionTurn(sessionId, turnServers) { - if (!turnServers || !turnServers.length) { - return; - } - var cachedSession = _sessionHash[sessionId]; - if (cachedSession) { - cachedSession.turnServers = turnServers; - } else { - _sessionHash[sessionId] = {turnServers: turnServers}; - } - } - - function getSessionTurnUris(rtcSessionId) { - var cachedSession = _sessionHash[rtcSessionId]; - if (cachedSession && cachedSession.turnServers && cachedSession.turnServers.length) { - return cachedSession.turnServers; - } - return null; - } - - function getClientTerminatedReason(retCode) { - switch (retCode) { - case Constants.ReturnCode.DISCONNECTED: - return Enums.CallClientTerminatedReason.DISCONNECTED; - case Constants.ReturnCode.FAILED_TO_SEND: - return Enums.CallClientTerminatedReason.FAILED_TO_SEND; - case Constants.ReturnCode.REQUEST_TIMEOUT: - return Enums.CallClientTerminatedReason.REQUEST_TIMEOUT; - default: - return Enums.CallClientTerminatedReason.REQUEST_TO_SERVER_FAILED; - } - } - - function joinSession(conversation, mediaType, options, cb) { - options.callOut = !!options.callOut; - options.handover = !!options.handover; - - if (!conversation) { - LogSvc.warn('[CircuitCallControlSvc]: joinSession invoked without a valid conversation'); - cb('Missing conversation'); - return; - } - if (!canInitiateCall(conversation, options.replaces, cb)) { - return; - } - if (conversation.isTelephonyConv) { - if (!options.handover) { - options.dialedDn = options.dialedDn ? options.dialedDn.trim() : null; - if (!options.dialedDn) { - cb('Missing Dialed DN'); - return; - } - } - if (!$rootScope.localUser.callerId) { - cb('Missing Caller ID'); - return; - } - } - - LogSvc.debug('[CircuitCallControlSvc]: joinSession - rtcSessionId=', options.callId || conversation.rtcSessionId); - - var newCallOptions; - if (options.replaces && mediaType.desktop) { - // This call should reuse the desktop stream from the call it's replacing (option used by VDI) - newCallOptions = { - reuseDesktopStreamFrom: options.replaces.sessionCtrl - }; - } - var newLocalCall = new LocalCall(conversation, newCallOptions); - if (_primaryLocalCall) { - // All pre-requisites were already checked by canInitiateCall(), so here we can move the primary call to secondary - _secondaryLocalCall = _primaryLocalCall; - } - _primaryLocalCall = newLocalCall; - _primaryLocalCall.setCallIdForTelephony(options.callId); - _primaryLocalCall.setTransactionId(); - - var call = conversation.call; - var hadRemoteCall = false; - if (call && call.isRemote && !call.isAtcRemote) { - LogSvc.debug('[CircuitCallControlSvc]: Terminate the remote call and add the new local call to the conversation'); - if (options.handover) { - _primaryLocalCall.activeClient = call.activeClient; // Indicates pull in progress - if (call.isTelephonyCall) { - _primaryLocalCall.atcCallInfo = call.atcCallInfo; - setCallPeerUser(_primaryLocalCall, call.peerUser.phoneNumber, null, call.peerUser.displayName); - _primaryLocalCall.participants = call.participants; - } - addCallToList(_primaryLocalCall); - conversation.call = _primaryLocalCall; - - publishConversationUpdate(conversation); - } - hadRemoteCall = true; - if (!call.isTelephonyCall) { - terminateCall(call); - } - } - - if (($rootScope.isSessionGuest || $rootScope.localUser.isCMP || conversation.isTemporary) && !conversation.testCall) { - // For session guests and Meeting Rooms, we don't have the actual number of participants - // in the conversation so allocate the max number of extra channels in order to avoid - // too many media renegotiations. - _primaryLocalCall.sessionCtrl.useMaxNumberOfExtraVideoChannels(); - } - if (_disableRemoteVideoByDefault || conversation.isTelephonyConv) { - LogSvc.info('[CircuitCallControlSvc] Disabling remote video for outgoing call'); - _primaryLocalCall.disableRemoteVideo(); - } - _primaryLocalCall.setState(Enums.CallState.Initiated); - _primaryLocalCall.direction = Enums.CallDirection.OUTGOING; - _primaryLocalCall.mediaType = mediaType; - if (options.replaces) { - _primaryLocalCall.replaces = options.replaces; - var oldStream = options.replaces.sessionCtrl.getLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP); - if (oldStream) { - LogSvc.debug('[CircuitCallControlSvc] Reusing desktop stream from call ID=', options.replaces.callId); - _primaryLocalCall.sessionCtrl.setLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP, oldStream); - // Set old desktop stream to null so it won't be stopped by the old RtcSessionController - options.replaces.sessionCtrl.setLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP, null); - } - } - if (options.joiningGroupCall) { - _primaryLocalCall.isJoiningGroupCall = true; - } - - if (options.callOut) { - // For call out scenarios, add all the conversation participants - // to the call object... - _primaryLocalCall.setPeerUsersAsParticipants(); - // ... and save this flag in the call obj - _primaryLocalCall.isCallOut = true; - } - - if (_primaryLocalCall.isTelephonyCall) { - if (!options.handover) { - setCallPeerUser(_primaryLocalCall, options.dialedDn, null, options.toName, options.userId); - } else { - var activeRemoteCall = getActiveRemoteCall(_primaryLocalCall.callId); - if (activeRemoteCall && !activeRemoteCall.isAtcConferenceCall()) { - _primaryLocalCall.atcCallInfo = activeRemoteCall.atcCallInfo; - setCallPeerUser(_primaryLocalCall, activeRemoteCall.peerUser.phoneNumber, null, activeRemoteCall.peerUser.displayName); - terminateCall(activeRemoteCall); - } - _primaryLocalCall.handover = true; - } - } - - var sessionCtrl = _primaryLocalCall.sessionCtrl; - - var onSdpOffer = function (evt) { - sessionCtrl.onSessionDescription = null; - sessionCtrl.onClosed = null; - - // Let's update the local call's mediaType in case we were not - // able to access all required media streams. - _primaryLocalCall.mediaType = sessionCtrl.getMediaConstraints(); - - var joinData = { - convId: conversation.convId, - rtcSessionId: _primaryLocalCall.callId || conversation.rtcSessionId, - ownerId: conversation.creatorId, - sdp: evt.sdp, - mediaType: _primaryLocalCall.mediaType, - callOut: options.callOut, - handover: options.handover, - sendInviteCancel: !options.callOut, - replaces: options.replaces && options.replaces.callId, - isTelephonyConversation: _primaryLocalCall.isTelephonyCall, - displayName: $rootScope.localUser.displayName, - transactionId: _primaryLocalCall.transactionId, - screenSharePointerSupported: !!(_primaryLocalCall.pointer && _primaryLocalCall.pointer.isEnabled) - }; - - if (_primaryLocalCall.isTelephonyCall) { - joinData.fromDn = Utils.cleanPhoneNumber($rootScope.localUser.callerId); - joinData.fromName = $rootScope.localUser.displayName; - joinData.fromUserId = $rootScope.localUser.userId; - joinData.dialedDn = Utils.cleanPhoneNumber(options.dialedDn); - joinData.toName = options.toName; - joinData.toUserId = options.userId; - joinData.noCallLog = $rootScope.localUser.noCallLog; - } - - // For testing incoming telephony calls from vgtc user: Send fromDn and fromName if localUser has role: VIRTUAL_TELEPHONY_CONNECTOR - if ($rootScope.localUser.hasTelephonyRole) { - joinData.fromName = 'Gru'; - joinData.fromDn = '+19998455246'; - } - - var joinInfo = DeviceDiagnosticSvc.createActionInfo(_primaryLocalCall, Constants.RtcDiagnosticsAction.JOIN); - if (joinInfo) { - joinInfo.data = JSON.stringify(joinData.sdp); - } - - _clientApiHandler.joinRtcCall(joinData, function (err) { - $rootScope.$apply(function () { - DeviceDiagnosticSvc.finishActionInfo(_primaryLocalCall, joinInfo); - - if (err) { - var replaces = _primaryLocalCall.replaces; - var reason = getClientTerminatedReason(err); - if (hadRemoteCall) { - // This method will re-create the remote call - _that.endCallWithCauseCode(_primaryLocalCall.callId, reason); - } else { - terminateCall(_primaryLocalCall, reason); - } - if (replaces) { - _primaryLocalCall = replaces; - } - cb(getJoinErrorText(err)); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Join RTC session call was successful'); - if (_primaryLocalCall) { - publishCallState(_primaryLocalCall); - - if (_primaryLocalCall.conferenceCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/participant/joining event'); - PubSubSvc.publish('/conference/participant/joining', [_primaryLocalCall]); - } - } - cb(null, options.warning, _primaryLocalCall); - } - }); - }); - }; - - var errorCb = function (err) { - var reason = getClientTerminatedReason(err); - LogSvc.warn('[CircuitCallControlSvc]: Failed to initiate call. ', err); - - if (_primaryLocalCall === newLocalCall) { - if (!options.handover) { - _primaryLocalCall.setDisconnectCause(Constants.DisconnectCause.CALL_SETUP_FAILED, err); - if (hadRemoteCall) { - // This method will re-create the remote call - _that.endCallWithCauseCode(_primaryLocalCall.callId, reason); - } else { - var callLeaveMethod = _primaryLocalCall.isDirect ? 'terminateRtcCall' : 'leaveRtcCall'; - _clientApiHandler[callLeaveMethod](_primaryLocalCall.callId, _primaryLocalCall.disconnectCause, function (err) { - err && LogSvc.warn('[CircuitCallControlSvc]: Error terminating RTC call. ', err); - }); - terminateCall(_primaryLocalCall, reason); - } - } else { - terminateCall(_primaryLocalCall, reason); - } - } - cb(getJoinErrorText(err)); - }; - - var onClosed = function () { - sessionCtrl.onClosed = null; - sessionCtrl.onSessionDescription = null; - cb && cb(getJoinErrorText()); - }; - - var turnPromise; - if (!conversation.call || conversation.call !== _primaryLocalCall && !options.handover) { - // Session initiating side - turnPromise = prepareSession(conversation, _primaryLocalCall, options); - } else { - turnPromise = getTurnCredentials(_primaryLocalCall); - } - turnPromise.then(function (turnCredentials) { - if (turnCredentials) { - putSessionTurn(conversation.rtcSessionId, turnCredentials.turnServer); - sessionCtrl.setTurnUris(turnCredentials.turnServer); - sessionCtrl.setTurnCredentials(turnCredentials); - } - - sessionCtrl.warmup(mediaType, null, function () { - if (!_primaryLocalCall) { - LogSvc.debug('[CircuitCallControlSvc]: Call has already been terminated'); - return; - } - - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: The connection warmup was successful. Start the call.'); - - if (!conversation.call || !(conversation.call.isRemote && options.handover)) { - addCallToList(_primaryLocalCall); - conversation.call = _primaryLocalCall; - publishConversationUpdate(conversation); - - if (!_primaryLocalCall.conferenceCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/outgoing event'); - PubSubSvc.publish('/call/outgoing', [_primaryLocalCall]); - } - - publishCallState(_primaryLocalCall); - } - - sessionCtrl.onSessionDescription = onSdpOffer; - sessionCtrl.onClosed = onClosed; - registerSessionController(_primaryLocalCall); - sessionCtrl.start(mediaType, null); - }); - }, function (err) { - $rootScope.$apply(errorCb.bind(null, err)); - }, options.screenConstraint); - }) - .catch(errorCb); - } - - function changeMediaType(callId, transactionId, mediaType, changeDesktopMedia, hold, cb, screenConstraint) { - LogSvc.info('[CircuitCallControlSvc]: changeMediaType - Media type: ', mediaType); - var call = findCall(callId); - if (!call) { - LogSvc.warn('[CircuitCallControlSvc]: changeMediaType - There is no local call'); - cb('No active call'); - return; - } - if (callId !== call.callId) { - LogSvc.info('[CircuitCallControlSvc]: changeMediaType - The given call ID does not match the local call'); - cb('Invalid callId'); - return; - } - if (!call.isEstablished()) { - LogSvc.info('[CircuitCallControlSvc]: changeMediaType - The local call is not established'); - cb('Call not established'); - return; - } - - var sessionCtrl = call.sessionCtrl; - if (!sessionCtrl.isConnStable()) { - cb('Connection not stable'); - return; - } - var onSdp = function (evt) { - sessionCtrl.onSessionDescription = null; - - //ANS-1337 - Change the transactionId when requesting ChangeMediaType - call.setTransactionId(transactionId); - - var changeMediaData = { - rtcSessionId: call.callId, - sdp: evt.sdp, - mediaType: mediaType, - transactionId: call.transactionId, - screenSharePointerSupported: !!(call.pointer && call.pointer.isEnabled) - }; - - _clientApiHandler.changeMediaType(changeMediaData, function (err) { - $rootScope.$apply(function () { - call.clearTransactionId(); - if (err) { - sessionCtrl.renegotiationFailed(err); - cb(err); - } else { - LogSvc.debug('[CircuitCallControlSvc]: changeMediaType was successful. Wait for SDP_ANSWER event'); - } - }); - }); - }; - - var screenShareWasActive = call.localMediaType.desktop; - getTurnCredentials(call) - .then(function (turnCredentials) { - sessionCtrl.setTurnUris(turnCredentials.turnServer); - sessionCtrl.setTurnCredentials(turnCredentials); - sessionCtrl.onSessionDescription = onSdp; - - if (typeof hold === 'boolean') { - var holdRetrieve = hold ? sessionCtrl.holdCall : sessionCtrl.retrieveCall; - holdRetrieve(function (err) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error in hold/retrieve media'); - sessionCtrl.onSessionDescription = null; - sessionCtrl.renegotiationFailed(err); - cb('Error in hold/retrieve media'); - } else { - cb(); - } - }); - } else { - sessionCtrl.changeMediaType(mediaType, changeDesktopMedia, function (err) { - if (!err) { - if (screenShareWasActive && !call.localMediaType.desktop) { - // Screen share was removed - LogSvc.debug('[CircuitCallControlSvc]: Publish /screenshare/ended event'); - PubSubSvc.publish('/screenshare/ended'); - call.pointer.isSupported = call.pointer.isEnabled = false; - } - call.updateMediaDevices(); - cb(); - } else { - $rootScope.$apply(function () { - LogSvc.warn('[CircuitCallControlSvc]: changeMediaType failed - reason: ', err); - sessionCtrl.onSessionDescription = null; - sessionCtrl.renegotiationFailed(err); - cb(err || 'Error changing media'); - }); - } - }, screenConstraint); - } - }) - .catch(function (err) { - cb(getJoinErrorText(err)); - }); - } - - function joinSessionCalled(conversation, mediaType, options, cb) { - var sessionCtrl = _primaryLocalCall.sessionCtrl; - options = options || {}; - - var onSdp = function (evt) { - sessionCtrl.onSessionDescription = null; - - var data = { - convId: conversation.convId, - rtcSessionId: _primaryLocalCall.callId || conversation.rtcSessionId, - ownerId: conversation.creatorId, - sdp: evt.sdp, - mediaType: mediaType, - callOut: false, - handover: false, - sendInviteCancel: true, - isTelephonyConversation: conversation.isTelephonyConv, - displayName: $rootScope.localUser.displayName, - transactionId: _primaryLocalCall.transactionId - }; - - function joinAnswerCb(err) { - $rootScope.$apply(function () { - _primaryLocalCall.clearTransactionId(); - if (err) { - terminateCall(_primaryLocalCall, getClientTerminatedReason(err)); - cb(getJoinErrorText(err)); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Join/Answer RTC session was successful.'); - cb(null, options.warning); - } - }); - } - - if (data.sdp && data.sdp.type === 'offer') { - _clientApiHandler.joinRtcCall(data, joinAnswerCb); - } else { - DeviceDiagnosticSvc.startJoinDelayTimer(_primaryLocalCall); - - _clientApiHandler.answerRtcCall(data, joinAnswerCb); - } - }; - - sessionCtrl.onSessionDescription = onSdp; - if (!sessionCtrl.start(mediaType, null)) { - terminateCall(_primaryLocalCall, Enums.CallClientTerminatedReason.RTC_SESSION_START_FAILED); - cb(getJoinErrorText()); - } - } - - function addRemoteCall(conversation, activeClient, session, preprocessor) { - if (!conversation) { - return null; - } - if (conversation.isTemporary && !activeClient) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conversation/temporary/ended event'); - PubSubSvc.publish('/conversation/temporary/ended', [conversation]); - return null; - } - - var remoteCall = new RemoteCall(conversation); - remoteCall.setState(activeClient ? Enums.CallState.ActiveRemote : Enums.CallState.Started); - - // Ensure remoteCalls get the instanceId to store join failures in state disconnected - if (session && session.instanceId) { - remoteCall.setInstanceId(session.instanceId); - } - - addCallToList(remoteCall); - if (activeClient) { - remoteCall.setActiveClient(activeClient); - addActiveRemoteCall(remoteCall); - if (session) { - if (session.participants.length === 1 && session.callOut) { - remoteCall.pullNotAllowed = true; - } - remoteCall.mediaType = Proto.getMediaType(session.mediaTypes); - } - } - - if (typeof preprocessor === 'function') { - // Preprocess the remoteCall object before raising the events - preprocessor(remoteCall); - } - - conversation.call = remoteCall; - publishConversationUpdate(conversation); - publishCallState(remoteCall); - - return remoteCall; - } - - function changeRemoteCallToStarted(activeRemoteCall) { - if (activeRemoteCall.isGuestInvite) { - removeCallFromList(activeRemoteCall); - var conversation = getConversation(activeRemoteCall.convId); - if (conversation && conversation.isTemporary) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conversation/temporary/ended event'); - PubSubSvc.publish('/conversation/temporary/ended', [conversation]); - } - } else { - activeRemoteCall.setState(Enums.CallState.Started); - activeRemoteCall.activeClient = null; - publishCallState(activeRemoteCall); - _activeRemoteCalls.some(function (call, idx) { - if (call.callId === activeRemoteCall.callId) { - _activeRemoteCalls.splice(idx, 1); - return true; - } - }); - } - } - - function sendBusy(inviteEvt) { - _clientApiHandler.declineRtcCall({ - convId: inviteEvt.convId, - rtcSessionId: inviteEvt.sessionId, - cause: Constants.InviteRejectCause.BUSY, - transactionId: inviteEvt.transactionId - }); - } - - function publishAtcCall(conversation, inviteEvt, first) { - var call = new LocalCall(conversation, inviteEvt.sessionId); - call.setInstanceId(inviteEvt.instanceId); - call.setTransactionId(inviteEvt.transactionId); - call.setState(Enums.CallState.Ringing); - if (inviteEvt.from) { - setCallPeerUser(call, inviteEvt.from.phoneNumber, inviteEvt.from.fullyQualifiedNumber, inviteEvt.from.displayName, null, function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish ' + first ? 'atccall/firstcall' : '/atccall/secondcall/' + ' event.'); - PubSubSvc.publish(first ? '/atccall/firstcall' : '/atccall/secondcall', [call]); - }); - } - } - - function decline(incomingCall, params, suppressEvent, cb) { - cb = cb || function () {}; - incomingCall = incomingCall || _primaryLocalCall; - removeCallFromList(incomingCall); - if (!incomingCall || (incomingCall.state !== Enums.CallState.Ringing && incomingCall.state !== Enums.CallState.Answering)) { - LogSvc.debug('[CircuitCallControlSvc]: call invalid to be declined'); - cb('Call Invalid'); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: decline - rtcSessionId: ', incomingCall.callId); - - var data = { - convId: incomingCall.convId, - rtcSessionId: incomingCall.callId, - cause: params.type, - transactionId: incomingCall.transactionId - }; - - var eventParams = [incomingCall]; - params.err && eventParams.push(params.err); - - _clientApiHandler.declineRtcCall(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error declining the RTC call. ', err); - cb(err); - } else { - cb(); - } - }); - }); - - if (!incomingCall.isDirect) { - // Add remote call - var conversation = getConversation(incomingCall.convId); - addRemoteCall(conversation); - } - - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/declining event'); - PubSubSvc.publish('/call/declining', eventParams); - terminateCall(incomingCall, Enums.CallClientTerminatedReason.CLIENT_ENDED_PRFX + params.type, suppressEvent); - } - - // Leave a local call - function leaveCall(localCall, reason, qosReason, isLastParticipant, cb) { - cb = cb || function () {}; - localCall = localCall || _primaryLocalCall; - - if (!localCall) { - LogSvc.debug('[CircuitCallControlSvc]: There is no local call'); - cb && $timeout(cb); - return; - } - if (localCall.terminateTimer) { - LogSvc.debug('[CircuitCallControlSvc]: Local call is already being terminated. Ignore leave request.'); - cb && $timeout(cb); - return; - } - if (localCall.checkState([Enums.CallState.Ringing, Enums.CallState.Answering])) { - decline(localCall, {type: reason || Constants.InviteRejectCause.DECLINE}, false, cb); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: leaveCall - rtcSessionId=', localCall.callId); - var conversation; - localCall.clearAtcHandoverInProgress(); - - if (localCall.activeClient) { - // Call terminated while pull in progress, create an active remote call so user can pull again - conversation = getConversation(localCall.convId); - addRemoteCall(conversation, localCall.activeClient, null, function (call) { - if (_primaryLocalCall.isTelephonyCall) { - call.atcCallInfo = _primaryLocalCall.atcCallInfo; - call.peerUser = _primaryLocalCall.peerUser; - } - }); - terminateCall(localCall, qosReason); - return; - } - - if (localCall.isDirect) { - _clientApiHandler.terminateRtcCall(localCall.callId, localCall.disconnectCause, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error terminating RTC call. ', err); - } - cb && cb(err); - }); - }); - } else { - if (localCall.participants.length) { - // Always create the remote call to show that the session is still active for group sessions - // (except for temporary conversations / guest invitation / calls without participants) - conversation = getConversation(localCall.convId); - addRemoteCall(conversation); - } - _clientApiHandler.leaveRtcCall(localCall.callId, localCall.disconnectCause, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error leaving the RTC call. ', err); - } - cb && cb(err); - }); - }); - } - - if (qosReason === Enums.CallClientTerminatedReason.PAGE_UNLOADED) { - localCall.terminateReason = qosReason; - // Since the page is unloading, immediately send the QoS report - localCall.qosSubmitted = true; - InstrumentationSvc.sendQOSData(localCall, localCall.mediaType, localCall.sessionCtrl.getLastSavedStats()); - } - - if (!isLastParticipant) { - if (localCall.directCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/terminating event'); - PubSubSvc.publish('/call/terminating', [_primaryLocalCall]); - } else if (localCall.conferenceCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/leaving event'); - PubSubSvc.publish('/conference/leaving', [localCall]); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Publish /group/leaving event'); - PubSubSvc.publish('/group/leaving', [localCall]); - } - } - - if (localCall.outgoingFailed()) { - // Delay the terminate to keep the call header visible for a couple seconds - localCall.terminateTimer = $timeout(function () { - localCall.terminateTimer = null; - terminateCall(localCall, qosReason); - }, CLOSE_CALL_DELAY); - localCall.terminateReason = qosReason; // In case the call is terminated before the timer pops, we have the actual reason. - } else { - terminateCall(localCall, qosReason); - } - - } - - function terminateConference(call, cb) { - cb = cb || function () {}; - - if (call.isDirect) { - LogSvc.debug('[CircuitCallControlSvc]: Direct call. Ignore terminate conference request.'); - $timeout(cb); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: terminateConference - rtcSessionId=', call.callId); - - _clientApiHandler.terminateRtcCall(call.callId, call.disconnectCause, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error terminating the RTC call. ', err); - - if (call === _primaryLocalCall) { - var conversation = getConversation(call.convId); - if (conversation && call.isEstablished()) { - addRemoteCall(conversation); - } - } - } - cb && cb(err); - }); - }); - if (call === _primaryLocalCall) { - terminateCall(call, Enums.CallClientTerminatedReason.USER_ENDED); - } - } - - function endRemoteCall(remoteCall, cb) { - LogSvc.debug('[CircuitCallControlSvc]: endRemoteCall - rtcSessionId=', remoteCall.callId); - if (remoteCall.isDirect) { - _clientApiHandler.terminateRtcCall(remoteCall.callId, remoteCall.disconnectCause, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error terminating remote RTC call. ', err); - } - cb(err); - }); - }); - } else { - _clientApiHandler.leaveRtcCall(remoteCall.callId, remoteCall.disconnectCause, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error leaving remote RTC call. ', err); - } - cb(err); - }); - }); - } - } - - function checkAndAddExtraChannels(conv) { - if (_primaryLocalCall.isDirect) { - // This is not applicable to direct calls - return; - } - - if (_primaryLocalCall.sessionCtrl.hasMaxExtraVideoChannels()) { - LogSvc.debug('[CircuitCallControlSvc]: We already have max number of extra video channels'); - return; - } - - // Compare the number of conversation participants to the number of - // participants in the call (which could be higher, due to the session guests), - // and select the highest number. - var numConvParticipants = conv && conv.participants.length; - if (!numConvParticipants) { - // We might not have the conversation object available, so use the current number of extra channels - // as the base (+ 2 means we need to account for the main channel and the current user) - numConvParticipants = _primaryLocalCall.sessionCtrl.getNumberOfExtraVideoChannels() + 2; - } - // The participants array in the local call obj doesn't account for the current user, hence the + 1 - var xc = Math.max(_primaryLocalCall.participants.length + 1, numConvParticipants); - - // Let the session controller decide whether renegotiation is needed. - var changed = _primaryLocalCall.sessionCtrl.setExtraVideoChannelsForParticipants(xc); - if (changed && _primaryLocalCall.isEstablished()) { - LogSvc.debug('[CircuitCallControlSvc]: The number of extra channels has increased. Trigger a media renegotiation.'); - changeMediaType(_primaryLocalCall.callId, null, _primaryLocalCall.localMediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to renegotiate the media. Error: ', err); - } - }); - } - } - - function getActiveRemoteCall(callId) { - var activeRemoteCall = null; - _activeRemoteCalls.some(function (call) { - if (call.callId === callId) { - activeRemoteCall = call; - return true; - } - }); - return activeRemoteCall; - } - - function getIncomingCall(callId) { - var incomingCall = _incomingCalls.find(function (call) { - return call.callId === callId; - }); - return incomingCall || null; - } - - function addActiveRemoteCall(remoteCall) { - var found = _activeRemoteCalls.some(function (c, idx) { - if (c === remoteCall) { - return true; - } - if (c.callId === remoteCall.callId) { - _activeRemoteCalls[idx] = remoteCall; - return true; - } - return false; - }); - if (!found) { - _activeRemoteCalls.push(remoteCall); - } - } - - function removeSessionParticipant(userId, cb) { - cb = cb || function () {}; - if (_primaryLocalCall) { - var callParticipant = _primaryLocalCall.getParticipant(userId); - if (callParticipant) { - if (callParticipant.actions.includes(Enums.ParticipantAction.Drop)) { - var data = { - rtcSessionId: _primaryLocalCall.callId, - userId: userId - }; - _clientApiHandler.removeSessionParticipant(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to remove participant'); - cb('res_RemoveParticipantFailed'); - } else { - cb(); - } - }); - }); - } else { - LogSvc.warn('[CircuitCallControlSvc]: remove call participant, unsupported pcState: ', callParticipant.pcState.name); - cb('Participant not active'); - } - } else { - LogSvc.error('[CircuitCallControlSvc]: call participant not found: ', userId); - cb('Participant not found'); - } - } else { - cb('No active call'); - } - } - - function verifyStreamIds(call, participant) { - // Go through the participants list and make sure that no one - // is using the same video stream ID of the supplied participant - if (!participant.streamId) { - return; - } - var simulateVideoActiveSpeaker = call.sessionCtrl.getNumberOfExtraVideoChannels() === 0; - if (!call.isMocked || simulateVideoActiveSpeaker) { - call.participants.forEach(function (p) { - if (p.streamId === participant.streamId && p.userId !== participant.userId) { - p.streamId = ''; - LogSvc.debug('[CircuitCallControlSvc]: The video stream has been re-assigned. Clear the streamId for previous participant. [userId, streamId] = ', [p.userId, participant.streamId]); - _primaryLocalCall.setParticipantRemoteVideoURL(p); - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event. userId =', p.userId); - PubSubSvc.publish('/call/participant/updated', [call.callId, p]); - } - }); - } - } - - function setParticipantActions(call, participant) { - var conversation = getConversation(call.convId); - if (!$rootScope.isSessionGuest) { - participant.setActions(conversation, $rootScope.localUser); - } - } - - function addParticipantToCallObj(call, participant, state, update) { - var added = call.addParticipant(participant, state, update); - if (added) { - verifyStreamIds(call, added); - setParticipantActions(call, added); - - // Check if we already have the user data - if (participant.noData && !participant.reqPending) { - // Get the user data and add to cache - LogSvc.debug('[CircuitCallControlSvc]: Get user data for added participant. userId: ', participant.userId); - UserSvc.getUserById(participant.userId, function (err, user) { - if (!user) { return; } - - // We successfully retrieved the user data. The participant object is automatically updated - // since it has the user object as its prototype. However, the firstName and lastName fields - // need to be manually updated because they are also set directly on the participant object. - // See RtcParticipant.createUser() for details. - LogSvc.debug('[CircuitCallControlSvc]: Successfully retrieved participant data. userId: ', participant.userId); - - // Check if the call is still active and the participant is still a member - if (call.isPresent() && call.hasParticipant(participant.userId)) { - // Raise a /call/participant/updated event for mobile clients) - participant.firstName = user.firstName; - participant.lastName = user.lastName; - - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event. userId =', participant.userId); - PubSubSvc.publish('/call/participant/updated', [call.callId, participant]); - } - }); - } - } - return added; - } - - function updateParticipantInCallObj(call, participant, state) { - var updated = call.updateParticipant(participant, state); - if (updated) { - verifyStreamIds(call, updated); - setParticipantActions(call, updated); - } - return updated; - } - - function removeCallParticipant(call, userId, cause) { - var state = Enums.ParticipantState.Terminated; - switch (cause) { - case Constants.RTCSessionParticipantLeftCause.CONNECTION_LOST: - case Constants.RTCSessionParticipantLeftCause.STREAM_LOST: - state = Enums.ParticipantState.ConnectionLost; - break; - case Constants.RTCSessionParticipantLeftCause.REMOVED: - state = Enums.ParticipantState.Removed; - break; - case Constants.RTCSessionParticipantLeftCause.USER_LEFT_STAGE: - state = Enums.ParticipantState.OffStage; - break; - } - call.setParticipantState(userId, state); - - var removedParticipant = call.removeParticipant(userId); - - if (!call.hasOtherParticipants()) { - if (call.conferenceCall) { - // everybody left but the localUser; return to the waiting state - call.setState(Enums.CallState.Waiting); - } else { - // everybody left - LogSvc.debug('[CircuitCallControlSvc]: Publish /group/terminated event'); - PubSubSvc.publish('/group/terminated', [call]); - - leaveCall(call, null, Enums.CallClientTerminatedReason.NO_USERS_LEFT, true); - return; - } - } - - if (removedParticipant) { - // Update the call's media type - call.updateMediaType(); - - if (!call.isRemote && removedParticipant.isCMP && removedParticipant.isMeetingPointInvitee) { - call.setMeetingPointInviteState(false); - // Unmute the remote audio stream - _that.enableRemoteAudio(call.callId); - } - - if (call.conferenceCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/participant/left event'); - PubSubSvc.publish('/conference/participant/left', [call, cause]); - } - var leftDataWrapper = cause ? { leftCause: cause } : null; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/removed event'); - PubSubSvc.publish('/call/participant/removed', [call.callId, removedParticipant, leftDataWrapper]); - } - publishCallState(call); - } - - function setRecordingInfoData(localCall, data) { - if (!localCall || !data || !data.state) { - return null; - } - var recording = localCall.recording; - - if (recording.state !== data.state) { - recording.notifyByCurtain = data.state === Constants.RecordingInfoState.START_PENDING || - (data.state === Constants.RecordingInfoState.STARTED && recording.state === Constants.RecordingInfoState.START_PENDING); - recording.notifyByUser = !recording.notifyByCurtain || (data.state === Constants.RecordingInfoState.START_PENDING && recording.state !== Constants.RecordingInfoState.STARTED); - } else { - recording.notifyByCurtain = false; - recording.notifyByUser = false; - } - - recording.state = data.state; - recording.duration = data.duration || recording.duration; - recording.reason = data.reason; - if (data.state === Constants.RecordingInfoState.STARTED) { - recording.resumeTime = Date.now(); - } - - if (data.starter && data.starter.userId && data.starter.userId !== recording.starter.userId) { - recording.starter = data.starter; - if (recording.starter.userId === $rootScope.localUser.userId) { - recording.starter.res = 'res_StartedRecording_You'; - } else { - recording.starter.name = recording.starter.firstName || recording.starter.displayName; - recording.starter.res = recording.starter.name ? 'res_StartedRecording_Other' : 'res_StartedRecording'; - } - } - return recording; - } - - function updateAttendeeCount(attendeeCount) { - if (_primaryLocalCall && (attendeeCount !== undefined) && (_primaryLocalCall.attendeeCount !== attendeeCount)) { - _primaryLocalCall.attendeeCount = attendeeCount; - _primaryLocalCall.updateCallState(); - - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/attendeeCountChanged'); - PubSubSvc.publish('/call/attendeeCountChanged', [_primaryLocalCall.callId, _primaryLocalCall.attendeeCount]); - } - } - - function getScreen(call) { - return new $q(function (resolve, reject) { - if (!call || call.screenShareEventHandler) { - // No call present or another getScreen is in progress - return reject('res_ErrorMsgInternal'); - } - if (!call.isVdi) { - call.screenShareEventHandler = ScreenSharingController.getScreen(function (data) { - $timeout(function () { - call.screenShareEventHandler = null; - call.rejectGetScreenPromise = null; - resolve(data || {}); - }, 0); - }, function (error) { - $timeout(function () { - call.screenShareEventHandler = null; - call.rejectGetScreenPromise = null; - if (error === Constants.ReturnCode.CHOOSE_DESKTOP_MEDIA_CANCELLED) { - // The UI should recognize this error and not show an error message - reject(error); - } else { - reject('res_AccessToDesktopFailed'); - } - }, 0); - }); - } else { - // VDI will invoke ScreenSharingController.getScreen() from the VD side - return resolve({}); - } - - // Expose API to allow rejecting the promise in case the call is terminated - call.rejectGetScreenPromise = function (err) { - LogSvc.warn('[CircuitCallControlSvc]: Cancelling getScreen promise for call ID: ', call.callId); - reject(err); - }; - }); - } - - ///////////////////////////////////////////////////////////////////////////// - // RtcSessionController event handlers - ///////////////////////////////////////////////////////////////////////////// - function registerSessionController(call) { - var sessionCtrl = call && call.sessionCtrl; - if (!sessionCtrl) { - LogSvc.error('[CircuitCallControlSvc]: Failed to get RtcSessionController for given call. ', call); - return; - } - - sessionCtrl.onIceCandidate = onIceCandidate.bind(null, call); - // sessionCtrl.onSessionDescription is registered on-demand - // sessionCtrl.sessionClosed is registered on demand - sessionCtrl.onSdpConnected = onSdpConnected.bind(null, call); - sessionCtrl.onIceConnected = onIceConnected.bind(null, call); - sessionCtrl.onIceDisconnected = onIceDisconnected.bind(null, call); - sessionCtrl.onRemoteVideoAdded = onRemoteVideoAdded.bind(null, call); - sessionCtrl.onRemoteVideoRemoved = onRemoteVideoRemoved.bind(null, call); - sessionCtrl.onRemoteStreamUpdated = onRemoteStreamUpdated.bind(null, call); - sessionCtrl.onDTMFToneChange = onDTMFToneChange.bind(null, call); - sessionCtrl.onRtcError = onRtcError.bind(null, call); - sessionCtrl.onRtcWarning = onRtcWarning.bind(null, call); - sessionCtrl.onLocalStreamEnded = onLocalStreamEnded.bind(null, call); - sessionCtrl.onQosAvailable = onQosAvailable.bind(null, call); - sessionCtrl.onStatsThresholdExceeded = onStatsThresholdExceeded.bind(null, call); - sessionCtrl.onStatsNoOutgoingPackets = onStatsNoOutgoingPackets.bind(null, call); - sessionCtrl.onNetworkQuality = onNetworkQuality.bind(null, call); - } - - function unregisterSessionController(call) { - var sessionCtrl = call && call.sessionCtrl; - if (sessionCtrl) { - sessionCtrl.onIceCandidate = null; - sessionCtrl.onSessionDescription = null; - sessionCtrl.sessionClosed = null; - sessionCtrl.onSdpConnected = null; - sessionCtrl.onIceConnected = null; - sessionCtrl.onIceDisconnected = null; - sessionCtrl.onRemoteVideoAdded = null; - sessionCtrl.onRemoteVideoRemoved = null; - sessionCtrl.onRemoteStreamUpdated = null; - sessionCtrl.onDTMFToneChange = null; - sessionCtrl.onRtcError = null; - sessionCtrl.onRtcWarning = null; - sessionCtrl.onLocalStreamEnded = null; - // Do not unregister handler for onQoSAvailable. The session controller will - // automatically remove the handler after the stats are collected. - sessionCtrl.onStatsThresholdExceeded = null; - sessionCtrl.onStatsNoOutgoingPackets = null; - sessionCtrl.onNetworkQuality = null; - } - } - - function onIceCandidate(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onIceCandidate'); - var data = { - rtcSessionId: call.callId, - userId: $rootScope.localUser.userId, - origin: event.origin, - candidates: event.candidates.map(JSON.stringify.bind(JSON)) - }; - - var iceInfo = DeviceDiagnosticSvc.createActionInfo(call, Constants.RtcDiagnosticsAction.ICE_CANDIDATES); - if (iceInfo) { - iceInfo.data = data.candidates; - iceInfo.isEndOfCandidates = event.endOfCandidates; - } - - _clientApiHandler.sendIceCandidates(data, function (err) { - DeviceDiagnosticSvc.finishActionInfo(call, iceInfo); - if (err) { - LogSvc.warn('Failed to send ICE candidate: ', err); - } - }); - } - - function onSdpConnected(call) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onSdpConnected'); - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/sdpConnected event'); - PubSubSvc.publish('/call/sdpConnected', [call]); - - var sdpConnectedInfo = DeviceDiagnosticSvc.createActionInfo(call, Constants.RtcDiagnosticsAction.SDP_CONNECTED); - DeviceDiagnosticSvc.finishActionInfo(call, sdpConnectedInfo); - - if (call.isTelephonyCall) { - // For telephony calls the backend won't send events when SDP is connected. - // So set the call state to waiting here. - if (!call.state.established) { - call.setState(Enums.CallState.Waiting); - } - call.updateCallState(); - } else { - call.setState((call.isDirect || call.hasOtherParticipants()) ? Enums.CallState.Active : Enums.CallState.Waiting); - } - publishCallState(call); - DeviceDiagnosticSvc.finishDeviceDiagnostics(call); - }); - } - - function onIceConnected(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onIceConnected. pcType:', event.pcType); - if (event.pcType === 'audio/video' && call.isEstablished()) { - var disconnectCause = call.disconnectCause || {}; - if (disconnectCause.cause === Constants.DisconnectCause.STREAM_LOST) { - call.disconnectCause = null; - } - publishIceConnectionState(call, 'connected'); - } - } - - - function onIceDisconnected(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onIceDisconnected. pcType:', event.pcType); - if (event.pcType === 'audio/video' && call.isEstablished() && !call.isHeld()) { - call.setDisconnectCause(Constants.DisconnectCause.STREAM_LOST); - publishIceConnectionState(call, 'disconnected'); - } - } - - function onRemoteVideoAdded(/*call*/) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onRemoteVideoAdded'); - } - - function onRemoteVideoRemoved(/*call*/) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onRemoteVideoRemoved'); - } - - function onRemoteStreamUpdated(call) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onRemoteStreamUpdated'); - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/remoteStreamUpdated event'); - PubSubSvc.publish('/call/remoteStreamUpdated', [call]); - }); - } - - function onDTMFToneChange(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onDTMFToneChange'); - - if (event.tone) { - LogSvc.debug('[CircuitCallControlSvc]: Sent DTMF Digit: ' + event.tone); - PubSubSvc.publish('/call/singleDigitSent', [call, event.tone]); - } else if (event.tone === '') { - LogSvc.info('[CircuitCallControlSvc]: Digits played completely'); - PubSubSvc.publish('/call/digitsSent', [call]); - } - } - - function onRtcError(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onRtcError'); - PubSubSvc.publish('/call/rtcError', [call, event.error]); - if (call.checkState([Enums.CallState.Ringing, Enums.CallState.Answering])) { - decline(call, {type: Constants.InviteRejectCause.BUSY}); - return; - } - - if (call.sameAs(_primaryLocalCall)) { - if (event.error === 'res_CallMediaFailed') { - call.setDisconnectCause(Constants.DisconnectCause.NEGOTIATION_FAILED); - _that.endCallWithCauseCode(call.callId, Enums.CallClientTerminatedReason.ICE_TIMED_OUT); - } else if (event.error === 'res_CallMediaDisconnected') { - call.setDisconnectCause(Constants.DisconnectCause.STREAM_LOST); - _that.endCallWithCauseCode(call.callId, Enums.CallClientTerminatedReason.LOST_MEDIA_STREAM); - // Since media connection has been lost, ping Access Server to check if websocket connection is still up - _clientApiHandler.pingServer(); - } else if (!call.isEstablished()) { - _that.endCall(call.callId); - } - } - } - - function onRtcWarning(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onRtcWarning'); - PubSubSvc.publish('/call/rtcWarning', [call, event.warning]); - } - - function onLocalStreamEnded(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onStreamEnded'); - if (event.isDesktop && _primaryLocalCall.hasLocalScreenShare()) { // The screen share stream doesn't have audio - LogSvc.debug('[CircuitCallControlSvc]: Screenshare stream ended, remove screenshare or terminate the call'); - if (_primaryLocalCall.isEstablished()) { - _that.removeScreenShare(_primaryLocalCall.callId); - } else { - // Call has not been answered yet, so end it - _that.endCall(_primaryLocalCall.callId); - } - } else { - // Trigger a media renegotiation so a new device can be picked up - _that.renegotiateMedia(call.callId, function (err) { - if (err) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/localStreamEnded event'); - PubSubSvc.publish('/call/localStreamEnded', [call]); - } - }); - } - } - - function onQosAvailable(call, event) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onQosAvailable'); - if (event.renegotiationInProgress) { - call.terminateReason = Enums.CallClientTerminatedReason.MEDIA_RENEGOTIATION; - } - if (call.sessionCtrl) { - if (call.qosSubmitted) { - LogSvc.info('[CircuitCallControlSvc]: QoS already submitted for call Id:', call.callId); - return; - } - InstrumentationSvc.sendQOSData(call, call.lastMediaType, event.qos, event.lastSavedStats); - } - - if (event.renegotiationInProgress) { - call.terminateReason = null; - } - } - - function onStatsThresholdExceeded(call, event) { - if (!_clientDiagnosticsDisabled) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onStatsThresholdExceeded. Cleared: ', !!(event && event.cleared)); - - if (event && event.cleared) { - PubSubSvc.publish('/call/rtp/threshholdExceeded/cleared', [call.callId]); - } else { - PubSubSvc.publish('/call/rtp/threshholdExceeded/detected', [call.callId]); - } - } - } - - function onStatsNoOutgoingPackets(call) { - LogSvc.debug('[CircuitCallControlSvc]: RtcSessionController - onStatsNoOutgoingPackets. Publish /call/rtp/noOutgoingPackets event'); - PubSubSvc.publish('/call/rtp/noOutgoingPackets', [call.callId]); - } - - function onNetworkQuality(call, event) { - if (event && event.quality) { - var qualityEstimation = event.quality; - var eventData = { - userId: $rootScope.localUser.userId, - audio: { - qualityLevel: qualityEstimation.qualityLevel.value, - currentPlSend: qualityEstimation.currentPlSend, - currentPlReceive: qualityEstimation.currentPlReceive, - firstTimeLowQuality: qualityEstimation.firstTimeLowQuality - } - }; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/network/quality event for quality=', eventData.audio.qualityLevel); - PubSubSvc.publish('/call/network/quality', [eventData]); - } - } - - function onActiveSpeakerEvent(evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.ACTIVE_SPEAKER'); - - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - - if (localCall.isDirect || localCall.participants.length < 2) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore RTC.ACTIVE_SPEAKER event for 1-2-1 calls'); - return; - } - - var activeSpeakers = []; - evt.first && activeSpeakers.push(evt.first); - evt.second && activeSpeakers.push(evt.second); - evt.third && activeSpeakers.push(evt.third); - var hasChanged = localCall.setActiveSpeakers(activeSpeakers); - - if (!hasChanged) { - // No changes - return; - } - - // The active speakers have changed. Raise an event to notify the UI - // NOTE: Do not invoke $rootScope.$apply for this event. We will invoke a $scope.$digest in - // CallStageCtrl if the call stage is visible. - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/activeSpeakers event'); - PubSubSvc.publish('/call/participants/activeSpeakers', [localCall.callId, activeSpeakers]); - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTC.ACTIVE_SPEAKER event. ', e); - } - } - - function onActiveVideoSpeakerEvent(evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.VIDEO_ACTIVE_SPEAKER'); - - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - - var participant = localCall.getParticipant(evt.userId); - if (!participant) { - LogSvc.debug('[CircuitCallControlSvc]: Participant info not available. Ignoring VIDEO_ACTIVE_SPEAKER. userId=', evt.userId); - return; - } - - if (participant.streamId === evt.videoStreamId) { - LogSvc.debug('[CircuitCallControlSvc]: Participant is already assigned to the received videoStreamId. Ignoring VIDEO_ACTIVE_SPEAKER'); - return; - } - - $rootScope.$apply(function () { - participant.streamId = evt.videoStreamId; - verifyStreamIds(localCall, participant); - localCall.setParticipantRemoteVideoURL(participant); - localCall.checkForActiveRemoteVideo(); - - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event. userId =', participant.userId); - PubSubSvc.publish('/call/participant/updated', [localCall.callId, participant]); - }); - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.VIDEO_ACTIVE_SPEAKER event.', e); - } - } - - function processInitActiveSession(session) { - LogSvc.debug('[CircuitCallControlSvc]: Processing active session for convId = ', session.convId); - - if (!session.participants || session.participants.length === 0) { - LogSvc.warn('[CircuitCallControlSvc]: The active session does not have participants. rtcSessionId = ', session.rtcSessionId); - return; - } - - var conversation = ConversationSvc.getConversationByRtcSession(session.rtcSessionId); - if (!conversation || (!conversation.hasJoined && !conversation.isTemporary)) { - return; - } - - if (conversation.call) { - // Somehow we ended up with a race condition - LogSvc.debug('[CircuitCallControlSvc]: Conversation already has an associated call'); - return; - } - if (session.turnServers) { - putSessionTurn(session.rtcSessionId, session.turnServers); - } - - if (getIncomingCall(session.rtcSessionId)) { - // Session correspond to an already ringing incoming call - return; - } - - var activeClient; - var lostSession = false; - session.participants.some(function (p) { - if (p.userId === $rootScope.localUser.userId) { - activeClient = p; - if (p.clientId === _clientApiHandler.clientId) { - // Session for current client without local call - lostSession = true; - } - return true; - } - }); - - if (conversation.type === Constants.ConversationType.DIRECT && !activeClient) { - return; - } - - if (lostSession) { - var terminateCb = function (e) { - if (e) { - LogSvc.warn('[CircuitCallControlSvc] Lost session could not be terminated. Session Id=', session.rtcSessionId); - } - }; - var disconnectCause = { - disconnectCause: Constants.DisconnectCause.CONNECTION_LOST, - disconnectReason: Constants.DisconnectReason.CALL_LOST - }; - // Terminate lost session - if (conversation.type === Constants.ConversationType.DIRECT) { - _clientApiHandler.terminateRtcCall(session.rtcSessionId, disconnectCause, terminateCb); - return; - } else { - _clientApiHandler.leaveRtcCall(session.rtcSessionId, disconnectCause, terminateCb); - } - } - - // If remote call is active on another client - addRemoteCall(conversation, activeClient, session, function (call) { - call.setCallIdForTelephony(session.rtcSessionId); - if (activeClient) { - setPeerUserForTelephonyCall(call, session); - } - }); - } - - function setPeerUserForTelephonyCall(remoteCall, session) { - if (remoteCall && remoteCall.isTelephonyCall && session && - session.participants && session.participants.length <= 2) { - - session.participants.some(function (p) { - if (p.participantType === Constants.RTCParticipantType.TELEPHONY) { - setCallPeerUser(remoteCall, p.phoneNumber, null, p.userDisplayName); - return true; - } - }); - } - } - - function findLocalCall(session) { - var foundLocalCall = false; - var localCall = findLocalCallByCallId(session.rtcSessionId); - if (localCall && session.participants) { - foundLocalCall = session.participants.some(function (p) { - return (p.userId === $rootScope.localUser.userId && p.clientId === _clientApiHandler.clientId); - }); - } - return foundLocalCall; - } - - function initActiveSessions() { - return new $q(function (resolve, reject) { - if (!_conversationLoaded && !$rootScope.isSessionGuest) { - reject('Not ready to initialize active sessions'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: Initialize active RTC sessions'); - - _clientApiHandler.getActiveSessions(function (err, activeSessions) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error getting active sessions: ', err); - } - activeSessions = activeSessions || []; - - // Terminate any existing remote calls. They will be recreated if they are still there. - for (var i = (_calls.length - 1); i >= 0; i--) { // Reverse since elements will be deleted - if (_calls[i].isRemote) { - terminateCall(_calls[i]); - } - } - _activeRemoteCalls.empty(); - - var conversation = null; - var foundLocalPrimaryCall = false; - var foundLocalSecondaryCall = false; - var convPromises = []; - activeSessions.forEach(function (s) { - var localCall = findLocalCallByCallId(s.rtcSessionId); - if (localCall) { - if (findLocalCall(s)) { - foundLocalPrimaryCall = foundLocalPrimaryCall || localCall === _primaryLocalCall; - foundLocalSecondaryCall = foundLocalSecondaryCall || localCall === _secondaryLocalCall; - LogSvc.info('[CircuitCallControlSvc]: Found localCall in active sessions. Trigger media renegotiation.'); - // Trigger a media renegotiation to refresh the ICE candidates - _that.renegotiateMedia(localCall.callId); - return; - } - - // If it gets to this point is because there is an active session for this user but the - // client Id does not match the one of the local user (recent reconnection). Local call - // shall be cleaned up so the remote call is recreated. - terminateCall(localCall, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - } - - conversation = ConversationSvc.getConversationByRtcSession(s.rtcSessionId); - if (!conversation || !conversation.hasJoined) { - if (s.invitedGuests && s.invitedGuests.includes($rootScope.localUser.userId)) { - LogSvc.info('[CircuitCallControlSvc]: Local user is invited as guest to this session'); - convPromises.push(ConversationSvc.createConversationAsGuestFromSummary(s.convId)); - } else if (!conversation) { - LogSvc.info('[CircuitCallControlSvc]: Conversation associated with session is not cached. Need to get it.'); - convPromises.push(ConversationSvc.getConversationPromise(s.convId)); - } - return; - } - - processInitActiveSession(s); - }); - - PubSubSvc.publish('/activeSessions/received'); - - if (_secondaryLocalCall && !foundLocalSecondaryCall) { - terminateCall(_secondaryLocalCall, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - } - if (_primaryLocalCall && !foundLocalPrimaryCall) { - terminateCall(_primaryLocalCall, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - } - - if (convPromises.length > 0) { - LogSvc.info('[CircuitCallControlSvc]: Wait for get conversation promises to be resolved.'); - $q.all(convPromises) - .then(function (convs) { - var convIDs = convs.map(function (c) { return c.convId; }); - LogSvc.debug('[CircuitCallControlSvc]: Retrieved the missing conversation(s): ', convIDs); - LogSvc.debug('[CircuitCallControlSvc]: Now that we have all the conversations, check if they still have active RTC sessions.'); - _clientApiHandler.getActiveSessions(function (err, activeSessions) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error getting active sessions: ', err); - activeSessions = []; - } - activeSessions.forEach(function (s) { - if (convIDs.indexOf(s.convId) === -1) { - return; - } - if (!findLocalCall(s)) { - processInitActiveSession(s); - } - }); - resolve(); - }); - }); - }) - .catch(function (err) { - LogSvc.error('[CircuitCallControlSvc]: Failed to retrieve the conversation(s). ', err); - }); - } else { - resolve(); - } - }); - }); - }); - } - - function updateCall(newConversation, oldConversation, call) { - // Create a temporary call object with old rtc session id to raise call ended event to UI - // The "replaced" flag is set to true in the ended message so that the - // mobile client knows this is part of a call update. - var oldCall; - if (call.isRemote) { - oldCall = new RemoteCall(oldConversation); - } else { - oldCall = new LocalCall(oldConversation); - } - oldConversation.call = oldCall; - - // Terminate the call to clear its resources. - oldCall.terminate(); - - // End temporary call associated to old coversation - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/ended event'); - PubSubSvc.publish('/call/ended', [oldCall, true]); - - oldConversation.call = null; - - // Update old conversation - publishConversationUpdate(oldConversation); - - call.updateCall(newConversation); - newConversation.call = call; - - // Update new conversation - publishConversationUpdate(newConversation); - publishCallState(call); - - // Inform the UI that call has been moved - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/moved event'); - PubSubSvc.publish('/call/moved', [oldCall.callId, call.callId]); - } - - function sendChangeMediaReject(callId, transactionId) { - var changeMediaRejectData = { - rtcSessionId: callId, - transactionId: transactionId - }; - _clientApiHandler.changeMediaReject(changeMediaRejectData, function (err) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: changeMediaReject sending error'); - } else { - LogSvc.debug('[CircuitCallControlSvc]: changeMediaReject sent'); - } - }); - } - - function presentIncomingCall(conversation, userId) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/incoming event'); - PubSubSvc.publish('/call/incoming', [conversation.call]); - if (!conversation.muted && NotificationSvc) { - var notificationType = conversation.call.isGuestInvite ? Enums.NotificationType.INCOMING_INVITATION_AS_GUEST : - (conversation.call.mediaType.video ? Enums.NotificationType.INCOMING_VIDEO_CALL : Enums.NotificationType.INCOMING_VOICE_CALL); - var notification = { - type: notificationType, - userId: userId, - extras: { - conversation: conversation, - call: conversation.call - } - }; - if (conversation.isTelephonyConv) { - // For telephony calls, the userId is not sufficient, so send the whole user object to the NotificationSvc - notification.user = conversation.call.peerUser; - } - NotificationSvc.show(notification); - } - - conversation.call.startRingingTimer(function () { - decline(conversation.call, {type: Constants.InviteRejectCause.TIMEOUT}, false); - }); - } - - function processInvite(evt, conversation) { - if (!evt || !conversation) { - LogSvc.error('[CircuitCallControlSvc]: processInvite called without evt or conversation'); - return; - } - - var replaces = null; - var replacesActiveRemote = false; - var userIsMuted = false; - var autoAnswer = false; - - if (evt.replaces && getActiveRemoteCall(evt.replaces)) { - replacesActiveRemote = true; - } else if (_primaryLocalCall) { - if (_primaryLocalCall.callId === evt.replaces) { - // Save the muted state now, because we'll lose this information if the replaced call - // is terminated before we answer the new _primaryLocalCall - userIsMuted = _primaryLocalCall.sessionCtrl && _primaryLocalCall.sessionCtrl.isMuted(); - replaces = _primaryLocalCall; - } else if (_primaryLocalCall.terminateTimer) { - // There's a soon-to-be-terminated local call - if (_primaryLocalCall.callId === evt.sessionId) { - LogSvc.debug('[CircuitCallControlSvc]: Reject incoming call. Current call still not terminated'); - sendBusy(evt); - return; - } - // The new call is for a different conversation. Terminate the current call. - terminateCall(_primaryLocalCall); - // Keep handling the INVITE event - - } else { - if (_primaryLocalCall.callId === evt.sessionId) { - if (_primaryLocalCall.isDirect) { - LogSvc.debug('[CircuitCallControlSvc]: INVITE for same local call. Reject it'); - sendBusy(evt); - } else { - LogSvc.warn('[CircuitCallControlSvc]: INVITE for same conversation as local call. Ignore it.'); - } - return; - } - if (!_primaryLocalCall.isEstablished() && !(conversation.isTelephonyConv && !isAtcDefaultBusyHandlingSelected())) { - // Cannot accept the second incoming call - if the second incoming call is a telephony call and default - // busy handling is not selected, the second call should be handled by CSTA - LogSvc.debug('[CircuitCallControlSvc]: Reject incoming call. Current call still not established'); - sendBusy(evt); - return; - } - } - } - if (conversation.isTelephonyConv) { - if (evt.to && evt.to.displayName && evt.to.displayName.startsWith('handover-from')) { - LogSvc.debug('[CircuitCallControlSvc]: Received handover call'); - if (_handoverTimer) { - autoAnswer = true; - $timeout.cancel(_handoverTimer); - _handoverTimer = null; - } else { - LogSvc.warn('[CircuitCallControlSvc]: INVITE for handover call from another client. Ignore it.'); - replacesActiveRemote = true; - } - } else if (_pickupTimer) { - autoAnswer = true; - $timeout.cancel(_pickupTimer); - _pickupTimer = null; - } - } else if (_lastEndedCall && _lastEndedCall.callId === evt.replaces) { - userIsMuted = _wasMuted; - replaces = _lastEndedCall; - } - - var found = getIncomingCall(evt.sessionId); - if (found) { - LogSvc.warn('[CircuitCallControlSvc]: INVITE for existing ringing call. Ignore it.'); - return; - } - - if (!replacesActiveRemote && !replaces && _incomingCalls.length >= 1 && !autoAnswer && !(conversation.isTelephonyConv && !isAtcDefaultBusyHandlingSelected())) { // Reject 2nd incoming call - // if the second incoming call is a telephony call and default busy handling is not selected, - // the second call should be handled by CSTA - LogSvc.debug('[CircuitCallControlSvc]: Reject second incoming call.'); - sendBusy(evt); - return; - } - - if (conversation.isTelephonyConv && !autoAnswer && !replacesActiveRemote) { - if ($rootScope.localUser.selectedRoutingOption === RoutingOptions.DeskPhone.name) { - publishAtcCall(conversation, evt, true); - return; - } - if (!$rootScope.localUser.selectedBusyHandlingOption || $rootScope.localUser.selectedBusyHandlingOption === BusyHandlingOptions.DefaultRouting.name) { - if (_primaryLocalCall && _primaryLocalCall.isPresent() && _secondaryLocalCall && _secondaryLocalCall.isPresent()) { - LogSvc.debug('[CircuitCallControlSvc]: Reject incoming call. We cannot handle more than 2 local phone calls'); - sendBusy(evt); // We can't handle more than 2 local phone calls, reject it - return; - } - } else if ((_primaryLocalCall || _incomingCalls.length >= 1 || !_activeRemoteCalls.isEmpty()) || (conversation.call && conversation.call.isAtcRemote)) { - publishAtcCall(conversation, evt); - return; - } - } - - var options = { - isAtcPullCall: conversation.isTelephonyConv && autoAnswer && evt.sdp.type === 'offer' - }; - if (replaces && replaces.localMediaType.desktop) { - // This call should reuse the desktop stream from the call it's replacing (option used by VDI) - options.reuseDesktopStreamFrom = replaces.sessionCtrl; - } - var incomingCall = new LocalCall(conversation, options); - incomingCall.setCallIdForTelephony(evt.sessionId); - incomingCall.setInstanceId(evt.instanceId); - incomingCall.setTransactionId(evt.transactionId); - - if (replaces) { - incomingCall.replaces = replaces; - var oldStream = replaces.sessionCtrl.getLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP); - if (oldStream) { - LogSvc.debug('[CircuitCallControlSvc] Reusing desktop stream from call ID=', replaces.callId); - incomingCall.sessionCtrl.setLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP, oldStream); - // Set old desktop stream to null so it won't be stopped by the old RtcSessionController - replaces.sessionCtrl.setLocalStream(RtcSessionController.LOCAL_STREAMS.DESKTOP, null); - } - } - if (_disableRemoteVideoByDefault) { - LogSvc.info('[CircuitCallControlSvc] Disabling remote video for incoming call'); - incomingCall.disableRemoteVideo(); - } - incomingCall.setState(Enums.CallState.Ringing); - incomingCall.direction = Enums.CallDirection.INCOMING; - if ($rootScope.localUser.isATC && incomingCall.isTelephonyCall) { - incomingCall.atcCallInfo = new AtcCallInfo(); - } - // MediaType should contain 'AUDIO' by default - var evtMediaType = evt.mediaType || ['AUDIO']; - addParticipantToCallObj(incomingCall, normalizeApiParticipant({userId: evt.userId}, evtMediaType), Enums.ParticipantState.Calling); - - incomingCall.ringingTimeout = (evt.ringingTimeout || incomingCall.ringingTimeout); - - var mediaType = Proto.getMediaType(evtMediaType); - incomingCall.mediaType = mediaType; - - if (mediaType.desktop) { - LogSvc.debug('[CircuitCallControlSvc]: INVITE for a Screen Share'); - } - if (replacesActiveRemote) { - // Do not show this call until answered - addCallToList(incomingCall, true); - return; - } - addCallToList(incomingCall); - - var onSessionWarmup = function () { - $rootScope.$apply(function () { - // The warmup was successful, so we can continue processing the call - if (incomingCall.state === Enums.CallState.Terminated) { - LogSvc.debug('[CircuitCallControlSvc]: New alerting call has been terminated already'); - return; - } - - registerSessionController(incomingCall); - LogSvc.debug('[CircuitCallControlSvc]: New alerting call: ', incomingCall.callId); - - if (conversation.call && !conversation.isTelephonyConv) { - LogSvc.debug('[CircuitCallControlSvc]: Terminate the remote call and add the new local call to the conversation'); - terminateCall(conversation.call); - } - - var oldCallId = null; - if (conversation.call && conversation.call.isAtcRemote) { - oldCallId = conversation.call.callId; - } - - conversation.call = incomingCall; - - if (conversation.isTelephonyConv && autoAnswer) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /atccall/replace event'); - PubSubSvc.publish('/atccall/replace', [incomingCall, oldCallId]); - } else { - // Show new incoming call - publishConversationUpdate(conversation); - } - - if (replaces) { - _that.answerCall(incomingCall.callId, replaces.localMediaType, function (err) { - if (!err) { - if (userIsMuted) { - _primaryLocalCall.sessionCtrl && _primaryLocalCall.sessionCtrl.mute(); - } - } - }); - return; - } - - if ($rootScope.localUser.isCMP) { - // answer the call immediately - autoAnswer = true; - // answer the call with HD video - mediaType.video = true; - mediaType.hdVideo = true; - // remove screen sharing - mediaType.desktop = false; - } - - if (autoAnswer) { - if (incomingCall.isTelephonyCall && evt.from) { - setCallPeerUser(incomingCall, evt.from.phoneNumber, evt.from.fullyQualifiedNumber, evt.from.displayName); - } - _that.answerCall(incomingCall.callId, mediaType); - return; - } - - // Notify the server that we're alerting - _clientApiHandler.sendProgress(incomingCall.callId, evt.userId, $rootScope.localUser.userId, function (err) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: sendProgress error', err); - } - }); - - if (incomingCall.isTelephonyCall && evt.from) { - setCallPeerUser(incomingCall, evt.from.phoneNumber, evt.from.fullyQualifiedNumber, evt.from.displayName, null, function () { - if (conversation.call === incomingCall) { - presentIncomingCall(conversation, evt.userId); - } - }); - return; - } - - presentIncomingCall(conversation, evt.userId); - }); - }; - - var onWarmupFailed = function (err) { - // Ignore the incoming call rather than rejecting it. The user may have other clients logged on that - // support webrtc and have a mic, so ignoring this call is the correct action. - LogSvc.warn('[CircuitCallControlSvc]: Warmup failed. Ignore the incoming call: ', err); - PubSubSvc.publish('/call/warmupFailed', [err, incomingCall]); - terminateCall(incomingCall, null, true); - - if (conversation.isTemporary) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conversation/temporary/ended event'); - PubSubSvc.publish('/conversation/temporary/ended', [conversation]); - } - }; - - // Get the RtcSessionController object - var sessionCtrl = incomingCall.sessionCtrl; - var turnUris = getSessionTurnUris(conversation.rtcSessionId); - - if ($rootScope.localUser.isCMP) { - // Always allocate the max number of extra channels for Meeting Rooms. - sessionCtrl.useMaxNumberOfExtraVideoChannels(); - } - - if (!WebRTCAdapter.enabled) { - LogSvc.warn('[CircuitCallControlSvc]: Client does not support WebRTC.'); - onWarmupFailed('res_NoWebRTC'); - return; - } - - getTurnCredentials(incomingCall) - .then(function (turnCredentials) { - if (!incomingCall.isPresent()) { - LogSvc.debug('[CircuitCallControlSvc]: There is no incoming call anymore'); - return; - } - if (turnUris) { - LogSvc.debug('[CircuitCallControlSvc]: Using TURN servers associated with session: ', turnUris); - sessionCtrl.setTurnUris(turnUris); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Using retrieved TURN servers'); - sessionCtrl.setTurnUris(turnCredentials.turnServer); - } - sessionCtrl.setTurnCredentials(turnCredentials); - LogSvc.debug('[CircuitCallControlSvc]: Set TURN credentials'); - - // Warmup the connection to see if we can handle the call - var localMediaType; - if (incomingCall.replaces) { - localMediaType = incomingCall.replaces.localMediaType; - } else { - // For Firefox we cannot access the getUserMedia API if the browser is inactive, - // so we need to bypass the getUserMedia warmup by not adding any media type. - var isFirefox = WebRTCAdapter.browser === 'firefox'; - // For Android we need to bypass the getUserMedia warmup too - // because we should create new LocalMediaStream only when user answered a call. - var isAndroid = WebRTCAdapter.browser === 'android'; - // Create LocalMediaStream only when answering the call for DotNet. - var isDotNet = WebRTCAdapter.browser === 'dotnet'; - - localMediaType = {audio: !isFirefox && !isAndroid && !isDotNet, video: false}; - } - - checkMediaSources(localMediaType, function (normalizedMediaType) { - sessionCtrl.warmup(normalizedMediaType, evt.sdp, onSessionWarmup, onWarmupFailed); - }); - }) - .catch(onWarmupFailed); - } - - function processActiveSessions(conversation, activeSessions) { - if (!conversation || !activeSessions) { - return; - } - - if (!conversation.hasJoined && !conversation.isTemporary) { - // This should not happen with the actual backend, but the mock also sends the - // events for conversations the user is no longer a participant. - LogSvc.debug('[CircuitCallControlSvc]: User is not a conversation participant. Ignore active session.'); - return; - } - - activeSessions.some(function (s) { - if (conversation.rtcSessionId === s.rtcSessionId) { - LogSvc.debug('[CircuitCallControlSvc]: Processing active session for convId = ', conversation.convId); - - if (!s.participants || s.participants.length === 0) { - LogSvc.warn('[CircuitCallControlSvc]: The active session does not have participants. rtcSessionId = ', s.rtcSessionId); - return true; - } - - var activeClient; - s.participants.some(function (p) { - if (p.userId === $rootScope.localUser.userId) { - activeClient = p; - return true; - } - }); - - if (conversation.type === Constants.ConversationType.DIRECT && !activeClient) { - return true; - } - if (conversation.call) { - // Somehow we ended up with a race condition - LogSvc.debug('[CircuitCallControlSvc]: Conversation already has an associated call'); - return true; - } - - addRemoteCall(conversation, activeClient, s, function (call) { - if (activeClient) { - setPeerUserForTelephonyCall(call, s); - } else if (call.conferenceCall) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/started event'); - PubSubSvc.publish('/conference/started', [call]); - } - }); - return true; - } - - }); - } - - function sendDTMFDigits(callId, digits, cb) { - var localCall = findLocalCallByCallId(callId); - - if (!localCall) { - LogSvc.info('[CircuitCallControlSvc]: sendDTMFDigits - The given call ID does not match the local call'); - cb('Invalid callId'); - return; - } - - LogSvc.info('[CircuitCallControlSvc]: Sending DTMF Digits=', digits); - - if (!localCall.sessionCtrl.sendDTMFDigits(digits)) { - LogSvc.error('[CircuitCallControlSvc]: Cannot send DTMF digits'); - cb('res_CannotSendDTMF'); - } - } - - function checkMediaSourcesAndJoin(conversation, mediaType, options, cb) { - // Large conference participants must join the session with audio receive-only - if (conversation.type === Constants.ConversationType.LARGE && $rootScope.isSessionGuest) { - mediaType.audio = false; - LogSvc.debug('[CircuitCallControlSvc]: Set media type for large conference participant to ', mediaType); - } - - checkMediaSources(mediaType, function (normalizedMediaType, warning) { - if (warning && conversation.isTelephonyConv) { - // Don't allow phone call to continue if we cannot access a microphone - cb(warning); - return; - } - options.warning = warning; - joinSession(conversation, normalizedMediaType, options, cb); - }); - } - - function checkMediaSources(mediaType, cb) { - LogSvc.debug('[CircuitCallControlSvc]: Check media sources for mediaType = ', mediaType); - - if ($rootScope.browser.msie && !$rootScope.isSessionGuest && !$rootScope.localUser.hasPermission(Constants.SystemPermission.IE_PLUGIN_SUPPORT)) { - LogSvc.debug('[CircuitCallControlSvc]: User does not have permission to use IE plugin. Publish /ieplugin/permissionDenied event.'); - PubSubSvc.publish('/iePlugin/permissionDenied'); - return; - } - - var warning; - mediaType = mediaType || {audio: true, video: false}; - if (_isMobile || _isDotNet || (!mediaType.audio && !mediaType.video)) { - LogSvc.debug('[CircuitCallControlSvc]: Normalized mediaType = ', mediaType); - cb(mediaType); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: Get audio and video media sources'); - WebRTCAdapter.getMediaSources(function (audioSources, videoSources) { - if (mediaType.video && (!videoSources || videoSources.length < 1)) { - mediaType.video = false; - warning = 'res_AnswerWarnNoCamera'; - } - if (mediaType.audio && (!audioSources || audioSources.length < 1)) { - mediaType.audio = false; - if (warning === 'res_AnswerWarnNoCamera') { - warning = 'res_AccessToMediaInputDevicesFailed'; - } else { - warning = 'res_AccessToAudioInputDeviceFailed'; - } - } - LogSvc.debug('[CircuitCallControlSvc]: Normalized mediaType = ', mediaType); - cb(mediaType, warning); - }); - } - - function dismissNotification(call) { - // Dismiss incoming call notification if present - if (call && call.activeNotification && !call.isHandoverInProgress) { - NotificationSvc && NotificationSvc.dismiss(call.activeNotification); - call.activeNotification = null; - } - } - - function sendParticipantPointer(userId, x, y) { - var data = { - content: { - type: 'PARTICIPANT_POINTER', - x: x, - y: y, - userId: $rootScope.localUser.userId - }, - destUserId: userId - }; - - _userToUserHandler.sendDesktopAppRequest(data, function (err) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error sending participant pointer coordinates: ', err); - } else { - LogSvc.info('[CircuitCallControlSvc]: Successfully sent participant pointer coordinates.'); - } - }); - } - - function sendParticipantDrawing(userId, points, options) { - var data = { - content: { - type: 'PARTICIPANT_DRAWING', - points: points, - options: options || {}, - userId: $rootScope.localUser.userId - }, - destUserId: userId - }; - - _userToUserHandler.sendDesktopAppRequest(data, function (err) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error sending participant drawing coordinates: ', err); - } else { - LogSvc.info('[CircuitCallControlSvc]: Successfully sent participant drawing coordinates.'); - } - }); - } - - function forceStopScreenshare(call, userId) { - if (call && call.hasLocalScreenShare() && call.checkState([Enums.CallState.Active, Enums.CallState.Waiting])) { - var participant = userId && call.getParticipant(userId); - var participantName = participant && participant.displayName; - - LogSvc.debug('[CircuitCallControlSvc]: Publish /screenshare/forceStop/started event'); - PubSubSvc.publish('/screenshare/forceStop/started', [userId]); - - _that.removeScreenShare(call.callId, function (error) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /screenshare/forceStop/ended event'); - PubSubSvc.publish('/screenshare/forceStop/ended', [error]); - }); - - NotificationSvc && NotificationSvc.show({ - type: Enums.NotificationType.HIGH_PRIORITY, - user: $rootScope.localUser, - extras: { - title: $rootScope.i18n.map.res_ScreenShareRemotelyDisabledTitle, - text: participantName ? $rootScope.i18n.localize('res_ScreenShareRemotelyDisabledByUser', [participantName]) : - $rootScope.i18n.map.res_ScreenShareRemotelyDisabled - } - }); - } - } - - function disableAllAudio(callId) { - // Mute the local user - _that.mute(callId); - // Mute the remote audio stream - _that.disableRemoteAudio(callId); - } - - function addParticipant(call, participant, cb) { - cb = cb || function () {}; - if (!call || !participant) { - LogSvc.error('[CircuitCallControlSvc]: addParticipant - Invalid arguments'); - cb('Invalid arguments'); - return; - } - var dialOutPhoneNumber = participant.dialOutPhoneNumber; - var contact = !dialOutPhoneNumber ? participant : { - userId: 'gtc-callout-' + dialOutPhoneNumber.replace(/[+ ]+/g, ''), - phoneNumber: Utils.cleanPhoneNumber(dialOutPhoneNumber), - displayName: participant.displayName, - resolvedUserId: participant.userId, - isExternal: true, - participantType: Constants.RTCParticipantType.TELEPHONY, - pstnDialIn: false - }; - - if (!contact.userId) { - LogSvc.warn('[CircuitCallControlSvc]: addParticipant - Participant missing userId'); - cb('Missing userId'); - return; - } - if (call.hasParticipant(contact.userId) || (contact.resolvedUserId && call.hasParticipant(contact.resolvedUserId))) { - LogSvc.info('[CircuitCallControlSvc]: addParticipantTo - Participant is already in the call'); - cb('Participant exists'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: Adding participant to existing RTC session. userId =', contact.userId); - // Audio must be enabled for invited users. - var mediaType = Utils.shallowCopy(call.mediaType); - mediaType.audio = true; - var data = { - rtcSessionId: call.callId, - userId: contact.userId, - mediaType: mediaType - }; - if (dialOutPhoneNumber) { - data.from = { - phoneNumber: Utils.cleanPhoneNumber($rootScope.localUser.callerId), - displayName: $rootScope.localUser.displayName - }; - data.to = { - phoneNumber: contact.phoneNumber, - displayName: contact.displayName, - resolvedUserId: contact.resolvedUserId - }; - } - - _clientApiHandler.addParticipantToRtcSession(data, function (err) { - $rootScope.$apply(function () { - if (err) { - if (err === Constants.ErrorCode.PERMISSION_DENIED) { - LogSvc.info('[CircuitCallControlSvc]: Failed to add participant. Reached conference participants limit'); - cb('res_MaxConversationParticipantsLimit'); - return; - } - LogSvc.warn('[CircuitCallControlSvc]: Failed to add participant'); - cb('res_AddParticipantToRtcSessionFailed'); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Add participant request was successful'); - // Set direction to outgoing - call.direction = Enums.CallDirection.OUTGOING; - - if (!call.isRemote) { - // Add participant to call stage - var normalizedParticipant = normalizeApiParticipant(contact, ['AUDIO']); - var pState = Enums.ParticipantState.Initiated; - var addedParticipant = addParticipantToCallObj(call, normalizedParticipant, pState); - - if (addedParticipant) { - if (addedParticipant.isCMP) { - addedParticipant.isMeetingPointInvitee = true; - call.setMeetingPointInviteState(true); - disableAllAudio(call.callId); - } - - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/added event'); - PubSubSvc.publish('/call/participant/added', [call.callId, addedParticipant]); - } - } - cb(); - } - }); - }); - } - - function publishMutedEvent(call) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/localUser/muted event'); - PubSubSvc.publish('/call/localUser/muted', [call.callId, call.remotelyMuted, call.locallyMuted]); - } - - function canInitiateCall(conversation, isReplacingAnotherCall, cb) { - cb = cb || function () {}; - if (_incomingCalls.length >= 1) { - cb('res_CannotJoinMultipleSessions'); - return false; - } - var primaryCallPresent = _primaryLocalCall && _primaryLocalCall.isPresent(); - - if (primaryCallPresent) { - if (_primaryLocalCall.isTelephonyCall) { - if (!conversation.isTelephonyConv || !$rootScope.localUser.isATC) { - cb('res_CannotJoinMultipleSessions'); - return false; - } - if (_secondaryLocalCall) { - cb('res_CannotInitiateThirdPhoneCall'); - return false; - } - } else if (!isReplacingAnotherCall) { - cb('res_CannotJoinMultipleSessions'); - return false; - } - } - - for (var idx = 0; idx < _calls.length; idx++) { - if (!_calls[idx].state.established && _calls[idx].isTelephonyCall) { - LogSvc.error('[CircuitCallControlSvc]: Cannot initiate second phone call if existing call is not established'); - cb('res_JoinRTCSessionFailed'); - return false; - } - } - return true; - } - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - PubSubSvc.subscribe('/conversation/update', function (conv, data) { - if (!data) { - // Event was not triggered by a Conversation.UPDATE event - return; - } - var localCall = findLocalCallByCallId(conv.rtcSessionId); - if (!localCall || localCall.isDirect) { - // Event is not applicable - return; - } - LogSvc.debug('[CircuitCallControlSvc]: Received /conversation/update event'); - - if (data.addedParticipants) { - // If the client is involved in a Group Call and a new participant is added, the client - // may need to initiate a media renegotiation to increase the number of extra video channels. - checkAndAddExtraChannels(conv); - } - - if (data.isModerated !== undefined) { - // Update the participant actions in case the conversation moderation has been toggled - localCall.participants.forEach(function (p) { - p.setActions(conv, $rootScope.localUser); - }); - } - }); - - PubSubSvc.subscribe('/conversation/upgrade', function (oldConversation, newConversation) { - LogSvc.debug('[CircuitCallControlSvc]: Received /conversation/upgrade event. Old convId = ' + - oldConversation.convId + '. New convId = ', newConversation.convId); - - if ((oldConversation.call) && - (newConversation.convId !== oldConversation.convId) && - (oldConversation.call.checkState(Enums.CallState.Active))) { - - LogSvc.debug('[CircuitCallControlSvc]: Move Session'); - if (oldConversation.type === Constants.ConversationType.DIRECT && newConversation.type === Constants.ConversationType.GROUP) { - joinSession(newConversation, oldConversation.call.localMediaType, {callOut: true, handover: false, replaces: oldConversation.call}, function (err) { - if (!err) { - if (oldConversation.call.sessionCtrl && oldConversation.call.sessionCtrl.isMuted()) { - newConversation.call.sessionCtrl && newConversation.call.sessionCtrl.mute(); - } - } - }); - } else { - var data = { - sessionId: oldConversation.rtcSessionId, - conversationId: oldConversation.convId, - newSessionId: newConversation.rtcSessionId, - newConversationId: newConversation.convId - }; - - _clientApiHandler.moveRtcSession(data, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Error moving RTC Session. ', err); - } - }); - - } - } - }); - - PubSubSvc.subscribe('/conversation/left', function (conversation) { - LogSvc.debug('[CircuitCallControlSvc]: Received /conversation/left event. convId = ' + conversation.convId); - - if (conversation && conversation.call) { - _that.hideRemoteCall(conversation.call.callId); - } - }); - - PubSubSvc.subscribe('/conversation/participants/add', function (call, participants) { - LogSvc.debug('[CircuitCallControlSvc]: Received /conversation/participants/add event.'); - if (call && participants) { - participants.forEach(function (participant) { - addParticipant(call, participant); - }); - } - }); - - PubSubSvc.subscribe('/csta/handover', function () { - LogSvc.debug('[CircuitCallControlSvc]: Received /csta/handover event.'); - _handoverTimer = $timeout(function () { - _handoverTimer = null; - }, ATC_HANDOVER_TIME); - }); - - PubSubSvc.subscribe('/atccall/moveFailed', function () { - LogSvc.debug('[CircuitCallControlSvc]: Received /atccall/moveFailed event.'); - if (_handoverTimer) { - $timeout.cancel(_handoverTimer); - _handoverTimer = null; - } - }); - - PubSubSvc.subscribe('/atccall/pickUpInProgress', function () { - LogSvc.debug('[CircuitCallControlSvc]: Received /atccall/pickUpInProgress event.'); - _pickupTimer = $timeout(function () { - _pickupTimer = null; - }, ATC_PICKUP_TIMER); - }); - - PubSubSvc.subscribe('/conversations/loadComplete', function () { - LogSvc.debug('[CircuitCallControlSvc]: Received /conversations/loadComplete event'); - _conversationLoaded = true; - initActiveSessions(); - }); - - PubSubSvc.subscribe('/registration/state', function (state) { - LogSvc.debug('[CircuitCallControlSvc]: Received /registration/state event'); - if (state !== RegistrationState.Registered) { - if (_primaryLocalCall && _primaryLocalCall.isTestCall) { - // Test calls cannot be recovered - terminateCall(_primaryLocalCall, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - } - - // Clear all incoming calls, since we can't answer them anymore - _incomingCalls.forEach(function (c) { - LogSvc.debug('[CircuitCallControlSvc]: Lost websocket connection, terminate local incoming call'); - terminateCall(c, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - }); - - if (state === RegistrationState.Disconnected || state === RegistrationState.Waiting) { - // Clear all other calls - _calls.forEach(function (c) { - terminateCall(c, Enums.CallClientTerminatedReason.LOST_WEBSOCKET_CONNECTION); - }); - } - } else if ($rootScope.isSessionGuest && _primaryLocalCall) { - // This is a guest client which reconnected while in a conference. - // We need to check if the conference is still ongoing. - initActiveSessions(); - } - }); - - PubSubSvc.subscribe('/conversation/re-added', function (conv) { - LogSvc.debug('[CircuitCallControlSvc]: Received /conversation/re-added event. convId = ', conv.convId); - _clientApiHandler.getSession(conv.rtcSessionId, function (err, session) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc] Error retrieving session. Err: ', err); - } else { - if (session.participants && session.participants.length > 0) { - $rootScope.$apply(function () { - processActiveSessions(conv, [session]); - }); - } - } - }); - }); - - PubSubSvc.subscribe('/call/meetingPointInvitee', function (callId) { - LogSvc.debug('[CircuitCallControlSvc]: Received /call/meetingPointInvitee event'); - if (callId) { - var localCall = findLocalCallByCallId(callId); - if (localCall) { - localCall.setMeetingPointInviteState(true); - disableAllAudio(callId); - } - } - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Client API Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - if (circuit.isElectron) { - _userToUserHandler.on('DESKTOP_APP.PARTICIPANT_POINTER', function (data, routing) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received UserToUser DESKTOP_APP.PARTICIPANT_POINTER'); - // Use routing.srcUserId instead of data.userId to determine which user has sent the message. - // data.userId is obsolete and is not verified by the backend. - data.userId = routing.srcUserId; - UserSvc.getUserById(data.userId, function (err, user) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: User not found.', data.userId); - return; - } else if (_primaryLocalCall.hasLocalScreenShare()) { - data.name = user.displayName; - require('electron').ipcRenderer.send('participant-pointer', data); - } - }); - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling UserToUser DESKTOP_APP.PARTICIPANT_POINTER event.', e); - } - }); - - _userToUserHandler.on('DESKTOP_APP.PARTICIPANT_DRAWING', function (data, routing) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received UserToUser DESKTOP_APP.PARTICIPANT_DRAWING'); - if ($rootScope.circuitLabs.PARTICIPANT_DRAWING) { - // Use routing.srcUserId instead of data.userId to determine which user has sent the message. - // data.userId is obsolete and is not verified by the backend. - data.userId = routing.srcUserId; - UserSvc.getUserById(data.userId, function (err, user) { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: User not found.', data.userId); - return; - } else if (_primaryLocalCall.hasLocalScreenShare()) { - data.name = user.displayName; - require('electron').ipcRenderer.send('participant-drawing', data); - } - }); - } - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling UserToUser DESKTOP_APP.PARTICIPANT_POINTER event.', e); - } - }); - } - - _userToUserHandler.on('ATC.ADVANCING', function (data) { - try { - - if (data.type === 'ADVANCING') { - LogSvc.debug('[CircuitCallControlSvc]: Received UserToUser ATC.ADVANCING'); - var call = getIncomingCall(data.rtcSessionId); - if (call) { - call.atcAdvancing = true; - call.sessionCtrl.setIgnoreNextConnection(true); - } - } - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling UserToUser ATC.ADVANCING event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.SDP_ANSWER', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.SDP_ANSWER'); - - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - - if (_clientApiHandler.clientId !== evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client: ', _clientApiHandler.clientId); - return; - } - if (localCall.isDirect) { - DeviceDiagnosticSvc.startJoinDelayTimer(localCall); - } - - - $rootScope.$apply(function () { - // Make sure we have a valid SDP - var sdp = evt.sdp; - if (sdp) { - var sdpAnswerInfo = DeviceDiagnosticSvc.createActionInfo(localCall, Constants.RtcDiagnosticsAction.SDP_ANSWER); - - if (!sdp.sdp || sdp.sdp === 'sdp' || sdp.sdp === 'data') { - // The mock keeps getting changed, so we need to adapt... - sdp.sdp = 'sdp'; - LogSvc.debug('[CircuitCallControlSvc]: This is a mocked call'); - localCall.setMockedCall(); - } - - localCall.setInstanceId(evt.instanceId); - // We also need to call setRemoteDescription for mock scenarios in order - // to clear the media renegotiation flags. - localCall.sessionCtrl.setRemoteDescription(sdp, function (err) { - localCall.clearTransactionId(); - - if (err) { - localCall.setDisconnectCause(Constants.DisconnectCause.REMOTE_SDP_FAILED, 'type=' + sdp.type + ' origin=' + sdpParser.getOrigin(sdp.sdp)); - leaveCall(localCall, null, Enums.CallClientTerminatedReason.SET_REMOTE_SDP_FAILED); - if (sdpAnswerInfo) { - sdpAnswerInfo.data = err; - } - } - - DeviceDiagnosticSvc.finishActionInfo(localCall, sdpAnswerInfo); - }); - } - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.SDP_ANSWER event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.ICE_CANDIDATES', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.ICE_CANDIDATES', evt); - - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - - if (_clientApiHandler.clientId !== evt.clientId) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client: ', _clientApiHandler.clientId); - return; - } - - localCall.sessionCtrl.addIceCandidates(evt.origin, evt.candidates); - - var iceInfo = DeviceDiagnosticSvc.createActionInfo(localCall, Constants.RtcDiagnosticsAction.REMOTE_ICE_CANDIDATES); - if (iceInfo) { - iceInfo.data = evt.candidates; - DeviceDiagnosticSvc.finishActionInfo(localCall, iceInfo); - } - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.ICE_CANDIDATES event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.SESSION_UPDATED', function (evt) { - $rootScope.$apply(function () { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_UPDATED'); - - var activeClient; - evt.session.participants.some(function (p) { - if (p.userId === $rootScope.localUser.userId) { - activeClient = p; - return true; - } - }); - - var conversation; - var localCall = findLocalCallByCallId(evt.sessionId); - if (activeClient) { - var activeRemoteCall = getActiveRemoteCall(evt.sessionId); - if (activeRemoteCall) { - LogSvc.debug('[CircuitCallControlSvc]: Event is for an existing active remote call'); - activeRemoteCall.mediaType = Proto.getMediaType(evt.session.mediaTypes); - activeRemoteCall.setActiveClient(activeClient); - publishCallState(activeRemoteCall); - return; - } - - var alertingCall = getIncomingCall(evt.sessionId); - if (alertingCall) { - // For alerting calls, the active remote call is supposed to be created with - // the INVITE_CANCEL message. - LogSvc.debug('[CircuitCallControlSvc]: Event is for an existing active remote call'); - return; - } - - if (!localCall || (localCall.callId !== evt.sessionId && !findActivePhoneCall(true))) { - // The event is not for the local call. We need to create a new active - // remote call in this case. - conversation = ConversationSvc.getConversationByRtcSession(evt.sessionId); - if (!conversation) { - LogSvc.warn('[CircuitCallControlSvc]: Could not find corresponding conversation'); - return; - } - - if (activeClient.clientId === _clientApiHandler.clientId) { - LogSvc.warn('[CircuitCallControlSvc]: Event is for a local call that has been terminated. Ignore event.'); - return; - } - - addRemoteCall(conversation, activeClient, evt.session, function (call) { - call.setCallIdForTelephony(evt.sessionId); - setPeerUserForTelephonyCall(call, evt.session); - }); - return; - } - - // IF we get to this point then the event is for the local call. - - if (activeClient.clientId !== _clientApiHandler.clientId) { - // Call has been moved. Create a new active remote call and terminate the local call. - conversation = getConversation(localCall.convId); - - var phoneNumber = null; - var displayName = null; - var participants = null; - if (localCall.isTelephonyCall) { - phoneNumber = localCall.peerUser.phoneNumber; - displayName = localCall.peerUser.displayName; - participants = localCall.participants; - } - - var atcCallInfo = localCall.atcCallInfo; - terminateCall(localCall, Enums.CallClientTerminatedReason.ANOTHER_CLIENT_PULLED_CALL); - - addRemoteCall(conversation, activeClient, evt.session, function (call) { - call.setCallIdForTelephony(evt.sessionId); - call.atcCallInfo = atcCallInfo; - if (call.isTelephonyCall) { - setCallPeerUser(call, phoneNumber, null, displayName); - call.participants = participants; - } - }); - return; - } - - // Check if the local user has been muted / unmuted - if (localCall.remotelyMuted !== activeClient.muted) { - localCall.remotelyMuted = activeClient.muted; - publishMutedEvent(localCall); - } - } else if (!localCall || !localCall.isLarge) { - LogSvc.info('[CircuitCallControlSvc]: Not a large conference and local user is not in the participants list. Ignore event. '); - return; - } - - if (!localCall.instanceId) { - // Make sure we always have the instanceId set in our call object (important for Qos) - localCall.setInstanceId(evt.session.instanceId); - } - - if (evt.session.muted !== localCall.sessionMuted) { - localCall.sessionMuted = evt.session.muted; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/muted event'); - PubSubSvc.publish('/call/muted', [localCall]); - } - if (evt.session.locked !== localCall.sessionLocked) { - localCall.sessionLocked = evt.session.locked; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/locked event'); - PubSubSvc.publish('/call/locked', [localCall]); - } - - if (evt.stageAction) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/raiseHand/stageAction event'); - PubSubSvc.publish('/call/raiseHand/stageAction', [evt.stageAction]); - } - - if (evt.session.curtain) { - localCall.curtain = evt.session.curtain; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/curtain event (session updated)'); - PubSubSvc.publish('/call/curtain', [localCall.curtain]); - } - - localCall.whiteboardEnabled = !!evt.session.whiteboardEnabled; - - var addedParticipants = []; - var updatedParticipants = []; - - var sessionParticipants = evt.session.participants || []; - - if (!$rootScope.localUser.isCMP) { - // Since we are not a CMP, let's remove ourselves from the participants list - sessionParticipants.some(function (p, idx) { - if (p.userId === $rootScope.localUser.userId) { - sessionParticipants.splice(idx, 1); - return true; - } - }); - } - - var removedParticipantIds; - if (!localCall.isDirect && !localCall.isAtcConferenceCall()) { - // Initialize with previous participants. We'll filter the participants that are - // still in the session below. - removedParticipantIds = localCall.participants - .filter(function (p) { - // Ignore ringing participants that have been added by the local user - return p.pcState !== Enums.ParticipantState.Initiated; - }) - .map(function (p) { - return p.userId; - }); - } - - if (sessionParticipants.length > 0 || localCall.conferenceCall) { - // There are other participants already in the call - - var mediaType = Proto.getMediaType(evt.session.mediaTypes); - // Safeguard if server sends wrong media types - mediaType.audio = mediaType.audio || localCall.localMediaType.audio; - mediaType.video = mediaType.video || localCall.localMediaType.video; - mediaType.desktop = mediaType.desktop || localCall.localMediaType.desktop; - localCall.mediaType = mediaType; - localCall.activeClient = null; - - if (localCall.replaces) { - var replaceCall = findCall(localCall.replaces.callId); - if (replaceCall) { - leaveCall(replaceCall, null, Enums.CallClientTerminatedReason.CALL_MOVED_TO_ANOTHER_CONV); - } - // Inform the UI that call has been moved - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/moved event'); - PubSubSvc.publish('/call/moved', [localCall.replaces.callId, localCall.callId]); - localCall.replaces = null; - } - - if (!localCall.isAtcConferenceCall()) { - sessionParticipants.forEach(function (p) { - var mediaParticipantUpdate = false; - var normalizedParticipant = normalizeApiParticipant(p); - - var pState = normalizedParticipant.muted ? Enums.ParticipantState.Muted : Enums.ParticipantState.Active; - - var callParticipant = localCall.getParticipant(p.userId); - lookupParticipant(normalizedParticipant, localCall); - - if (callParticipant) { - Utils.removeArrayElement(removedParticipantIds, p.userId); - - var oldPcState = callParticipant.pcState; - var oldVideoUrl = callParticipant.videoUrl; - - if (callParticipant.streamId !== normalizedParticipant.streamId || !callParticipant.hasSameMediaType(normalizedParticipant)) { - mediaParticipantUpdate = true; - } - - var updatedParticipant = updateParticipantInCallObj(localCall, normalizedParticipant, pState); - if (updatedParticipant && (mediaParticipantUpdate || updatedParticipant.pcState !== oldPcState || updatedParticipant.videoUrl !== oldVideoUrl)) { - updatedParticipants.push(updatedParticipant); - } - } else { - var addedParticipant = addParticipantToCallObj(localCall, normalizedParticipant, pState); - if (addedParticipant) { - addedParticipants.push(addedParticipant); - } - } - }); - } - } - - var oldState = localCall.state; - updateAttendeeCount(evt.session.attendeeCount); - if (localCall.conferenceCall || localCall.checkState(Enums.CallState.Answering)) { - if (!localCall.isEstablished()) { - localCall.setState(Enums.CallState.Waiting); - } - // Invoke updateCallState so call object changes state to Active if applicable - localCall.updateCallState(); - } - if (!localCall.checkState(oldState)) { - // state has changed - publishCallState(localCall); - } - - conversation = $rootScope.isSessionGuest ? null : getConversation(localCall.convId); - - // Guest participant(s) might have joined. Check if need extra channels. - checkAndAddExtraChannels(conversation); - - if (localCall.conferenceCall && !localCall.hasOtherParticipants()) { - // This is the first user joining the conference - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/starting event'); - PubSubSvc.publish('/conference/starting', [localCall]); - } - - if (removedParticipantIds && removedParticipantIds.length > 0) { - removedParticipantIds.forEach(function (userId) { - var removedParticipant = localCall.removeParticipant(userId); - if (removedParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/removed event. userId = ', userId); - PubSubSvc.publish('/call/participant/removed', [localCall.callId, removedParticipant]); - } - }); - } - - if (updatedParticipants.length > 0) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participants/updated event'); - PubSubSvc.publish('/call/participants/updated', [localCall.callId, updatedParticipants]); - } - if (addedParticipants.length > 0) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participants/added event'); - PubSubSvc.publish('/call/participants/added', [localCall.callId, addedParticipants]); - } - - // Check if the session is being recorded or a recording has been paused - // This block of code (including the event) should not be run while on an echo test call - if (!localCall.isTestCall) { - if (evt.session.recordingInfo && evt.session.recordingInfo.state !== Constants.RecordingInfoState.INITIAL) { - var recordingData = setRecordingInfoData(localCall, evt.session.recordingInfo); - if (recordingData) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/recording/info event'); - PubSubSvc.publish('/call/recording/info', [localCall]); - } - } - } - - // Reset isJoiningGroupCall flag so we don't show a recording notification on the next SESSION_UPDATED - localCall.isJoiningGroupCall = false; - - simulateActiveSpeakers(); - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.SESSION_UPDATED event. ', e); - } - }); - }); - - _clientApiHandler.on('RTCSession.SESSION_STARTED', function (evt) { - if (!_conversationLoaded) { - LogSvc.info('[CircuitCallControlSvc]: Conversations are not loaded. Do not process the SESSION_STARTED event.'); - return; - } - - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_STARTED'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (localCall && localCall.isTestCall) { - LogSvc.debug('[CircuitCallControlSvc]: Received SESSION_STARTED event for echo test call. Just ignore it.'); - return; - } - - if (evt.session.turnServers) { - putSessionTurn(evt.sessionId, evt.session.turnServers); - } - - var conversation = getConversation(evt.convId); - if (!conversation) { - LogSvc.info('[CircuitCallControlSvc]: Could not find corresponding conversation'); - if (evt.callOut) { - return; - } - LogSvc.info('[CircuitCallControlSvc]: Get the conversation from the server'); - ConversationSvc.getConversationById(evt.convId, {randomLoaded: true}, function (err, conversation) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Could not retrieve corresponding conversation. Delete Pending Invite'); - } else { - LogSvc.info('[CircuitCallControlSvc]: Successfully retrieved the conversation. Check if there are any active sessions.'); - _clientApiHandler.getActiveSessions(function (err, activeSessions) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error getting active sessions: ', err); - activeSessions = []; - } - processActiveSessions(conversation, activeSessions); - }); - }); - } - }); - return; - } - - if (!conversation.hasJoined) { - // This should not happen with the actual backend, but the mock also sends the - // events for conversations the user is no longer a participant. - LogSvc.info('[CircuitCallControlSvc]: User is not a conversation participant. Ignore event.'); - return; - } - - $rootScope.$apply(function () { - if (localCall) { - LogSvc.debug('[CircuitCallControlSvc]: Found local call for SESSION_STARTED event'); - LogSvc.info('[CircuitCallControlSvc]: Ignore SESSION_STARTED. State =', localCall.state.name); - } else if (evt.callOut) { - evt.session.participants.some(function (p) { - if (p.userId === $rootScope.localUser.userId && p.clientId !== _clientApiHandler.clientId) { - // This an outgoing call on remote device - addRemoteCall(conversation, p, evt.rtcSession, function (call) { - call.setCallIdForTelephony(evt.sessionId); - call.pullNotAllowed = true; - }); - return true; - } - }); - } else if (conversation.type === Constants.ConversationType.GROUP || conversation.type === Constants.ConversationType.LARGE) { - // This is a new conference call. - // Create a remote call and allow the user to join. - var activeClient = evt.session.participants.find(function (p) { - return p.userId === $rootScope.localUser.userId && p.clientId !== _clientApiHandler.clientId; - }); - addRemoteCall(conversation, activeClient, evt.rtcSession, function (call) { - call.setCallIdForTelephony(evt.sessionId); - - if (!activeClient) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /conference/started event'); - PubSubSvc.publish('/conference/started', [call]); - } - }); - } - updateAttendeeCount(evt.session.attendeeCount); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.SESSION_STARTED event: ', e); - } - }); - - _clientApiHandler.on('RTCSession.PARTICIPANT_JOINED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.PARTICIPANT_JOINED'); - var userId = evt.participant.userId; - if ($rootScope.localUser.userId === userId) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore JOINED event for own user'); - return; - } - - var activeRemoteCall = getActiveRemoteCall(evt.sessionId); - if (activeRemoteCall) { - $rootScope.$apply(function () { - if (activeRemoteCall.pullNotAllowed) { - activeRemoteCall.pullNotAllowed = false; - } - if (activeRemoteCall.isTelephonyCall && !activeRemoteCall.atcCallInfo) { - setCallPeerUser(activeRemoteCall, evt.participant.phoneNumber, null, evt.participant.userDisplayName); - } - }); - return; - } - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local call. Ignore it.'); - return; - } - - if (!localCall.isEstablished() && localCall.direction === Enums.CallDirection.INCOMING) { - // Ignore the JOINED events for incoming alerting calls - LogSvc.info('[CircuitCallControlSvc]: Ignore JOINED event for incoming alerting calls'); - return; - } - - $rootScope.$apply(function () { - if (!!evt.participant.userDisplayName && evt.participant.userDisplayName.startsWith('VoiceMail of')) { - localCall.sessionCtrl.setCallStatsOptions({sendOnlyStream: true}); - } - - if (localCall.isTelephonyCall && !localCall.atcCallInfo) { - setCallPeerUser(localCall, evt.participant.phoneNumber, null, evt.participant.userDisplayName); - } - - if (localCall.checkState([Enums.CallState.Delivered, Enums.CallState.Waiting])) { - if (localCall.checkState(Enums.CallState.Delivered)) { - // This is for outgoing call when the other participant answers the call. - localCall.setState(Enums.CallState.Waiting); - } else if (localCall.conferenceCall && localCall.sessionCtrl.isConnected()) { - // This is for conference call when a second participant joins the call, - // the call state of the first one should be changed to active. - localCall.setState(Enums.CallState.Active); - } - localCall.activeClient = null; - if (localCall.replaces) { - var replaceCall = findCall(localCall.replaces.callId); - if (replaceCall) { - leaveCall(replaceCall, null, Enums.CallClientTerminatedReason.CALL_MOVED_TO_ANOTHER_CONV); - } - // Inform the UI that call has been moved - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/moved event'); - PubSubSvc.publish('/call/moved', [localCall.replaces.callId, localCall.callId]); - localCall.replaces = null; - } - } - - var normalizedParticipant = normalizeApiParticipant(evt.participant); - var pState = normalizedParticipant.muted ? Enums.ParticipantState.Muted : Enums.ParticipantState.Active; - lookupParticipant(normalizedParticipant, localCall); - - if (localCall.hasParticipant(userId)) { - var updatedParticipant = updateParticipantInCallObj(localCall, normalizedParticipant, pState); - if (updatedParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event'); - PubSubSvc.publish('/call/participant/updated', [localCall.callId, updatedParticipant]); - } - } else { - var addedParticipant = addParticipantToCallObj(localCall, normalizedParticipant, pState); - if (addedParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/added event'); - PubSubSvc.publish('/call/participant/added', [localCall.callId, addedParticipant]); - } - } - - var joinDataWrapper = evt.userEnteredStage ? { userEnteredStage: evt.userEnteredStage } : null; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/joined event'); - PubSubSvc.publish('/call/participant/joined', [localCall, localCall.getParticipant(normalizedParticipant.userId), joinDataWrapper]); - simulateActiveSpeakers(); - - // Update the call's media type - localCall.updateMediaType(); - - publishCallState(localCall); - - // We might need extra channels - checkAndAddExtraChannels(); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTC.PARTICIPANT_JOINED event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.SESSION_MOVED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_MOVED'); - - // Is this event for a pending invite - if (_pendingInvites[evt.oldRtcSessionId]) { - // Yes, remove the pending invite. Still unable to process the SESSION_MOVED - LogSvc.info('[CircuitCallControlSvc]: Event is for a pending INVITE. Ignore it.'); - delete _pendingInvites[evt.oldRtcSessionId]; - return; - } - - var call = findCall(evt.oldRtcSessionId); - if (!call) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local or remote call. Ignore it.'); - return; - } - - // Get old and new conversations - var oldConversation = getConversation(evt.oldConversationId); - var newConversation = getConversation(evt.newConversationId); - if (!oldConversation || !newConversation) { - // Wait for conversation service to load the conversation data - LogSvc.warn('[CircuitCallControlSvc]: Could not find corresponding conversations'); - return; - } - - $rootScope.$apply(function () { - updateCall(newConversation, oldConversation, call); - - if (call.isRemote) { - // Session Moved for Remote Call - LogSvc.debug('[CircuitCallControlSvc]: Session Moved for Remote Call'); - return; - } - - if ($window.navigator.platform !== 'iOS') { - // Add extra channels if needed - checkAndAddExtraChannels(newConversation); - } - - // Add New Participants to the call if localUser is the originator of the session move - if (newConversation.creatorId === $rootScope.localUser.userId) { - var oldConvParticipantsHash = {}; - oldConversation.participants.forEach(function (participant) { - oldConvParticipantsHash[participant.userId] = true; - }); - newConversation.participants.forEach(function (participant) { - if (!oldConvParticipantsHash[participant.userId]) { - addParticipant(call, participant); - } - }); - } - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTC.SESSION_MOVED event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.WHITEBOARD_ENABLED', function (evt) { - var call = findCall(evt.sessionId); - if (call) { - $rootScope.$apply(function () { - call.whiteboardEnabled = true; - PubSubSvc.publish('/call/whiteboard/enabled', [call, evt.whiteboard]); - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/whiteboard/enabled event'); - }); - } - }); - - _clientApiHandler.on('RTCSession.WHITEBOARD_DISABLED', function (evt) { - var call = findCall(evt.sessionId); - if (call) { - $rootScope.$apply(function () { - call.whiteboardEnabled = false; - PubSubSvc.publish('/call/whiteboard/disabled', [call]); - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/whiteboard/disabled event'); - }); - } - }); - - _clientApiHandler.on('RTCCall.INVITE', function (evt) { - if (!_conversationLoaded) { - LogSvc.info('[CircuitCallControlSvc]: Conversations are not loaded. Do not process the INVITE event.'); - return; - } - - if ($rootScope.localUser.isCMP) { - if ($rootScope.localUser.userPresenceState.state === Constants.PresenceState.BUSY) { - LogSvc.info('[CircuitCallControlSvc]: CMP is updating its software. Reject the INVITE with cause BUSY.'); - sendBusy(evt); - return; - } - - if (!$rootScope.inviterUserId || ($rootScope.inviterUserId !== evt.userId)) { - LogSvc.info('[CircuitCallControlSvc]: Reject the INVITE with cause BUSY.'); - sendBusy(evt); - return; - } - } - - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.INVITE'); - - var isGuestInvite = evt.isGuestInvite && !$rootScope.localUser.isCMP; - var conversation = getConversation(evt.convId); - if (!conversation) { - LogSvc.info('[CircuitCallControlSvc]: Could not find corresponding conversation. Save pending INVITE and retrieve conversation.'); - // Save the INVITE event and get the conversation - var sessionId = evt.sessionId; - _pendingInvites[sessionId] = evt; - if (isGuestInvite) { - ConversationSvc.createConversationAsGuestFromSummary(evt.convId) - .then(function (conversation) { - if (_pendingInvites[sessionId]) { - LogSvc.info('[CircuitCallControlSvc]: Successfully retrieved conversation. Process the pending INVITE message.'); - processInvite(_pendingInvites[sessionId], conversation); - delete _pendingInvites[sessionId]; - } - }) - .catch(function () { - LogSvc.warn('[CircuitCallControlSvc]: Could not create temporary conversation from summary.'); - delete _pendingInvites[sessionId]; - }); - } else { - ConversationSvc.getConversationById(evt.convId, {randomLoaded: true, sessionId: evt.sessionId}, function (err, conversation) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Could not retrieve corresponding conversation. Delete Pending Invite'); - } else { - if (_pendingInvites[sessionId]) { - LogSvc.info('[CircuitCallControlSvc]: Successfully retrieved conversation. Process the pending INVITE message.'); - processInvite(_pendingInvites[sessionId], conversation); - } else { - LogSvc.info('[CircuitCallControlSvc]: Successfully retrieved conversation. Check if there are any active sessions.'); - _clientApiHandler.getActiveSessions(function (err, activeSessions) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Error getting active sessions: ', err); - activeSessions = []; - } - processActiveSessions(conversation, activeSessions); - }); - }); - } - } - delete _pendingInvites[sessionId]; - }); - } - return; - } else if (isGuestInvite) { - conversation = ConversationSvc.createConversationAsGuest(conversation); - } - - if (!conversation.hasJoined && !isGuestInvite) { - // This should not happen with the actual backend, but the mock also sends the - // events for conversations the user is no longer a participant. - LogSvc.info('[CircuitCallControlSvc]: User is not a conversation participant. Ignore event.'); - return; - } - - $rootScope.$apply(function () { - processInvite(evt, conversation); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.INVITE event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.PARTICIPANT_LEFT', function (evt) { - $rootScope.$apply(function () { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.PARTICIPANT_LEFT'); - var isLocalUser = $rootScope.localUser.userId === evt.userId; - - var localCall = findLocalCallByCallId(evt.sessionId); - var negotiationFailureCauses = [ - Constants.RTCSessionParticipantLeftCause.TRANSPORT_NEGOTIATION_FAILED, - Constants.RTCSessionParticipantLeftCause.NEGOTIATION_FAILED, - Constants.RTCSessionParticipantLeftCause.SECURITY_NEGOTIATION_FAILED - ]; - - if (isLocalUser) { - if (localCall && !localCall.isDirect && !localCall.isEstablished() && !negotiationFailureCauses.includes(evt.cause)) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore PARTICIPANT_LEFT event for non-established group calls'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: Publish /localUser/leftConference event'); - PubSubSvc.publish('/localUser/leftConference', [evt.sessionId]); - } - - var activeRemoteCall = getActiveRemoteCall(evt.sessionId); - if (activeRemoteCall) { - LogSvc.info('[CircuitCallControlSvc]: Event is for active remote call.'); - if (isLocalUser) { - changeRemoteCallToStarted(activeRemoteCall); - } - return; - } - - if (!localCall) { - var alertingCall = getIncomingCall(evt.sessionId); - if (alertingCall && !alertingCall.isDirect && alertingCall.hasParticipant(evt.userId)) { - // Caller left the conference - LogSvc.info('[CircuitCallControlSvc]: Event is for an incoming call. Caller has left the conversation.'); - removeCallParticipant(alertingCall, evt.userId, evt.cause); - // Create a new active remote call and terminate the alerting call. - var alertingConversation = getConversation(alertingCall.convId); - if (alertingConversation) { - addRemoteCall(alertingConversation); - terminateCall(alertingCall, Enums.CallClientTerminatedReason.CALLER_LEFT_CONFERENCE); - } - } else { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local call. Ignore it.'); - } - return; - } - - if (localCall.isTestCall) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore PARTICIPANT_LEFT event for test calls'); - return; - } - - if (localCall.isDirect) { - if (!isLocalUser && (evt.cause === Constants.RTCSessionParticipantLeftCause.CONNECTION_LOST || - evt.cause === Constants.RTCSessionParticipantLeftCause.STREAM_LOST)) { - localCall.setParticipantState(evt.userId, Enums.ParticipantState.ConnectionLost); - } else { - LogSvc.debug('[CircuitCallControlSvc]: Ignore PARTICIPANT_LEFT event for direct calls'); - } - return; - } - - // This is a local GROUP session. - var userId = evt.userId; - var conversation = getConversation(localCall.convId); - if (!isLocalUser) { - if (evt.cause === Constants.RTCSessionParticipantLeftCause.MAX_PARTICIPANTS_REACHED) { - // Participant tried to join, but couldn't due to limitations. - var isConvUser = conversation.participants.some(function (participant) { - return (participant.userId === userId); - }); - var sendEventCb = function (err, user) { - if (err) { return; } - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/rejected event'); - PubSubSvc.publish('/call/participant/rejected', [localCall.callId, user]); - }; - // If the user is in conversation, at first get the participant data. - // Otherwise the participant is a guest, send the event without user data. - isConvUser ? UserSvc.getUserById(userId, sendEventCb) : sendEventCb(); - return; - } - - // Remove the leaving participant - removeCallParticipant(localCall, userId, evt.cause); - } else { - addRemoteCall(conversation); - if (evt.cause === Constants.RTCSessionParticipantLeftCause.REMOVED) { - var requester = localCall.getParticipant(evt.requesterId); - PubSubSvc.publish('/call/droppedRemotely', [localCall, requester]); - } else if (negotiationFailureCauses.includes(evt.cause)) { - onRtcError(localCall, {error: 'res_CallMediaFailed'}); - return; - } - terminateCall(localCall, Enums.CallServerTerminatedReason.SERVER_ENDED_PRFX + evt.cause); - } - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.PARTICIPANT_LEFT event. ', e); - } - }); - }); - - _clientApiHandler.on('RTCCall.SDP_FAILED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.SDP_FAILED with cause =', evt.cause); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - - if (_clientApiHandler.clientId !== evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client'); - return; - } - - if (!localCall.instanceId) { - localCall.setInstanceId(evt.instanceId); - } - - if (evt.cause === Constants.SdpFailedCause.SESSION_TERMINATED) { - LogSvc.debug('[CircuitCallControlSvc]: SDP_FAILED due to session terminated. Ignore'); - return; - } - - $rootScope.$apply(function () { - if (!localCall.isEstablished()) { - if (evt.cause === Constants.SdpFailedCause.MAX_PARTICIPANTS_REACHED) { - onRtcError(localCall, {error: 'res_JoinRTCSessionFailedPermission'}); - } else if (!localCall.isDirect || !localCall.isCallOut) { - // We won't get an INVITE_FAILED for these scenarios, so we have to - // terminate the call here - if (evt.cause === Constants.SdpFailedCause.SESSION_STARTED_FAILED) { - terminateCall(localCall, Enums.CallServerTerminatedReason.SERVER_ENDED_PRFX + evt.cause); - } else { - leaveCall(localCall, null, Enums.CallClientTerminatedReason.REQUEST_TO_SERVER_FAILED); - } - } - } else { - localCall.sessionCtrl.renegotiationFailed('SDP failed received'); - } - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.SDP_FAILED event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.SESSION_TERMINATED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_TERMINATED'); - - if (_sessionHash[evt.sessionId]) { - delete _sessionHash[evt.sessionId]; - } - // Is this event for a pending invite - if (_pendingInvites[evt.sessionId]) { - // Yes, remove the pending invite. Still unable to process the SESSION_TERMINATED - LogSvc.info('[CircuitCallControlSvc]: Event is for a pending INVITE. Ignore it.'); - delete _pendingInvites[evt.sessionId]; - return; - } - - var call = findCall(evt.sessionId); - if (!call) { - LogSvc.warn('[CircuitCallControlSvc]: Could not find call with rtcSessionId =', evt.sessionId); - return; - } - - if (call.terminateTimer) { - LogSvc.info('[CircuitCallControlSvc]: Call is already being terminated. Ignore event'); - return; - } - - if (call.isTelephonyCall && _handoverTimer) { - LogSvc.debug('[CircuitCallControlSvc]: Call is being moved to another device, keep showing'); - call.setAtcHandoverInProgress(); - $timeout.cancel(_handoverTimer); - _handoverTimer = null; - } - - $rootScope.$apply(function () { - var cause = evt.cause; - LogSvc.info('[CircuitCallControlSvc]: Publish /call/terminated. cause = ', cause); - PubSubSvc.publish('/call/terminated', [call, cause]); - terminateCall(call, Enums.CallServerTerminatedReason.SERVER_ENDED_PRFX + cause); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.SESSION_TERMINATED event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.INVITE_CANCEL', function (evt) { - try { - if (_clientApiHandler.clientId === evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore own INVITE_CANCEL'); - return; - } - - // Is this event for a pending invite - if (_pendingInvites[evt.sessionId]) { - // Yes, remove the pending invite. Still unable to process the iNVITE_CANCEL - LogSvc.info('[CircuitCallControlSvc]: Event is for a pending INVITE. Ignore it.'); - delete _pendingInvites[evt.sessionId]; - return; - } - - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.INVITE_CANCEL with cause: ', evt.cause); - var alertingCall = getIncomingCall(evt.sessionId); - if (!alertingCall) { - LogSvc.debug('[CircuitCallControlSvc]: There is no corresponding alerting call'); - if (evt.cause === Constants.InviteCancelCause.ACCEPT) { - var existingCall = findCall(evt.sessionId); - if (existingCall && existingCall.isRemote) { - LogSvc.debug('[CircuitCallControlSvc]: There is a remote call, probably ringing timeout or another client called in'); - existingCall.activeClient = {clientId: evt.clientID}; // Should be updated by subsequent SESSION_UPDATED - existingCall.setState(Enums.CallState.ActiveRemote); - addActiveRemoteCall(existingCall); - publishCallState(existingCall); - } - } - return; - } - - if (!alertingCall.isDirect || evt.cause === Constants.InviteCancelCause.ACCEPT) { - var conversation = getConversation(alertingCall.convId); - var activeClient = evt.cause === Constants.InviteCancelCause.ACCEPT ? {clientId: evt.clientID} : null; - addRemoteCall(conversation, activeClient, null, function (call) { - call.setCallIdForTelephony(evt.sessionId); - if (activeClient && call.isTelephonyCall) { - setCallPeerUser(call, alertingCall.peerUser.phoneNumber, null, alertingCall.peerUser.displayName); - } - }); - } - var reason = Enums.CallClientTerminatedReason.ANOTHER_CLIENT_REJECTED; - if (evt.cause === Constants.InviteCancelCause.ACCEPT) { - reason = Enums.CallClientTerminatedReason.ANOTHER_CLIENT_ANSWERED; - } else if (evt.cause === Constants.InviteCancelCause.REVOKED) { - reason = Enums.CallClientTerminatedReason.ENDED_BY_ANOTHER_USER; - } - terminateCall(alertingCall, reason); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.INVITE_CANCEL event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.INVITE_FAILED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.INVITE_FAILED'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - if (!localCall.isDirect) { - var callParticipant = localCall.getParticipant(evt.userId); - if (evt.to) { - callParticipant.displayName = evt.to.displayName; - var updatedParticipant = updateParticipantInCallObj(localCall, callParticipant); - if (updatedParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event'); - PubSubSvc.publish('/call/participant/updated', [localCall.callId, updatedParticipant]); - } - } - if (callParticipant && callParticipant.isActive()) { - // This may happen as a result of race condition - LogSvc.debug('[CircuitCallControlSvc]: Unexpected event. participant already connected: ', evt.userId); - return; - } - } - - if (evt.clientID && _clientApiHandler.clientId !== evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client: ', _clientApiHandler.clientId); - return; - } - - if (localCall.isTelephonyCall && evt.to) { - setCallPeerUser(localCall, evt.to.phoneNumber, evt.to.fullyQualifiedNumber, evt.to.displayName, evt.to.userId); - } - - $rootScope.$apply(function () { - var call = localCall; - var cause = evt.cause; - var userId = evt.userId; - var pcState; - switch (cause) { - case Constants.InviteFailedCause.NOT_REACHABLE: - pcState = Enums.ParticipantState.Offline; - call.isDirect && call.setState(Enums.CallState.Failed); - break; - case Constants.InviteFailedCause.BUSY: - pcState = Enums.ParticipantState.Busy; - call.isDirect && call.setState(Enums.CallState.Busy); - break; - case Constants.InviteFailedCause.DECLINE: - pcState = Enums.ParticipantState.Declined; - call.isDirect && call.setState(Enums.CallState.Declined); - break; - case Constants.InviteFailedCause.TIMEOUT: - pcState = Enums.ParticipantState.Timeout; - call.isDirect && call.setState(Enums.CallState.NotAnswered); - break; - case Constants.InviteFailedCause.TEMPORARILY_UNAVAILABLE: - case Constants.InviteFailedCause.INVALID_NUMBER: - pcState = Enums.ParticipantState.Busy; - call.isDirect && call.setState(Enums.CallState.Failed); - break; - case Constants.InviteFailedCause.REVOKED: - pcState = Enums.ParticipantState.Removed; - call.isDirect && call.setState(Enums.CallState.Failed); - break; - default: - LogSvc.warn('[CircuitCallControlSvc]: Unexpected RTCCall.INVITE_FAILED cause =', evt.cause); - pcState = Enums.ParticipantState.Busy; - call.isDirect && call.setState(Enums.CallState.Failed); - break; - } - - if (call.isDirect) { - call.setParticipantState(userId, pcState); - publishCallState(call); - if (call.hasVideo()) { - call.removeParticipant(userId); - } - leaveCall(call, null, Enums.CallServerTerminatedReason.SERVER_ENDED_PRFX + cause, true); - } else { - call.setParticipantState(userId, pcState); - var removedParticipant = call.removeParticipant(userId); - if (removedParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/removed event'); - PubSubSvc.publish('/call/participant/removed', [call.callId, removedParticipant]); - - if (call.participants.isEmpty()) { - // Everyone left the conference - if (call.conferenceCall) { - // Conference is still running, make sure Enums.CallState is Waiting - if (!call.checkState(Enums.CallState.Waiting)) { - call.setState(Enums.CallState.Waiting); - publishCallState(call); - } - } else { - call.setState(Enums.CallState.NotAnswered); - publishCallState(call); - leaveCall(null, null, Enums.CallServerTerminatedReason.SERVER_ENDED_PRFX + cause, true); - } - } - } - } - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTC.INVITE_FAILED event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.ACTIVE_SPEAKER', onActiveSpeakerEvent); - - _clientApiHandler.on('RTCSession.VIDEO_ACTIVE_SPEAKER', onActiveVideoSpeakerEvent); - - _clientApiHandler.on('RTCCall.CHANGE_MEDIA_TYPE_REQUESTED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.CHANGE_MEDIA_TYPE_REQUESTED'); - var alertingCall = getIncomingCall(evt.sessionId); - if (alertingCall) { - sendChangeMediaReject(evt.sessionId, evt.transactionId); - return; - } - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall || localCall.isRemote) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local call. Ignore it.'); - return; - } - - if (evt.clientID && _clientApiHandler.clientId !== evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client: ', _clientApiHandler.clientId); - return; - } - if (localCall.activeClient) { - LogSvc.debug('[CircuitCallControlSvc]: Pull in progress. leave it for active client to handle'); - return; - } - if ((!localCall.isEstablished() && localCall.direction === Enums.CallDirection.INCOMING) || !localCall.isDirect) { - sendChangeMediaReject(localCall.callId, evt.transactionId); - return; - } - - $rootScope.$apply(function () { - if (evt.sdp) { - var sessionCtrl = localCall.sessionCtrl; - if (!sessionCtrl.isConnStable()) { // A SDP handling is already under way - sendChangeMediaReject(localCall.callId, evt.transactionId); - return; - } - localCall.setTransactionId(evt.transactionId); - - if (localCall.isTelephonyCall && localCall.atcAdvancing && evt.sdp.type !== 'nooffer') { - // If PBX is 4K, one change_media_request without SDP is received when client answers after advancing the call. - // OSV sends two change_media_request(s) with SDP. - localCall.sessionCtrl.setIgnoreTurnOnNextPC(); - } - - var onSdp = function (evt) { - sessionCtrl.onSessionDescription = null; - - var changeMediaAcceptData = { - rtcSessionId: localCall.callId, - sdp: evt.sdp, - transactionId: localCall.transactionId - }; - _clientApiHandler.changeMediaAccept(changeMediaAcceptData, function (err) { - $rootScope.$apply(function () { - localCall.clearTransactionId(); - if (err) { - LogSvc.error('[CircuitCallControlSvc]: changeMediaAccept sdp answer sending error'); - } else { - LogSvc.debug('[CircuitCallControlSvc]: changeMediaAccept sdp answer sent'); - } - }); - }); - }; - - localCall.atcAdvancing = false; - getTurnCredentials(localCall) - .then(function (turnCredentials) { - sessionCtrl.setTurnCredentials(turnCredentials); - if (sdpParser.isNoOfferSdp(evt.sdp)) { - changeMediaType(localCall.callId, localCall.transactionId, localCall.localMediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to renegotiate the media'); - } - }); - } else { - sessionCtrl.onSessionDescription = onSdp; - sessionCtrl.setRemoteDescription(evt.sdp, function (err) { - if (err) { - sessionCtrl.onSessionDescription = null; - if (localCall.transactionId === evt.transactionId) { - sendChangeMediaReject(localCall.callId, evt.transactionId); - } else { - LogSvc.warn('[CircuitCallControlSvc]: TransactionId: ' + evt.transactionId + ' has already been completed.'); - } - } - }); - } - }) - .catch(function () { - sendChangeMediaReject(localCall.callId, evt.transactionId); - }); - } else { - sendChangeMediaReject(localCall.callId, evt.transactionId); - } - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.CHANGE_MEDIA_REQUESTED event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.CHANGE_MEDIA_TYPE_FORCED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.CHANGE_MEDIA_TYPE_FORCED'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local call. Ignore it.'); - return; - } - if (localCall.isDirect) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not applicable for direct calls. Ignore it.'); - return; - } - if (evt.clientID && _clientApiHandler.clientId !== evt.clientID) { - LogSvc.debug('[CircuitCallControlSvc]: Event is not for this client: ', _clientApiHandler.clientId); - return; - } - if (localCall.activeClient) { - LogSvc.debug('[CircuitCallControlSvc]: Pull in progress. leave it for active client to handle'); - return; - } - if (evt.removeMediaTypes) { - if (localCall.localMediaType.desktop && evt.removeMediaTypes.includes(Constants.RealtimeMediaType.DESKTOP_SHARING)) { - $rootScope.$apply(function () { - forceStopScreenshare(localCall, evt.userId); - }); - } - if (evt.removeMediaTypes.includes(Constants.RealtimeMediaType.AUDIO)) { - $rootScope.$apply(function () { - _that.removeAudio(localCall.callId); - }); - } - } - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.CHANGE_MEDIA_TYPE_FORCED event. ', e); - } - }); - - _clientApiHandler.on('RTCCall.RTC_QUALITY_RATING_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.RTC_QUALITY_RATING_EVENT'); - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/showRatingDialog event'); - PubSubSvc.publish('/call/showRatingDialog', [evt]); - }); - }); - - _clientApiHandler.on('RTCSession.PARTICIPANT_UPDATED', function (evt) { - try { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.PARTICIPANT_UPDATED'); - - var localCall = findLocalCallByCallId(evt.sessionId); - var call = getActiveRemoteCall(evt.sessionId) || localCall; - if (call && call.callId === evt.sessionId && call.isTelephonyCall && !call.atcCallInfo && $rootScope.localUser.userId !== evt.participant.userId) { - setCallPeerUser(call, evt.participant.phoneNumber, null, evt.participant.userDisplayName); - } - - if (!localCall) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local call. Ignore it.'); - return; - } - - $rootScope.$apply(function () { - var userId = evt.participant.userId; - var muted = evt.participant.muted; - if ($rootScope.localUser.userId === userId) { - if (localCall.remotelyMuted !== muted) { - localCall.remotelyMuted = muted; - publishMutedEvent(localCall); - } - return; - } - - if (!localCall.isEstablished() && localCall.direction === Enums.CallDirection.INCOMING) { - // Ignore the UPDATED events for incoming alerting calls - LogSvc.debug('[CircuitCallControlSvc]: Ignore UPDATED event for incoming alerting calls'); - return; - } - - - var callParticipant = localCall.getParticipant(evt.participant.userId); - if (!callParticipant) { - LogSvc.debug('[CircuitCallControlSvc]: Ignore UPDATED event, can not find participant to update for userId: ', userId); - return; - } - var normalizedParticipant = normalizeApiParticipant(evt.participant); - var pState = normalizedParticipant.muted ? Enums.ParticipantState.Muted : Enums.ParticipantState.Active; - - var mutedUpdate = (callParticipant.muted !== normalizedParticipant.muted); - var mediaUpdate = !callParticipant.hasSameMediaType(normalizedParticipant) || - callParticipant.streamId !== normalizedParticipant.streamId; - - var updatedParticipant = updateParticipantInCallObj(localCall, normalizedParticipant, pState); - if (updatedParticipant) { - lookupParticipant(updatedParticipant, localCall); - - if (mediaUpdate) { - // Update the call's media type - localCall.updateMediaType(); - } - - if (mediaUpdate || mutedUpdate) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/participant/updated event'); - PubSubSvc.publish('/call/participant/updated', [localCall.callId, updatedParticipant]); - } - } - publishCallState(localCall); - }); - - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCSession.PARTICIPANT_UPDATED event. ', e); - } - }); - - _clientApiHandler.on('RTCSession.SESSION_RECORDING_INFO', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_RECORDING_INFO'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - var data = setRecordingInfoData(localCall, evt.recordingInfo); - if (data) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/recording/info event'); - PubSubSvc.publish('/call/recording/info', [localCall]); - } - }); - }); - - _clientApiHandler.on('RTCSession.SESSION_TERMINATION_TIMER_STARTED', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_TERMINATION_TIMER_STARTED'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/sessionTerminateTimerChanged (started)'); - PubSubSvc.publish('/call/sessionTerminateTimerChanged', [localCall.callId, evt.endTime]); - }); - }); - - _clientApiHandler.on('RTCSession.SESSION_TERMINATION_TIMER_CANCELLED', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_TERMINATION_TIMER_CANCELLED'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/sessionTerminateTimerChanged (cancelled)'); - PubSubSvc.publish('/call/sessionTerminateTimerChanged', [localCall.callId, null]); - }); - }); - - _clientApiHandler.on('RTCSession.SESSION_ATTRIBUTES_CHANGED_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.SESSION_ATTRIBUTES_CHANGED_EVENT'); - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - if (evt.attendeeCount === undefined || localCall.attendeeCount === evt.attendeeCount) { - LogSvc.debug('[CircuitCallControlSvc]: No changes'); - return; - } - $rootScope.$apply(function () { - updateAttendeeCount(evt.attendeeCount); - }); - }); - - _clientApiHandler.on('RTCCall.PROGRESS', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCCall.PROGRESS'); - - var localCall = findLocalCallByCallId(evt.sessionId); - if (!localCall) { - LogSvc.info('[CircuitCallControlSvc]: Event is not for local or remote call. Ignore it.'); - return; - } - - if (localCall.isDirect) { - if (!evt.sdp && evt.progressType === Constants.RTCProgressType.ALERTING) { - localCall.receivedAlerting = true; - LogSvc.debug('[CircuitCallControlSvc]: Publish /sound/call/alerting/started, call ID: ', localCall.callId); - PubSubSvc.publish('/sound/call/alerting/started', [localCall]); - } else if (evt.sdp && localCall.receivedAlerting) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /sound/call/alerting/stopped, call ID: ', localCall.callId); - PubSubSvc.publish('/sound/call/alerting/stopped', [localCall]); - } - } - - if (!localCall.checkState([Enums.CallState.Initiated, Enums.CallState.Delivered])) { - LogSvc.info('[CircuitCallControlSvc]: Ignore PROGRESS. Call state =', localCall.state.name); - return; - } - - if (evt.to && localCall.isTelephonyCall) { - setCallPeerUser(localCall, evt.to.phoneNumber, evt.to.fullyQualifiedNumber, evt.to.displayName, evt.to.userId); - } - - try { - $rootScope.$apply(function () { - if (evt.sdp && (evt.sdp.type === 'answer' || evt.sdp.type === 'pranswer')) { - // This is an early media (pranswer) indication. - evt.sdp.type = 'pranswer'; - localCall.isPranswer = true; - localCall.sessionCtrl.setRemoteDescription(evt.sdp, function (err) { - if (err) { - localCall.setDisconnectCause(Constants.DisconnectCause.REMOTE_SDP_FAILED, 'type=' + evt.sdp.type + ' origin=' + sdpParser.getOrigin(evt.sdp.sdp)); - leaveCall(localCall, null, Enums.CallClientTerminatedReason.SET_REMOTE_SDP_FAILED); - } - }); - } - localCall.setState(Enums.CallState.Delivered); - publishCallState(localCall); - }); - } catch (e) { - LogSvc.error('[CircuitCallControlSvc]: Exception handling RTCCall.PROGRESS event.', e); - } - }); - - _clientApiHandler.on('RTCSession.INVITE_TO_STAGE_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.INVITE_TO_STAGE_EVENT'); - if (!findLocalCallByCallId(evt.sessionId)) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - var invitationData = { - invitedByUserId: evt.invitedByUserId, - allowedMediaTypes: evt.allowedMediaTypes, - expirationTime: Utils.delayOrTimestamp(evt.expirationDelay, evt.expirationTime) - }; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/raiseHand/invite, questionNumber: ', evt.questionNumber); - PubSubSvc.publish('/call/raiseHand/invite', [evt.questionNumber, invitationData]); - }); - }); - - _clientApiHandler.on('RTCSession.INVITE_TO_STAGE_CANCEL_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.INVITE_TO_STAGE_CANCEL_EVENT'); - if (!findLocalCallByCallId(evt.sessionId)) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/raiseHand/invite (cancel), questionNumber: ', evt.questionNumber); - PubSubSvc.publish('/call/raiseHand/invite', [evt.questionNumber, null]); - }); - }); - - _clientApiHandler.on('RTCSession.QUESTION_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.QUESTION_EVENT'); - if (!findLocalCallByCallId(evt.sessionId)) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/raiseHand/question, questionNumber: ', evt.question.questionNumber); - PubSubSvc.publish('/call/raiseHand/question', [evt.question, evt.subType]); - }); - }); - - _clientApiHandler.on('RTCSession.CURTAIN_EVENT', function (evt) { - LogSvc.debug('[CircuitCallControlSvc]: Received RTCSession.CURTAIN_EVENT'); - if (!findLocalCallByCallId(evt.sessionId)) { - LogSvc.warn('[CircuitCallControlSvc]: Unexpected event. There is no local call'); - return; - } - $rootScope.$apply(function () { - _primaryLocalCall.curtain = evt.curtain; - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/curtain event'); - PubSubSvc.publish('/call/curtain', [_primaryLocalCall.curtain]); - }); - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - /** - * Used by SDK to initialize active sessions. - * Returns a promise that is fullfilled when all sessions have been processed - * and the different calls arrays are populated. - */ - this.initActiveSessionsForSdk = function () { - _conversationLoaded = true; - return initActiveSessions(); - }; - - /** - * Used by unit tests to place the CircuitCallControlSvc in a state where it can - * start processing the RTC events. - */ - this.initForUnitTests = function () { - _conversationLoaded = true; - }; - - /** - * Set indication of whether incoming remote video should be disabled by default for - * new incoming and outgoing Circuit calls. - . * - * @param {boolean} value true indicates that incoming video is disabled by default. - */ - this.setDisableRemoteVideoByDefault = function (value) { - LogSvc.info('[CircuitCallControlSvc] Setting _disableRemoteVideoByDefault to ', !!value); - _disableRemoteVideoByDefault = !!value; - }; - - /** - * Get indication of whether incoming remote video is currently disabled by default. - . * - * @returns {boolean} true indicates that incoming video is disabled by default. - */ - this.getDisableRemoteVideoByDefault = function () { - return _disableRemoteVideoByDefault; - }; - - /** - * Set indication of whether Client Diagnostics are enabled/disabled - . - * - * @param {boolean} value true=disabled; false=enabled - */ - this.setClientDiagnosticsDisabled = function (value) { - _clientDiagnosticsDisabled = value; - }; - - /** - * Get all of the calls. - * - * @returns {Array} An array of Call object. - */ - this.getCalls = function () { - return _calls; - }; - - /** - * Get all the phone calls - * - * @param {boolean} onlyLocal true=only local phone calls - * @return {Array} An array of Call object - */ - this.getPhoneCalls = function (onlyLocal) { - return _calls.filter(function (call) { - return (call.isTelephonyCall && (!onlyLocal || !call.isRemote)); - }); - }; - - this.getEstablishedLocalPhoneCalls = function () { - return _calls.filter(function (call) { - return (call.isTelephonyCall && call.state.established && !call.isRemote); - }); - }; - - /** - * Get the active local WebRTC call. - * - * @param {String} callId The ID of existing group call or conference - * @returns {LocalCall} The LocalCall object. - */ - this.getActiveCall = function (callId) { - if (callId) { - return findLocalCallByCallId(callId); - } - if (_primaryLocalCall && _primaryLocalCall.replaces) { - return _primaryLocalCall.replaces; - } - return _primaryLocalCall || null; - }; - - /** - * Get the ringing incoming WebRTC call, - * null will be returned if there is no incoming call. - * - * @returns {LocalCall} The LocalCall object. - */ - this.getIncomingCall = function () { - return _incomingCalls[0] || null; - }; - - /** - * Get the active remote WebRTC call. - * - * @returns {Array} The RemoteCall object array. - */ - this.getActiveRemoteCall = function () { - return _activeRemoteCalls; - }; - - /** - * Join an existing group call or conference - * - * @param {String} callId The ID of existing group call or conference - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.joinGroupCall = function (callId, mediaType, cb) { - cb = cb || function () {}; - if (!callId) { - LogSvc.warn('[CircuitCallControlSvc]: joinGroupCall - Invalid Call Id'); - cb('Missing callId'); - return; - } - var call = findCall(callId); - if (!call || !call.isRemote || call.state.name !== Enums.CallState.Started.name || !!call.activeClient) { - LogSvc.warn('[CircuitCallControlSvc]: joinGroupCall invoked without a valid call'); - cb('Invalid call'); - return; - } - var conversation = getConversation(call.convId); - if (!conversation || !conversation.call || conversation.call.callId !== call.callId) { - LogSvc.warn('[CircuitCallControlSvc]: joinGroupCall - Cannot find valid conversation'); - cb('Invalid conversation'); - return; - } - var options = { - callOut: false, - handover: false, - joiningGroupCall: true - }; - checkMediaSourcesAndJoin(conversation, mediaType, options, cb); - }; - - /** - * Make a new outgoing call in an existing conversation. - * - * @param {String} convId The conversation ID for the conversation to start the call. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.makeCall = function (convId, mediaType, cb) { - cb = cb || function () {}; - if (!convId) { - LogSvc.warn('[CircuitCallControlSvc]: makeCall - Invalid Conversation Id'); - cb('Missing convId'); - return; - } - var conversation = getConversation(convId); - if (!conversation) { - LogSvc.warn('[CircuitCallControlSvc]: makeCall - Cannot find conversation with id ', convId); - cb('Conversation not found'); - return; - } - checkMediaSourcesAndJoin(conversation, mediaType, {callOut: true, handover: false}, cb); - }; - - /** - * Make a test call. - * - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} onConversationCreated A callback function that is immediately invoked with the rtcSessionId for the test call. - * @param {Function} onCallStarted A callback function when the call is started or in case there is an error. - */ - this.makeEchoTestCall = function (mediaType, onConversationCreated, onCallStarted) { - onConversationCreated = onConversationCreated || function () {}; - onCallStarted = onCallStarted || function () {}; - // If the guest is registered for test call, use conversationId, otherwise use clientId - var testConvId = $rootScope.isSessionGuest && $rootScope.localUser.conversationId || - ('echo_' + $rootScope.localUser.clientId); - - var conversation = { - creatorId: $rootScope.localUser.userId, - convId: testConvId, - rtcSessionId: 'echo_' + $rootScope.localUser.clientId, - participants: [{userId: $rootScope.localUser.userId}], - testCall: true - }; - conversation = Conversation.extend(conversation); - onConversationCreated(conversation.rtcSessionId); - - // Wait a little before initiating the test call - $timeout(function () { - joinSession(conversation, mediaType, {callOut: false, handover: false}, onCallStarted); - }, 200); - }; - - /** - * Make a new outgoing call to a given DN. - * - * @param {String} dialedDn The dialed number. - * @param {String} toName The display name of the called circuit user - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error, executed when the call is successfully placed. - */ - this.dialNumber = function (dialedDn, toName, mediaType, cb) { - cb = cb || function () {}; - ConversationSvc.getTelephonyConversation(function (err, conversation) { - if (err || !conversation) { - cb(err); - } else { - checkMediaSourcesAndJoin(conversation, mediaType, {callOut: true, handover: false, dialedDn: dialedDn, toName: toName}, cb); - } - }); - }; - - /** - * Make a new outgoing call to a given DN. - * - * @param {Object} [destination] Object The destination to be called. - * @param {String} [destination.dialedDn] The dialed number. - * @param {String} [destination.toName] The display name of the called circuit user. - * @param {String} [destination.userId] Id of the dialed user (if available). Otherwise null or empty string. - * @param {Object} [destination.mediaType] The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error, executed when the call is successfully placed. - */ - this.dialPhoneNumber = function (destination, cb) { - cb = cb || function () {}; - destination = destination || {}; - ConversationSvc.getTelephonyConversation(function (err, conversation) { - if (err || !conversation) { - cb(err); - } else { - if (!canInitiateCall(conversation, false, cb)) { - return; - } - if (_primaryLocalCall && _primaryLocalCall.isTelephonyCall && !_primaryLocalCall.isHolding()) { - _that.holdCall(_primaryLocalCall.callId, function (err) { - if (err) { - cb && cb(err); - } else { - checkMediaSourcesAndJoin(conversation, destination.mediaType, { - callOut: true, - handover: false, - dialedDn: destination.dialedDn, - toName: destination.toName, - userId: destination.userId - }, cb); - } - }); - return; - } - checkMediaSourcesAndJoin(conversation, destination.mediaType, { - callOut: true, - handover: false, - dialedDn: destination.dialedDn, - toName: destination.toName, - userId: destination.userId - }, cb); - } - }); - }; - - /** - * Start a conference in an existing group conversation. - * - * @param {String} convId The ID of existing group conversation to start the conference. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.startConference = function (convId, mediaType, cb) { - mediaType = mediaType || { audio: true, video: false }; - cb = cb || function () {}; - if (!convId) { - LogSvc.warn('[CircuitCallControlSvc]: startConference - Invalid Conversation Id'); - cb('Missing convId'); - return; - } - var conversation = getConversation(convId); - if (!conversation) { - LogSvc.warn('[CircuitCallControlSvc]: startConference - Cannot find conversation with id ', convId); - cb('Conversation not found'); - return; - } - if (conversation.type !== Constants.ConversationType.GROUP && conversation.type !== Constants.ConversationType.LARGE) { - LogSvc.warn('[CircuitCallControlSvc]: startConference - Invalid conversation type'); - cb('Invalid conversation type'); - return; - } - checkMediaSourcesAndJoin(conversation, mediaType, {callOut: false, handover: false}, cb); - }; - - /** - * Answer the incoming call. Any existing active call will be terminated. - * - * @param {String} callId The call ID of incoming call to be answered. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.answerCall = function (callId, mediaType, cb) { - cb = cb || function () {}; - var incomingCall = null; - var incomingCallIdx; - _incomingCalls.some(function (call, idx) { - if (callId === call.callId) { - incomingCall = call; - incomingCallIdx = idx; - return true; - } - }); - if (!incomingCall) { - LogSvc.warn('[CircuitCallControlSvc]: answerCall - There is no alerting call'); - cb('No alerting call'); - return; - } - - function answerCallContinue() { - _secondaryLocalCall = _primaryLocalCall; - _primaryLocalCall = incomingCall; - simulateActiveSpeakers(); - - _incomingCalls.splice(incomingCallIdx, 1); - dismissNotification(_primaryLocalCall); - - //Cancel ringing timer if running - _primaryLocalCall.stopRingingTimer(); - - _primaryLocalCall.setState(_primaryLocalCall.isATCCall ? Enums.CallState.Waiting : Enums.CallState.Answering); - - var conversation = getConversation(_primaryLocalCall.convId); - if (!conversation || !conversation.call || conversation.call.callId !== _primaryLocalCall.callId) { - LogSvc.warn('[CircuitCallControlSvc]: joinCall - Cannot find valid conversation'); - decline(_primaryLocalCall, {type: Constants.InviteRejectCause.BUSY}, false); - cb('Conversation not found'); - return; - } - - publishCallState(_primaryLocalCall); - checkMediaSources(mediaType, function (normalizedMediaType, warning) { - joinSessionCalled(conversation, normalizedMediaType, {warning: warning}, cb); - }); - } - - if (_primaryLocalCall && _primaryLocalCall.isPresent() && (!_primaryLocalCall.isTelephonyCall || - !incomingCall.isTelephonyCall || (_isMobile && !_isIOS)) && !incomingCall.replaces || !$rootScope.localUser.isATC) { - leaveCall(_primaryLocalCall, null, Enums.CallClientTerminatedReason.USER_ENDED); - if (_secondaryLocalCall) { - leaveCall(_secondaryLocalCall, null, Enums.CallClientTerminatedReason.USER_ENDED); - } - } else if (_primaryLocalCall && _primaryLocalCall.isTelephonyCall && $rootScope.localUser.isATC && !_primaryLocalCall.isHolding()) { - _that.holdCall(_primaryLocalCall.callId, function (err) { - if (err) { - cb && cb(err); - } else { - answerCallContinue(); - } - }); - return; - } - - answerCallContinue(); - }; - - /** - * End a local call. - * - * @param {String} callId The call ID of local call to be terminated. - * @param {Function} cb A callback function replying with an error - */ - this.endCall = function (callId, cb) { - var cause = Constants.InviteRejectCause.DECLINE; - var activeCall = _that.getActiveCall(); - if (activeCall && activeCall.callId === callId) { - if (activeCall.isEstablished() || activeCall.direction === Enums.CallDirection.OUTGOING) { - cause = null; // This will be defaulted to USER_ENDED - } - } - _that.endCallWithCauseCode(callId, cause, cb); - }; - - /** - * End a local call specifying the cause. - * - * @param {String} callId The call ID of local call to be terminated. - * @param {Constants.InviteRejectCause} cause Cause code for terminating the call. - * @param {Function} cb A callback function replying with an error - */ - this.endCallWithCauseCode = function (callId, cause, cb) { - var qosCause; - if (cause) { - if (typeof cause === 'string' && cause.startsWith(Enums.CallClientTerminatedReason.CLIENT_ENDED_PRFX)) { - qosCause = cause; - } else { - qosCause = Enums.CallClientTerminatedReason.CLIENT_ENDED_PRFX + cause; - } - } else { - qosCause = Enums.CallClientTerminatedReason.USER_ENDED; - } - var localCall = findLocalCallByCallId(callId); - if (localCall) { - if (localCall.replaces && localCall.checkState(Enums.CallState.Answering)) { - cb('Auto answer in progress'); - } else { - if (localCall.terminateTimer) { - terminateCall(localCall, qosCause); - // Only initiate timeout if there is a callback - cb && $timeout(cb); - } else { - leaveCall(localCall, cause, qosCause, false, cb); - } - } - } else if (_primaryLocalCall && _primaryLocalCall.replaces && _primaryLocalCall.replaces.callId === callId) { - leaveCall(_primaryLocalCall.replaces, cause, qosCause, false, cb); - } else { - var activeCall = findCall(callId); - if (activeCall) { - if (activeCall.terminateTimer) { - terminateCall(activeCall); - cb && $timeout(cb); - } else { - leaveCall(activeCall, cause, qosCause, false, cb); - } - return; - } - var alertingCall = getIncomingCall(callId); - if (alertingCall) { - decline(alertingCall, {type: cause || Constants.InviteRejectCause.DECLINE}, false, cb); - } else { - LogSvc.warn('[CircuitCallControlSvc]: endCall - There is no local or alerting call'); - var conversation = ConversationSvc.getConversationByRtcSession(callId); - if (conversation && conversation.call) { - conversation.call = null; - publishConversationUpdate(conversation); - } - - cb && cb('Call not found'); - } - } - }; - - /** - * End conference (terminate rtc call). - * - * @param {String} callId The call ID of local call used to terminate the conference. - * @param {Function} cb A callback function replying with an error - */ - this.endConference = function (callId, cb) { - var call = findCall(callId); - if (!call) { - cb && cb('Call not found'); - } else if ($rootScope.isSessionGuest || call.isGuestInvite) { - cb && cb('Not allowed'); - } else { - terminateConference(call, cb); - } - }; - - /** - * Start a session with screen-share. - * - * @param {String} convId The ID of existing conversation to start the call. - * @param {String} screenConstraint for Firefox (i.e. 'screen', 'window', 'application'). - * @param {Function} cb A callback function replying with an error - */ - this.startScreenShare = function (convId, screenConstraint, cb) { - cb = cb || function () {}; - if (!convId) { - LogSvc.warn('[CircuitCallControlSvc]: startScreenShare - Invalid Conversation Id'); - cb('Invalid convId'); - return; - } - var conversation = getConversation(convId); - if (!conversation) { - LogSvc.warn('[CircuitCallControlSvc]: startScreenShare - Cannot find conversation with id ', convId); - cb('Conversation not found'); - return; - } - // For now, let's start all screen-sharing sessions together with audio. - // We will re-evaluate this later, once we start supporting adding/removing - // different media types during the call. - var mediaType = {audio: true, video: false, desktop: true}; - var options = { - callOut: true, - handover: false, - screenConstraint: screenConstraint - }; - - joinSession(conversation, mediaType, options, function (err) { - if (!err) { - LogSvc.debug('[CircuitCallControlSvc]: Publish /screenshare/started event'); - PubSubSvc.publish('/screenshare/started'); - } - cb(err); - }); - }; - - /** - * Add screen-share to an existing RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {String} screenConstraint for Firefox (i.e. 'screen', 'window', 'application'). - * @param {Function} cb A callback function replying with an error - */ - this.addScreenShare = function (callId, screenConstraint, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: addScreenShare - There is no local call'); - cb('No active call'); - return; - } - - var mediaType = { - audio: localCall.localMediaType.audio, - video: false, // only support either video or desktop for now - desktop: true - }; - - getScreen(localCall) - .then(function (data) { - var screenShareData = { - streamId: data.streamId, - screenType: screenConstraint, - pointer: data.pointer - }; - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: AddScreenShare failed: ', err); - cb(err); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: Publish /screenshare/started event'); - PubSubSvc.publish('/screenshare/started'); - cb(); - }, screenShareData); - }) - .catch(function (error) { - cb(error); - }); - }; - - /** - * Add a media stream to an existing RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {MediaStream} stream The media stream to share. - * @param {Function} cb A callback function replying with an error - */ - this.addMediaStream = function (callId, stream, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: addMediaStream - There is no local call'); - cb('No active call'); - return; - } - - var mediaType = { - audio: localCall.localMediaType.audio, - video: false, // only support either video or desktop for now - desktop: true - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: addMediaStream failed: ', err); - cb('res_AddMediaStreamFailed'); - } else { - cb(); - } - }, {mediaStream: stream}); - }; - - /** - * Remove a media stream from an existing RTC session. - * - * @param {String} callId The call ID of call to remove stream from. - * @param {Function} cb A callback function replying with an error - */ - this.removeMediaStream = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: removeMediaStream - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: removeMediaStream...'); - - var mediaType = { - audio: localCall.localMediaType.audio, - video: localCall.localMediaType.video, - desktop: false - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: removeMediaStream failed: ', err); - cb('res_RemoveMediaStreamFailed'); - } else { - cb(); - } - }); - }; - - /** - * Select new desktop media to share in an existing screen-share RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {String} screenConstraint for Firefox (i.e. 'screen', 'window', 'application'). - * @param {Function} cb A callback function replying with an error - */ - this.selectMediaToShare = function (callId, screenConstraint, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: selectMediaToShare - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: selectMediaToShare...'); - - var mediaType = { - audio: localCall.localMediaType.audio, - video: false, // only support either video or desktop for now - desktop: true - }; - - getScreen(localCall) - .then(function (data) { - var screenShareData = { - streamId: data.streamId, - screenType: screenConstraint, - pointer: data.pointer - }; - changeMediaType(callId, null, mediaType, true, null, cb, screenShareData); - }) - .catch(function (error) { - cb(error); - }); - - PubSubSvc.publish('/screenshare/changed'); - }; - - /** - * Remove screen-share from an existing RTC session. - * - * @param {String} callId The call ID of call to remove screen share from. - * @param {Function} cb A callback function replying with an error - */ - this.removeScreenShare = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: removeScreenShare - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: removeScreenShare...'); - - var mediaType = { - audio: localCall.localMediaType.audio, - video: localCall.localMediaType.video, - desktop: false - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: removeScreenShare failed: ', err); - cb('res_RemoveScreenShareFailed'); - } else { - cb(); - } - }); - }; - - /** - * Enable audio in an existing RTC session. - * - * @param {String} callId The call ID of call to add audio to. - * @param {Function} cb A callback function replying with an error - */ - this.addAudio = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: addAudio - There is no local call'); - cb('No active call'); - return; - } - - var mediaType = { - audio: true, - video: localCall.localMediaType.video, - desktop: localCall.localMediaType.desktop - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: addAudio -> Failed to renegotiate the media. ', err); - cb('Renegotiate for receive-only media failed'); - } else { - LogSvc.debug('[CircuitCallControlSvc]: addAudio -> Media renegotiation was successful'); - cb(); - } - }); - }; - - /** - * Disable audio in an existing RTC session. - * - * @param {String} callId The call ID of call to remove audio from. - * @param {Function} cb A callback function replying with an error - */ - this.removeAudio = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: removeAudio - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: removeAudio...'); - - var mediaType = { - audio: false, - video: localCall.localMediaType.video, - desktop: localCall.localMediaType.desktop - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: removeAudio -> Failed to renegotiate the media. ', err); - cb('Renegotiate for receive-only media failed'); - } else { - LogSvc.debug('[CircuitCallControlSvc]: removeAudio -> Media renegotiation was successful'); - cb(); - } - }); - }; - - /** - * Enable remote audio stream in an active call - * - * @param {String} callId The call ID to be muted - */ - this.enableRemoteAudio = function (callId) { - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: enableRemoteAudio - There is no local call'); - return; - } - - if (localCall.remoteAudioDisabled) { - localCall.enableRemoteAudio(); - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/remoteStreamUpdated event'); - PubSubSvc.publish('/call/remoteStreamUpdated', [localCall]); - } - }; - - /** - * Disable remote audio stream in an active call - * - * @param {String} callId The call ID to be muted - */ - this.disableRemoteAudio = function (callId) { - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: disableRemoteAudio - There is no local call'); - return; - } - - if (!localCall.remoteAudioDisabled) { - localCall.disableRemoteAudio(); - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/remoteStreamUpdated event'); - PubSubSvc.publish('/call/remoteStreamUpdated', [localCall]); - } - }; - - /** - * Enable video in an existing RTC session. - * - * @param {String} callId The call ID of call to add video to. - * @param {Function} cb A callback function replying with an error - */ - this.addVideo = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: addVideo - There is no local call'); - cb('No active call'); - return; - } - - var mediaType = { - audio: localCall.localMediaType.audio, - video: true, - desktop: false // only support either video or desktop for now - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: addVideo failed: ', err); - cb('res_AddVideoFailed'); - } else { - cb(); - } - }); - }; - - /** - * Disable video in an existing RTC session. - * - * @param {String} callId The call ID of call to remove video from. - * @param {Function} cb A callback function replying with an error - */ - this.removeVideo = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: removeVideo - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: removeVideo...'); - - var mediaType = { - audio: localCall.localMediaType.audio, - video: false, - desktop: localCall.localMediaType.desktop - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: removeVideo failed: ', err); - cb('res_RemoveVideoFailed'); - } else { - cb(); - } - }); - }; - - /** - * Toggle (allow or block) remote video streams in an existing RTC session. - * - * @param {String} callId The call ID of call to remove remote video from. - * @param {Function} cb A callback function replying with an error - */ - this.toggleRemoteVideo = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: toggleRemoteVideo - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: toggleRemoteVideo...'); - - var sessionCtrl = localCall.sessionCtrl; - if (!sessionCtrl.isConnStable()) { - cb('Connection not stable'); - return; - } - - if (localCall.remoteVideoDisabled) { - localCall.enableRemoteVideo(); - } else { - localCall.disableRemoteVideo(); - // Publish event here to prevent UI changes during disabling video - PubSubSvc.publish('/call/toggleRemoteVideo'); - } - changeMediaType(callId, null, localCall.localMediaType, false, null, function (err) { - if (err) { - // The media renegotiation failed. So restore the original state. - if (localCall.remoteVideoDisabled) { - localCall.enableRemoteVideo(); - } else { - localCall.disableRemoteVideo(); - } - LogSvc.warn('[CircuitCallControlSvc]: toggleRemoteVideo failed: ', err); - cb('res_ToggleVideoFailed'); - } else { - cb(); - !localCall.remoteVideoDisabled && PubSubSvc.publish('/call/toggleRemoteVideo'); - } - }); - }; - - /** - * Change the HD video in an existing RTC session. - * - * @param {String} callId The call ID of call for which video will be toggled. - * @param {boolean} hdVideo Turn the HD video on/off. - * @param {Function} cb A callback function replying with an error - */ - this.changeHDVideo = function (callId, hdVideo, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: changeHDVideo - There is no local call'); - cb('No active call'); - return; - } - if (!localCall.mediaType.video) { - LogSvc.warn('[CircuitCallControlSvc]: changeHDVideo - Local call has no video'); - cb('No video'); - return; - } - - hdVideo = !!hdVideo; - if (localCall.localMediaType.hdVideo === hdVideo) { - // No changes - cb(); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: changeHDVideo...'); - - var mediaType = { - audio: _primaryLocalCall.localMediaType.audio, - video: true, - hdVideo: hdVideo, - desktop: false // only support either video or desktop for now - }; - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: changeHDVideo failed: ', err); - cb('res_ToggleVideoFailed'); - } else { - cb(); - } - }); - }; - - /** - * Toggle the video in an existing RTC session. - * - * @param {String} callId The call ID of call for which video will be toggled. - * @param {Function} cb A callback function replying with an error - */ - this.toggleVideo = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: toggleVideo - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: toggleVideo...'); - - var mediaType = { - audio: localCall.localMediaType.audio, - video: !localCall.localMediaType.video, - desktop: localCall.localMediaType.desktop - }; - if (mediaType.video) { - mediaType.desktop = false; // only support either video or desktop for now - } - - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: toggleVideo failed: ', err); - cb('res_ToggleVideoFailed'); - } else { - cb(); - } - }); - }; - - /** - * Starts a media renegotiation from the client without changing the current media types. - * - * @param {String} callId The call ID of the call for which to start the media renegotiation. - * @param {Function} cb A callback function replying with an error - */ - this.renegotiateMedia = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: renegotiateMedia - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: renegotiateMedia...'); - changeMediaType(callId, null, localCall.localMediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to renegotiate the media'); - cb('Renegotiate media failed'); - } else { - cb(); - } - }); - }; - - /** - * Set the audio and/or video media type for the current call. - * - * @param {String} callId The call ID of the call for which to start the media renegotiation. - * @param {Object} mediaType Object with audio and video boolean attributes. - * @param {Function} cb A callback function replying with an error - */ - this.setMediaType = function (callId, mediaType, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: setMediaType - There is no local call'); - cb('No active call'); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: setMediaType...'); - changeMediaType(callId, null, mediaType, false, null, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to renegotiate the media'); - cb('Renegotiate media failed'); - } else { - cb(); - } - }); - }; - - /** - * Starts a media renegotiation from the client with inactive mode and without changing the media types - * - * @param {String} callId The call ID of the call to be held - * @param {Function} cb A callback function replying with an error - */ - this.holdCall = function (callId, cb) { - cb = cb || function () {}; - var call = findLocalCallByCallId(callId); - if (!call) { - LogSvc.warn('[CircuitCallControlSvc]: holdCall - There is no call'); - cb('No active call'); - return; - } - if (call.isHolding() || call.isHoldInProgress()) { - LogSvc.debug('[CircuitCallControlSvc]: holdCall - The call is already held'); - cb(); // No error, just return since we might be answering a second telephony call - return; - } - LogSvc.debug('[CircuitCallControlSvc]: holdCall...'); - call.setHoldInProgress(); - changeMediaType(callId, null, call.localMediaType, false, true, function (err) { - call.clearHoldInProgress(); - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to hold the call'); - cb('res_HoldCallFailed'); - } else { - cb(); - } - }); - }; - - /** - * Starts a media renegotiation from the client to retrieve a held call without changing the media types - * - * @param {String} callId The call ID of the call to be retrieved - * @param {Function} cb A callback function replying with an error - */ - this.retrieveCall = function (callId, cb) { - cb = cb || function () {}; - var call = findLocalCallByCallId(callId); - if (!call.isHolding()) { - LogSvc.debug('[CircuitCallControlSvc]: retrieveCall - There is no held local call'); - cb(); - return; - } - LogSvc.debug('[CircuitCallControlSvc]: retrieveCall...'); - call.setRetrieveInProgress(); - changeMediaType(callId, null, call.localMediaType, false, false, function (err) { - call.clearRetrieveInProgress(); - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to retrieve the call'); - cb('res_RetrieveCallFailed'); - } else { - if (call === _secondaryLocalCall) { - _secondaryLocalCall = _primaryLocalCall; - _primaryLocalCall = call; - } - var conversation = getConversation(_primaryLocalCall.convId); - conversation.call = _primaryLocalCall; - publishConversationUpdate(conversation); - cb(); - } - }); - }; - - this.swapCall = function (callId, cb) { - cb = cb || function () {}; - _secondaryLocalCall = _that.getPhoneCalls(true).length > 1 ? findHeldPhoneCall(true) : null; - if (!_secondaryLocalCall || !_primaryLocalCall) { - LogSvc.warn('[CircuitCallControlSvc]: swapCall - There are no held and active local calls'); - cb('No calls to swap'); - return; - } - - if (_secondaryLocalCall && _secondaryLocalCall.callId !== callId) { - LogSvc.warn('[CircuitCallControlSvc]: swapCall - Wrong callId of the held call'); - cb('Wrong callId'); - return; - } - - var heldCallId = _secondaryLocalCall.callId; - var activeCallId = _primaryLocalCall.callId; - _that.holdCall(activeCallId, function (err) { - if (err) { - cb && cb(err); - } else { - _that.retrieveCall(heldCallId, cb); - } - }); - }; - - /** - * Move a remote call to the local client - * - * @param {String} callId The call ID of existing remote call established on another client. - * @param {BOOL} fallbackToAudio flag to check whether to pull with audio only. - * @param {Function} cb A callback function replying with an error - */ - this.pullRemoteCall = function (callId, fallbackToAudio, cb) { - cb = cb || function () {}; - var activeRemoteCall = getActiveRemoteCall(callId); - if (!activeRemoteCall) { - LogSvc.warn('[CircuitCallControlSvc]: pullRemoteCall invoked without a valid call'); - cb('No active remote call'); - return; - } - if (activeRemoteCall.pullNotAllowed) { - cb('res_PullCallNotAllowed'); - return; - } - var conversation = getConversation(activeRemoteCall.convId); - if (!conversation || (!conversation.isTelephonyConv && conversation.call.callId !== callId)) { - LogSvc.warn('[CircuitCallControlSvc]: pullRemoteCall - Cannot find valid conversation'); - cb('Conversation not found'); - return; - } - - function pullCallContinue() { - var activeClient = activeRemoteCall.activeClient || {}; - var mediaType = { - audio: true, - video: !!(activeClient.mediaType && activeClient.mediaType.video) - }; - - if (fallbackToAudio) { - mediaType.video = false; - } - - checkMediaSourcesAndJoin(conversation, mediaType, {callOut: false, handover: true, callId: callId}, cb); - } - - if (_primaryLocalCall && _primaryLocalCall.isTelephonyCall) { - _that.holdCall(_primaryLocalCall.callId, function (err) { - if (err) { - cb && cb(err); - } else { - pullCallContinue(); - } - - }); - return; - } - - pullCallContinue(); - }; - - /** - * End a remote call established on another client - * - * @param {String} callId The call ID of existing remote call established on another client. - * @param {Function} cb A callback function replying with an error - */ - this.endRemoteCall = function (callId, cb) { - cb = cb || function () {}; - var activeRemoteCall = getActiveRemoteCall(callId); - if (!activeRemoteCall) { - LogSvc.warn('[CircuitCallControlSvc]: endRemoteCall invoked without a valid call'); - cb('No active remote call'); - return; - } - var conversation = getConversation(activeRemoteCall.convId); - if (!conversation || conversation.call.callId !== callId) { - LogSvc.warn('[CircuitCallControlSvc]: endRemoteCall - Cannot find valid conversation'); - cb('Conversation not found'); - return; - } - - endRemoteCall(activeRemoteCall, function (err) { - if (err) { - cb('res_EndRemoteCallFailed'); - } else { - changeRemoteCallToStarted(activeRemoteCall); - cb(); - } - }); - }; - - /** - * Hide a remote call. - * - * @param {String} callId The call ID of remote call to be hidden. - */ - this.hideRemoteCall = function (callId) { - var activeRemoteCall = findCall(callId); - if (!activeRemoteCall) { - LogSvc.warn('[CircuitCallControlSvc]: hideRemoteCall invoked without a valid call'); - return; - } - terminateCall(activeRemoteCall); - }; - - /** - * Remove participant in a group call. - * - * @param {String} callId The call ID of a group call or conference. - * @param {Object} participant The call participant object. - * @param {Function} cb A callback function replying with an error - */ - this.dropParticipant = function (callId, participant, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall || localCall.isDirect) { - LogSvc.warn('[CircuitCallControlSvc]: dropParticipant - invalid call'); - cb('Call invalid'); - return; - } - if (!participant || !participant.userId) { - LogSvc.warn('[CircuitCallControlSvc]: dropParticipant - Invalid participant'); - cb('Participant invalid'); - return; - } - if (participant.isCMP && participant.isMeetingPointInvitee) { - localCall.setMeetingPointInviteState(false); - // Unmute the remote audio stream - _that.enableRemoteAudio(callId); - } - LogSvc.debug('[CircuitCallControlSvc]: Dropping participant in existing RTC session. userId =', participant.userId); - removeSessionParticipant(participant.userId, cb); - }; - - /** - * Add participant to a local call. - * - * @param {String} callId The call ID - * @param {Object} participant The participant object. - * @param {Function} cb A callback function replying with an error - */ - this.addParticipantToCall = function (callId, participant, cb) { - var call = findLocalCallByCallId(callId); - if (!call) { - LogSvc.warn('[CircuitCallControlSvc]: addParticipantToCall - There is no local call'); - cb && cb('Invalid callId'); - } else { - addParticipant(call, participant, cb); - } - }; - - /** - * Add participant to an RTC Session. Unlike addParticipantToCall this API does not rely - * on a local call being present. - * - * @param {String} callId The call ID - * @param {Object} participant The participant object. - * @param {Function} cb A callback function replying with an error - */ - this.addParticipantToRtcSession = function (callId, participant, cb) { - var call = findCall(callId); - if (!call) { - LogSvc.warn('[CircuitCallControlSvc]: addParticipantToRtcSession - Call not found'); - cb && cb('Invalid callId'); - } else { - addParticipant(call, participant); - } - }; - - /** - * mute self locally in an active call - * - * @param {String} callId The call ID to be muted - * @param {Function} cb A callback function replying with an error - */ - this.mute = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall || callId !== localCall.callId) { - LogSvc.warn('[CircuitCallControlSvc]: mute - There is no local call'); - cb('Call not valid'); - return; - } - - if (!localCall.locallyMuted) { - LogSvc.debug('[CircuitCallControlSvc]: Mute the local call'); - localCall.mute(function () { - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/localUser/mutedSelf event'); - PubSubSvc.publish('/call/localUser/mutedSelf', [callId, true]); - cb(); - }); - }); - } else { - cb('Call already muted'); - } - }; - - /** - * unmute locally only (used for large conference) - * - * @param {Function} cb A callback function called on success - */ - this.unmuteLocally = function (cb) { - if (_primaryLocalCall && _primaryLocalCall.locallyMuted) { - LogSvc.debug('[CircuitCallControlSvc]: Unmute the local call'); - _primaryLocalCall.unmute(function () { - $rootScope.$apply(function () { - LogSvc.debug('[CircuitCallControlSvc]: Publish /call/localUser/mutedSelf event'); - PubSubSvc.publish('/call/localUser/mutedSelf', [_primaryLocalCall.callId, false]); - cb && cb(); - }); - }); - } - }; - - /** - * unmute self in an active call, which may have been muted locally or remotely - * - * @param {String} callId The call ID to be unmuted - * @param {Function} cb A callback function replying with an error - */ - this.unmute = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: unmute - There is no local call'); - return; - } - - if (!localCall.isMuted()) { - cb('Call not muted'); - return; - } - var isRemotelyMuted = localCall.remotelyMuted; - - _that.unmuteLocally(isRemotelyMuted ? null : cb); - - if (isRemotelyMuted) { - // Remotely muted - _that.unmuteParticipant(localCall.callId, $rootScope.localUser, cb); - } - }; - - /** - * toggle between mute and unmute - * - * @param {String} callId The call ID to toggle mute - * @param {Function} cb A callback function replying with an error - */ - this.toggleMute = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: toggleMute - There is no local call'); - cb('Call not valid'); - return; - } - if (!localCall.isLocalMuteAllowed()) { - LogSvc.warn('[CircuitCallControlSvc]: toggleMute - Local Mute is not allowed'); - cb(); - return; - } - if (localCall.isMuted()) { - _that.unmute(localCall.callId, cb); - } else { - _that.mute(localCall.callId, cb); - } - }; - - /** - * mute remote participant - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object to be muted - * @param {Function} cb A callback function replying with an error - */ - this.muteParticipant = function (callId, participant, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: muteParticipant - There is no local call'); - cb('Call invalid'); - return; - } - var participantId = participant && participant.userId; - if (!participantId) { - LogSvc.warn('[CircuitCallControlSvc]: muteParticipant - Invalid participant ID'); - cb('Participant invalid'); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: Mute participant in existing RTC session. participantId =', participantId); - - var data = { - rtcSessionId: localCall.callId, - usersIds: [participantId], - muted: true - }; - - _clientApiHandler.muteParticipant(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to mute participant'); - cb('res_MuteParticipantFailed'); - } else { - cb(); - } - }); - }); - }; - - /** - * check if there are unmuted participants - * - * @param {String} callId The call ID of active call - */ - this.hasUnmutedParticipants = function (callId) { - var localCall = findLocalCallByCallId(callId); - if (!localCall || callId !== localCall.callId) { - LogSvc.warn('[CircuitCallControlSvc]: hasUnmutedParticipants - There is no local call'); - return false; - } - return localCall.hasUnmutedParticipants(); - }; - - /** - * mute RTC session - * - * @param {String} callId The call ID of active call - * @param {Function} cb A callback function replying with an error - */ - this.muteRtcSession = function (callId, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall || callId !== localCall.callId) { - LogSvc.warn('[CircuitCallControlSvc]: muteRtcSession - There is no local call'); - cb('Call invalid'); - return; - } - if (localCall.hasUnmutedParticipants()) { - LogSvc.debug('[CircuitCallControlSvc]: Mute RTC session (all other participants)'); - - var data = { - rtcSessionId: localCall.callId, - muted: true, - excludedUserIds: [$rootScope.localUser.userId] - }; - - _clientApiHandler.muteRtcSession(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to mute RTC session'); - cb('res_MuteAllOtherParticipantsFailed'); - } else { - cb(); - } - }); - }); - } - }; - - /** - * unmute self which has been muted remotely - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object to be unmuted - * @param {Function} cb A callback function replying with an error - */ - - this.unmuteParticipant = function (callId, participant, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: unmuteParticipant - There is no local call'); - cb('Call invalid'); - return; - } - var participantId = participant && participant.userId; - if (!participantId) { - LogSvc.warn('[CircuitCallControlSvc]: unmuteParticipant - Invalid participant ID'); - cb('Participant invalid'); - return; - } - - if (participantId !== $rootScope.localUser.userId) { - LogSvc.warn('[CircuitCallControlSvc]: Cannot unmute remote participant'); - cb('Cannot unmute others'); - return; - } - - LogSvc.debug('[CircuitCallControlSvc]: Unmute participant in existing RTC session. userId =', participantId); - - var data = { - rtcSessionId: localCall.callId, - usersIds: [participantId], - muted: false - }; - - _clientApiHandler.muteParticipant(data, function (err) { - $rootScope.$apply(function () { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to unmute participant'); - cb('res_UnmuteParticipantFailed'); - } else { - localCall.remotelyMuted = false; - publishMutedEvent(localCall); - cb(); - } - }); - }); - }; - - /** - * toggle between participant mute and unmute - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object for which to toggle mute - * @param {Function} cb A callback function replying with an error - */ - this.toggleMuteParticipant = function (callId, participant, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: toggleMuteParticipant - There is no local call'); - cb('Call invalid'); - return; - } - if (participant && participant.userId) { - if (participant.muted) { - return _that.unmuteParticipant(callId, participant, cb); - } - return _that.muteParticipant(callId, participant, cb); - } else { - LogSvc.warn('[CircuitCallControlSvc]: unmuteParticipant - Invalid participant ID'); - cb('Participant invalid'); - return; - } - }; - - this.stopRingingTone = function (callId) { - var alertingCall = getIncomingCall(callId); - - if (!alertingCall) { - LogSvc.warn('[CircuitCallControlSvc]: stopRingingTone - incoming call not found: ', callId); - return; - } - - alertingCall.ringToneStopped = true; - LogSvc.debug('[CircuitCallControlSvc]: stopRingingTone for call with callId = ', callId); - PubSubSvc.publish('/call/ringingTone/stop', [callId]); - }; - - /** - * End active call. - * - * @param {Function} cb A callback function replying with a status - */ - this.endActiveCall = function (cb) { - cb = cb || function () {}; - if (_primaryLocalCall) { - _that.endCall(_primaryLocalCall.callId, cb); - } else { - cb('No active call'); - } - }; - - this.startRecording = function (cb, allowScreenshareRecording) { - if (!_primaryLocalCall) { - LogSvc.warn('[CircuitCallControlSvc]: startRecording - There is no local call'); - cb('No active call'); - return; - } - - var mediaTypes = {audio: true, video: false, desktop: !!allowScreenshareRecording}; - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - mediaTypes: mediaTypes - }; - _clientApiHandler.startRecording(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - this.stopRecording = function (cb) { - if (!_primaryLocalCall) { - LogSvc.warn('[CircuitCallControlSvc]: stopRecording - There is no local event call'); - cb('No active call'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId - }; - _clientApiHandler.stopRecording(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - this.submitCallQualityRating = function (ratingData, callData, cb) { - if (!ratingData || !ratingData.hasOwnProperty('rating') || !callData) { - LogSvc.warn('[CircuitCallControlSvc]: submitCallQualityRating - Missing rating or call data'); - cb && cb('No rating data'); - return; - } - Object.assign(ratingData, callData); - _clientApiHandler.submitCallQualityRating([ratingData], function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Send new raise-hand question for large conference - * - * @param {String} questionText question text - * @param {boolean} isAnonymous question is anonymous - * @param {Function} cb A callback function replying with an error or success - */ - this.raiseQuestion = function (questionText, isAnonymous, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: raiseQuestion - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - question: questionText, - displayName: $rootScope.localUser.displayName, - isAnonymous: !!isAnonymous - }; - _clientApiHandler.raiseQuestion(data, function (err, questionNumber) { - $rootScope.$apply(function () { - cb && cb(err, questionNumber); - }); - }); - }; - - /** - * Send stage invitation to guest for large conference - * - * @param {String} questionNumber question number (ID) - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStage = function (questionNumber, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: inviteToStage - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - questionNumber: questionNumber - }; - _clientApiHandler.inviteToStage(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Invitation answer from invited guest (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {boolean} accepted invitation was accepted or discarded - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStageAnswer = function (questionNumber, accepted, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: inviteToStageAnswer - There is no local event call'); - cb('No active event'); - return; - } - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - questionNumber: questionNumber, - isAccepted: !!accepted - }; - _clientApiHandler.inviteToStageAnswer(data, function (err) { - $rootScope.$apply(function () { - if (!err && accepted) { - _that.addAudio(_primaryLocalCall.callId, function (audioErr) { - if (!audioErr) { - _that.unmuteLocally(); - } - cb && cb(audioErr); - }); - } else { - cb && cb(err); - } - }); - }); - }; - - /** - * Cancel on stage invitation by moderator (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStageCancel = function (questionNumber, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: inviteToStageCancel - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - questionNumber: questionNumber - }; - _clientApiHandler.inviteToStageCancel(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Remove someone from stage (large conference) - * - * @param {String} userId user ID - * @param {Function} cb A callback function replying with an error or success - */ - this.removeFromStage = function (userId, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: removeFromStage - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - userToRemoveId: userId || $rootScope.localUser.userId - }; - _clientApiHandler.removeFromStage(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Retrieve raise-hand questions (large conference) - * guests get only their own questions, moderators all - * - * @param {String} startQuestionNumber first question number to retrieve (starting from 1) - * @param {String} numberOfQuestions count of returned questions, 0 for all - * @param {Function} cb A callback function replying with an error or success - */ - this.getQuestions = function (startQuestionNumber, numberOfQuestions, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: getQuestions - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - startQuestionNumber: startQuestionNumber, - numberOfQuestions: numberOfQuestions - }; - _clientApiHandler.getQuestions(data, function (err, questions) { - $rootScope.$apply(function () { - cb && cb(err, questions); - }); - }); - }; - - /** - * Update a raise-hand question state (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {String} newState question state, Constants.QuestionState - * @param {Function} cb A callback function replying with an error or success - */ - this.updateQuestionState = function (questionNumber, newState, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: updateQuestionState - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - questionNumber: questionNumber, - newQuestionState: newState - }; - _clientApiHandler.updateQuestionState(data, function (err, questions) { - $rootScope.$apply(function () { - cb && cb(err, questions); - }); - }); - }; - - /** - * Open curtain in large conference - * - * @param {Function} cb A callback function replying with an error or success - */ - this.openCurtain = function (cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: openCurtain - There is no local event call'); - cb('No active event'); - return; - } - - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId - }; - _clientApiHandler.openCurtain(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Close curtain in large conference - * - * @param {String} state Curtain state (FINAL or PAUSED) - * @param {String} message Optional message shown to guests - * @param {String} pauseDuration Optional duration of the break (in seconds), backend converts into fixed time (expectedCurtainOpenTime) - * @param {Function} cb A callback function replying with an error or success - */ - this.closeCurtain = function (state, message, pauseDuration, cb) { - if (!_primaryLocalCall || !_primaryLocalCall.isLarge) { - LogSvc.warn('[CircuitCallControlSvc]: closeCurtain - There is no local event call'); - cb('No active event'); - return; - } - - var curtain = { - curtainState: state - }; - if (message) { curtain.message = message; } - if (pauseDuration) { curtain.pauseDuration = pauseDuration; } - var data = { - convId: _primaryLocalCall.convId, - rtcSessionId: _primaryLocalCall.callId, - curtain: curtain - }; - _clientApiHandler.closeCurtain(data, function (err) { - $rootScope.$apply(function () { - cb && cb(err); - }); - }); - }; - - /** - * Retrieve list of nodes with states. Currently only used for testcall. - * - * @param {String} nodeType requested node type (currently only MEDIA_ACCESS) - * @param {String} tenantId name of the tenant or empty for all - * @param {Function} cb A callback function replying with an error or success - */ - this.getNodeState = function (nodeType, tenantId, cb) { - var data = { - convId: 'noCall', - nodeType: nodeType, - tenantId: tenantId || '' - }; - _clientApiHandler.getNodeState(data, function (err, nodeData) { - $rootScope.$apply(function () { - if (err) { - LogSvc.error('[CircuitCallControlSvc]: Failed to get node(s). ', err); - } - cb && cb(err, nodeData); - }); - }); - }; - - /** - * Only applicable to local call (Siemens AG temporary solution) - */ - this.canSendWebRTCDigits = function () { - return _primaryLocalCall && _primaryLocalCall.sessionCtrl.canSendDTMFDigits(); - }; - - /** - * Send DTMF digits - * - * @param {String} callId The call ID of active call - * @param {String} digits The digits to be sent - * @param {Function} cb A callback function replying with a status - */ - this.sendDigits = function (callId, digits, cb) { - cb = cb || function () {}; - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: sendDigits - There is no local call'); - cb('Call not valid'); - return; - } - - if (!digits) { - cb('res_InvalidDtmf'); - return; - } - - if (!(/^[\d#\*,]+$/.exec(digits))) { - LogSvc.error('[CircuitCallControlSvc]: Digits cannot be sent: ' + digits + ' (Allowed: 0-9,#,*)'); - cb('res_InvalidDtmf'); - return; - } - - sendDTMFDigits(callId, digits, cb); - }; - - /** - * Locally disable the remote incoming video (this doesn't trigger a - * media renegotiation) - * @param {Object} call The existing group call or conference - * @returns {undefined} - */ - this.disableIncomingVideo = function (call) { - if (_primaryLocalCall && _primaryLocalCall.sameAs(call)) { - LogSvc.debug('[CircuitCallControlSvc]: Disabling remote incoming video. Call ID=', _primaryLocalCall.callId); - _primaryLocalCall.sessionCtrl.disableIncomingVideo(); - } - }; - - /** - * Locally enable the remote incoming video (this doesn't trigger a - * media renegotiation) - * @param {Object} call The existing group call or conference - * @returns {undefined} - */ - this.enableIncomingVideo = function (call) { - if (_primaryLocalCall && _primaryLocalCall.sameAs(call)) { - LogSvc.debug('[CircuitCallControlSvc]: Enabling remote incoming video. Call ID=', _primaryLocalCall.callId); - _primaryLocalCall.sessionCtrl.enableIncomingVideo(); - } - }; - - /** - * Enable or disable participant pointer - * - * @param {String} callId The ID of existing group call or conference - * @returns {undefined} - */ - this.toggleParticipantPointer = function (callId) { - var localCall = findLocalCallByCallId(callId); - if (!localCall) { - LogSvc.warn('[CircuitCallControlSvc]: toggleParticipantPointer - There is no local call'); - return; - } - - if (!localCall.pointer || !localCall.pointer.isSupported) { - LogSvc.warn('[CircuitCallControlSvc]: toggleParticipantPointer - Local call does not support participant pointer'); - return; - } - localCall.pointer.isEnabled = !localCall.pointer.isEnabled; - changeMediaType(localCall.callId, localCall.transactionId, localCall.localMediaType, false, null, null, null); - }; - - /** - * Find active or incoming local call - * @returns {LocalCall} The LocalCall object. - */ - this.findCall = findCall; - - this.findActivePhoneCall = findActivePhoneCall; - - this.findHeldPhoneCall = findHeldPhoneCall; - - /** - * Sends a UserToUser message to the sharing user which then - * shows that pointer in the shared screen. - * - * @param userId ID of user sharing the screen - * @param x x-coordinates - * @param y y-coordinates - * @returns {undefined} - */ - this.sendParticipantPointer = sendParticipantPointer; - - /** - * Sends a UserToUser message to the sharing user which then - * shows the drawing in the shared screen. - * - * @param userId ID of user sharing the screen - * @param points Array of x/y-coordinates - * @param options Object with color, tool and size property - * @returns {undefined} - */ - this.sendParticipantDrawing = sendParticipantDrawing; - - this.removeCallFromList = removeCallFromList; - - this.isSecondCall = function () { - return (_primaryLocalCall || _incomingCalls.length >= 1 || !_activeRemoteCalls.isEmpty()); - - }; - - this.updateActiveCallMediaDevices = function () { - if (_primaryLocalCall) { - _primaryLocalCall.updateMediaDevices(); - } - }; - - // Used in development mode to simulate a conference with several participants - this.addSimulatedParticipants = function () { - var NUM_SIMULATE_PARTICIPANTS = 20; - var START_SIMULATED_USER_ID = 50000; - var ADD_PARTICIPANT_DELAY = 2500; - var CALL_DURATION = 120000; - var REMOVE_PARTICIPANT_DELAY = 2500; - - var localCall = _that.getActiveCall(); - if (!localCall || localCall.isDirect || !localCall.isMocked || localCall.participants.length > 0) { - return; - } - - var removeParticipant = function () { - if (!localCall.isPresent() || localCall.participants.length === 0) { - return; - } - removeCallParticipant(localCall, localCall.participants[0].userId); - if (localCall.participants.length > 0) { - $timeout(removeParticipant, REMOVE_PARTICIPANT_DELAY); - } - }; - - var addNextParticipant = function (idx) { - function createParticipant() { - var participant = { - isSimulated: true, - userId: (START_SIMULATED_USER_ID + idx).toString(), - mediaTypes: [Constants.RealtimeMediaType.AUDIO], - muted: false - }; - - if (idx > 0 && !(idx % 6)) { - // Add a Meeting Room - participant.participantType = Constants.RTCParticipantType.MEETING_POINT; - participant.userDisplayName = 'CMR (' + idx + ')'; - } else { - // Add Session Guest - participant.participantType = Constants.RTCParticipantType.SESSION_GUEST; - participant.userDisplayName = [Math.floor(idx / 10), (idx % 10)].join(' '); - } - return participant; - } - - var normalizedParticipant = normalizeApiParticipant(createParticipant()); - if (!localCall.isMeetingPointInvited && normalizedParticipant.isCMP) { - normalizedParticipant.isMeetingPointInvitee = true; - localCall.setMeetingPointInviteState(true); - disableAllAudio(localCall.callId); - } - - var addedParticipant = addParticipantToCallObj(localCall, normalizedParticipant, Enums.ParticipantState.Active); - - PubSubSvc.publish('/call/participant/added', [localCall.callId, addedParticipant]); - PubSubSvc.publish('/call/participant/joined', [localCall, addedParticipant]); - - simulateActiveSpeakers(); - - if (idx < NUM_SIMULATE_PARTICIPANTS - 1) { - $timeout(function () { - if (localCall.isPresent()) { - addNextParticipant(idx + 1); - } - }, ADD_PARTICIPANT_DELAY); - } else { - $timeout(removeParticipant, CALL_DURATION); - } - }; - - addNextParticipant(0); - localCall.setState(Enums.CallState.Active); - publishCallState(localCall); - }; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.CircuitCallControlSvcImpl = CircuitCallControlSvcImpl; - - return circuit; - -})(Circuit); - -// Define external globals for JSHint -/*global RegistrationState */ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var AgentState = Circuit.Enums.AgentState; - var AtcCallInfo = circuit.AtcCallInfo; - var AtcMessage = circuit.Enums.AtcMessage; - var AtcRegistrationState = circuit.Enums.AtcRegistrationState; - var AtcRemoteCall = circuit.AtcRemoteCall; - var BusyHandlingOptions = circuit.BusyHandlingOptions; - var CallDirection = circuit.Enums.CallDirection; - var CallState = circuit.Enums.CallState; - var ClientApiHandler = circuit.ClientApiHandlerSingleton; - var Constants = circuit.Constants; - var CstaCallState = circuit.Enums.CstaCallState; - var CstaParser = circuit.CstaParser; - var JournalEntryTypes = circuit.Enums.JournalEntryTypes; - var MissedReasonTypes = circuit.Enums.MissedReasonTypes; - var PhoneNumberFormatter = Circuit.PhoneNumberFormatter; - var RedirectionTypes = Circuit.Enums.RedirectionTypes; - var RoutingOptions = circuit.RoutingOptions; - var Targets = circuit.Enums.Targets; - var UserToUserHandler = circuit.UserToUserHandlerSingleton; - var Utils = circuit.Utils; - var WebRTCAdapter = circuit.WebRTCAdapter; - var TransferCallFailedCauses = circuit.Enums.TransferCallFailedCauses; - - /** - * CstaSvc Implementation. A CSTA service to control CSTA request/response/event\ - * - * @class - */ - function CstaSvcImpl( - $rootScope, - $timeout, - LogSvc, - PubSubSvc, - CircuitCallControlSvc, - AtcRegistrationSvc, - UserSvc, - ConversationSvc) { - - LogSvc.debug('New Service: CstaSvc'); - - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - // Timers - var TRANSFER_TIMEOUT = 4000; // Transferred event should be received within 4s - var CALLBACK_NONRELATED_TIMEOUT = 5000; // 5s - Timer to keep displaying call after busy when CCNR - var REVERSE_LOOKUP_MAX_TIME = 2000; // 2s - Max time to wait for user search by phone number - var MAX_ENDED_CALL_WAIT_TIME = 2000; // 2s - Max time to wait for a local ended call - - var VALID_LOCAL_STATES = ['connected', 'hold', 'queued']; - var VALID_PARTNER_STATES = ['connected', 'hold']; - - var NUMBER_NOT_AVAILABLE = 'Number not available'; - - var DEFAULT_TRANSFER_CALLID = '00000000000000000000000000000000'; - var DEFAULT_CALCULATED_TRANSFER_CALLID = '02000000000000000000000000000000'; - - var FORWARD_EXPR = /Forward/; - - - /////////////////////////////////////////////////////////////////////////// - // Local variables - /////////////////////////////////////////////////////////////////////////// - var _that = this; - - var _regData = null; - var _osmoData = { - forwardList: [] - }; - - var _userToUserHandler = UserToUserHandler.getInstance(); - var _clientApiHandler = ClientApiHandler.getInstance(); - var _cstaParser = new CstaParser(); - - var _telephonyConversation = null; - - // The local WebRTC calls - var _activeCall = null; - var _heldCall = null; - var _alertingCall = null; - - // List with all the remote calls - var _atcRemoteCalls = {}; - - var _webrtcRemoteCall = null; - - var _calls = []; - - // Track transferred calls - var _trackTransferCallIds = {}; - - // track the transferred calls - var _transferredCalls = {}; - - var _snapshotPerformed = false; - - var _onsUnregistered = true; - - var _primaryClient = true; - - var _incomingCallConnection = {}; - - var _handoverCall = {}; - - var _lastTwoCallPartners = []; - - var _conferenceParticipants = []; - - var _resolving = false; - - // Last ended call. Save the last ended call for racing conditions - var _lastEndedCall = null; - var _endedCallWaitTimer = null; - - var _regState = null; - var _savedStaticOnd = null; - - var _savedVMRingDuration = null; - - var _savedMainRingDuration = null; - - var _savedClientRingDuration = null; - - var _savedCellRingDuration = null; - - var _receivedActiveSessions = false; - - /////////////////////////////////////////////////////////////////////////// - // Internal functions - /////////////////////////////////////////////////////////////////////////// - function setCallPeerUser(call, phoneNumber, fqNumber, displayName, cb) { - // Temporarily display whatever is available until user search is done - call.setPeerUser(phoneNumber, displayName); - var maxWaitTime = $timeout(function () { - maxWaitTime = null; - if (call.isPresent()) { - cb && cb(); - cb = null; - } - }, REVERSE_LOOKUP_MAX_TIME, false); - - UserSvc.startReverseLookUp(fqNumber, function (user) { - $timeout.cancel(maxWaitTime); - if (call.isPresent()) { - if (user) { - call.setPeerUser(phoneNumber, user.displayName, user.userId); - } - cb && cb(); - } - }); - } - - function setRedirectingUser(call, phoneNumber, fqNumber, displayName, type, cb) { - if (call.redirectingUser && call.redirectingUser.redirectionType) { - return; - } - call.setRedirectingUser(phoneNumber, fqNumber, displayName, null, type); - var maxWaitTime = $timeout(function () { - maxWaitTime = null; - if (call.isPresent()) { - cb && cb(); - cb = null; - } - }, REVERSE_LOOKUP_MAX_TIME, false); - - UserSvc.startReverseLookUp(fqNumber, function (user) { - $timeout.cancel(maxWaitTime); - if (call.isPresent()) { - if (user) { - call.setRedirectingUser(phoneNumber, fqNumber, user.displayName, user.userId, type); - } - cb && cb(); - } - }); - } - - function resolveParticipants() { - if (!_conferenceParticipants.length) { - _resolving = false; - return; - } - var participant = _conferenceParticipants.splice(0, 1); - UserSvc.startPhoneCallSearch(participant[0].phoneNumber, function () { - var users = UserSvc.getUserSearchList().slice(0); - var call = participant[0].call; - delete participant[0].call; - var addedParticipant = null; - if (users.length === 1) { - // Exact match - addedParticipant = call.addParticipant({ - userId: users[0].userId, - phoneNumber: participant[0].phoneNumber, - displayName: users[0].displayName, - participantId: participant[0].participantId - }); - } else { - addedParticipant = call.addParticipant(participant[0]); - } - - // Used by iOS client to update UI e.g., after a merge call action - if (addedParticipant && _telephonyConversation) { - LogSvc.debug('[CstaSvc]: Publish /atccall/participant/added'); - PubSubSvc.publish('/atccall/participant/added', [call.callId, addedParticipant]); - } - - $timeout(resolveParticipants, 0); - }); - } - - function setTelephonyConversation() { - _telephonyConversation = _telephonyConversation || ConversationSvc.getCachedTelephonyConversation(); - if (!_telephonyConversation) { - ConversationSvc.getTelephonyConversationPromise() - .then(function (conv) { - _telephonyConversation = conv; - }) - .catch(function () { - LogSvc.debug('[CstaSvc]: No telephony conversation found'); - }); - } - } - - function refreshData() { - if ($rootScope.localUser.isATC) { - setTelephonyConversation(); - - Object.assign(_osmoData, AtcRegistrationSvc.getAtcRegistrationData()); - _activeCall = findActiveCall(); - _heldCall = findHeldCall(); - _alertingCall = findAlertingCall(); - _webrtcRemoteCall = findWebRTCRemoteCall(); - if (_calls.length > 0) { - _calls = _calls.filter(function (call) { - if (call.checkState(CallState.Terminated)) { - if (_activeCall === call) { - _activeCall = null; - } - if (_heldCall === call) { - _heldCall = null; - } - if (_alertingCall === call) { - _alertingCall = null; - } - if (_webrtcRemoteCall === call) { - _webrtcRemoteCall = null; - } - return false; - } - return true; - }); - } - - if (!_activeCall && !_alertingCall && !_heldCall && !_webrtcRemoteCall) { - _calls.empty(); - } - } - } - - function updateExistingCall(newCall) { - for (var idx = 0; idx < _calls.length; idx++) { - if (_calls[idx].callId === newCall.callId) { - var atcCallInfo = _calls[idx].atcCallInfo; - if (newCall.peerUser.phoneNumber !== _calls[idx].peerUser.phoneNumber) { - atcCallInfo.clearServicesPermitted(); - } - _calls[idx] = newCall; - if (!newCall.atcCallInfo || !newCall.atcCallInfo.cstaConn) { - _calls[idx].atcCallInfo = atcCallInfo; - } - return _calls[idx]; - } - } - return null; - } - - function findActiveCall() { - var activeCall = CircuitCallControlSvc.getActiveCall(); - if (!activeCall || !activeCall.isTelephonyCall) { - return; - } - var updatedCall = updateExistingCall(activeCall); - if (updatedCall) { - return updatedCall; - } - var atcActiveCall = Object.create(activeCall); - atcActiveCall.atcCallInfo = activeCall.atcCallInfo || new AtcCallInfo(); - _calls.push(atcActiveCall); - return atcActiveCall; - } - - function findHeldCall() { - var heldCall = CircuitCallControlSvc.findHeldPhoneCall(); - if (!heldCall) { - return; - } - var updatedCall = updateExistingCall(heldCall); - if (updatedCall) { - return updatedCall; - } - var atcHeldCall = Object.create(heldCall); - atcHeldCall.atcCallInfo = heldCall.atcCallInfo || new AtcCallInfo(); - _calls.push(atcHeldCall); - return atcHeldCall; - } - - function findAlertingCall() { - var alertingCall = CircuitCallControlSvc.getIncomingCall(); - if (!alertingCall || !alertingCall.isTelephonyCall) { - return; - } - var updatedCall = updateExistingCall(alertingCall); - if (updatedCall) { - return updatedCall; - } - var atcAlertingCall = Object.create(alertingCall); - atcAlertingCall.atcCallInfo = alertingCall.atcCallInfo || new AtcCallInfo(); - _calls.push(atcAlertingCall); - return atcAlertingCall; - } - - function findWebRTCRemoteCall() { - var webRTCRemoteCall = CircuitCallControlSvc.getActiveRemoteCall()[0]; - if (!webRTCRemoteCall || !webRTCRemoteCall.isTelephonyCall) { - return; - } - var updatedCall = updateExistingCall(webRTCRemoteCall); - if (updatedCall) { - return updatedCall; - } - webRTCRemoteCall.atcCallInfo = webRTCRemoteCall.atcCallInfo || new AtcCallInfo(); - _calls.push(webRTCRemoteCall); - return webRTCRemoteCall; - } - - function removeAtcRemoteCall(callId, noCallLog) { - if (_atcRemoteCalls[callId]) { - LogSvc.debug('[CstaSvc]: Remove remote call with Call Id = ' + callId); - - var remoteCall = _atcRemoteCalls[callId]; - !noCallLog && createJournalEntry(remoteCall); - remoteCall.setCstaState(CstaCallState.Idle); - - // Update the UI - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [remoteCall]); - - delete _atcRemoteCalls[callId]; - } - } - - function createJournalEntry(remoteCall) { - if ($rootScope.localUser.noCallLog) { - return; - } - if (!remoteCall) { - LogSvc.error('[CstaSvc]: No remoteCall'); - return; - } - if (!remoteCall.atcCallInfo.getIgnoreCall() && remoteCall.isAtcRemote) { - if (!_telephonyConversation) { - LogSvc.error('[CstaSvc]: No telephony conversation'); - return; - } - if (!remoteCall.peerUser.phoneNumber && - remoteCall.peerUser.displayName === _telephonyConversation.peerUser.displayName) { - LogSvc.warn('[CstaSvc]: This call has no peer display information.'); - return; - } - var originalPartnerChanged = remoteCall.atcCallInfo.originalPartnerDisplay && remoteCall.atcCallInfo.originalPartnerDisplay.fqn && - remoteCall.atcCallInfo.originalPartnerDisplay.fqn !== remoteCall.atcCallInfo.peerFQN; - var partner = { - phoneNumber: originalPartnerChanged ? remoteCall.atcCallInfo.originalPartnerDisplay.dn : remoteCall.peerUser.phoneNumber, - displayName: originalPartnerChanged ? remoteCall.atcCallInfo.originalPartnerDisplay.name : remoteCall.peerUser.displayName, - userId: !originalPartnerChanged ? remoteCall.peerUser.userId || _telephonyConversation.peerUser.userId : _telephonyConversation.peerUser.userId, - resolvedUser: !!remoteCall.peerUser.userId && !originalPartnerChanged, - fqn: originalPartnerChanged ? remoteCall.atcCallInfo.originalPartnerDisplay.fqn : remoteCall.atcCallInfo.peerFQN - }; - - var journalEntry = { - convId: _telephonyConversation.convId, - starter: remoteCall.direction === CallDirection.INCOMING ? partner.userId : $rootScope.localUser.userId, - source: remoteCall.direction === CallDirection.INCOMING ? partner.fqn : $rootScope.localUser.phoneNumber, - destination: remoteCall.direction === CallDirection.OUTGOING ? partner.fqn : $rootScope.localUser.phoneNumber, - startTime: remoteCall.creationTime, - duration: remoteCall.establishedTime ? Date.now() - remoteCall.establishedTime : 0, - type: remoteCall.establishedTime === 0 ? JournalEntryTypes.MISSED : JournalEntryTypes.REGULAR, - participants: [ - { - userId: $rootScope.localUser.userId, - type: 'USER', - phoneNumber: $rootScope.localUser.phoneNumber, - displayName: $rootScope.localUser.displayName - }, - { - userId: partner.userId, - type: 'TELEPHONY', - phoneNumber: partner.phoneNumber, - displayName: partner.displayName, - resolvedUser: partner.resolvedUser - } - ] - }; - if (journalEntry.type === JournalEntryTypes.MISSED) { - switch (remoteCall.atcCallInfo.getMissedReason()) { - case MissedReasonTypes.DEST_OUT_OF_ORDER: - journalEntry.missedReason = Constants.RTCItemMissed.UNREACHABLE; - break; - case MissedReasonTypes.REORDER_TONE: - journalEntry.missedReason = Constants.RTCItemMissed.INVALID_NUMBER; - break; - case MissedReasonTypes.BUSY: - journalEntry.missedReason = Constants.RTCItemMissed.USER_BUSY; - break; - case MissedReasonTypes.CANCELLED: - journalEntry.missedReason = Constants.RTCItemMissed.CANCELED; - break; - case MissedReasonTypes.DECLINED: - journalEntry.missedReason = Constants.RTCItemMissed.DECLINED; - break; - case MissedReasonTypes.TRANSFERRED: - journalEntry.type = JournalEntryTypes.REGULAR; - break; - default: - journalEntry.missedReason = Constants.RTCItemMissed.TEMPORARILY_UNAVAILABLE; - } - } - - if (_primaryClient) { - _clientApiHandler.addJournalEntry(journalEntry); - } else { - // Start a timeout between 1 and 2 seconds - var delay = 1000 + 1000 * Math.random(); - $timeout(function () { - if (!entryAlreadySent(journalEntry)) { - _clientApiHandler.addJournalEntry(journalEntry); - } - }, delay, false); - } - - } - } - - function entryAlreadySent(journalEntry) { - var journalParticipant = journalEntry && journalEntry.participants && journalEntry.participants[1]; - - if (journalParticipant) { - return _lastTwoCallPartners.some(function (participant, idx) { - if ((Utils.cleanPhoneNumber(participant.phoneNumber) === Utils.cleanPhoneNumber(journalParticipant.phoneNumber)) && - (participant.displayName === journalParticipant.displayName)) { - LogSvc.debug('[CstaSvc]: Call log entry already sent by another client'); - _lastTwoCallPartners.splice(idx, 1); - return true; - } - }); - } - return false; - } - - function clearAtcRemoteCalls() { - for (var key in _atcRemoteCalls) { - if (_atcRemoteCalls.hasOwnProperty(key)) { - removeAtcRemoteCall(key, true); - } - } - } - - function findAtcRemoteCall(callId) { - for (var key in _atcRemoteCalls) { - if (_atcRemoteCalls.hasOwnProperty(key)) { - if (_atcRemoteCalls[key].callId === callId) { - return _atcRemoteCalls[key]; - } - } - } - return null; - } - - function createAtcRemoteCall(callId) { - if (!_telephonyConversation) { - return null; - } - var conversation = Object.create(_telephonyConversation); - conversation.rtcSessionId = callId; - return new AtcRemoteCall(conversation); - } - - function createCallFromSnapshot(snapshotCallResp) { - var data = snapshotCallResp.endpoints; - - if (!data || data.length < 2) { - return; - } - - var newCall = null; - var callId = data[0].callIdentifier.cID; - - var callState = (data.length > 2) ? CstaCallState.Conference : CstaCallState.Active; - data.forEach(function (ep) { - if (!callState) { - return; - } - - if (isMyDeviceId(ep.deviceOnCall)) { - if (VALID_LOCAL_STATES.indexOf(ep.localConnectionState) === -1) { - callState = null; - return; - } - var position = getCallPosition(ep.deviceOnCall, snapshotCallResp.epid); - - if (position === Targets.WebRTC && _webrtcRemoteCall) { - newCall = _webrtcRemoteCall; - position = Targets.Other; - } else { - newCall = createAtcRemoteCall(callId); - } - - newCall.atcCallInfo.setPosition(position); - newCall.atcCallInfo.setCstaConnection(ep.callIdentifier); - - // Extend top level servicesPermitted (Private data) with my endpoint's service permitted. - Object.assign(snapshotCallResp.servicesPermitted, ep.servicesPermitted); - - if (ep.localConnectionState === 'queued') { - callState = CstaCallState.Parked; - } else if (ep.localConnectionState === 'hold') { - if (data.length > 2) { - callState = CstaCallState.ConferenceHolding; - } else { - callState = (callState === CstaCallState.Held) ? CstaCallState.HoldOnHold : CstaCallState.Holding; - } - } - } else if (data.length === 2) { - if (VALID_PARTNER_STATES.indexOf(ep.localConnectionState) === -1) { - callState = null; - return; - } - var display = getDisplayInfo(ep.deviceOnCall); - newCall.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(newCall, newCall.atcCallInfo.peerDn, newCall.atcCallInfo.peerFQN, newCall.atcCallInfo.peerName); - - if (ep.localConnectionState === 'hold') { - callState = (callState === CstaCallState.Holding) ? CstaCallState.HoldOnHold : CstaCallState.Held; - } - } - }); - - if (snapshotCallResp.callingDevice && snapshotCallResp.callingDevice.replace('+', '') === $rootScope.localUser.phoneNumber.replace('+', '')) { - newCall.direction = CallDirection.OUTGOING; - } else { - newCall.direction = CallDirection.INCOMING; - } - - if (callState) { - newCall.atcCallInfo.setServicesPermitted(snapshotCallResp.servicesPermitted); - newCall.setCstaState(callState); - _atcRemoteCalls[callId] = newCall; - - if (newCall.atcCallInfo.getPosition() === Targets.WebRTC) { - handleHangingCall(newCall); - return; - } - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', newCall); - return newCall; - } - } - - function handleHangingCall(call) { - // This is a hanging WebRTC call. - // IF Seamless Handover is allowed, attempt to recover the call in case it is still available. - // ELSE clear the call. - - LogSvc.info('[CstaSvc]: Found a hanging WebRTC call'); - - call.atcCallInfo.setPosition(Targets.Other); - - if (call.atcCallInfo.isSeamlessHandoverAllowed()) { - LogSvc.info('[CstaSvc]: Attempt to recover the hanging call by initiating a Seamless Handover'); - handover(call, Targets[Targets.WebRTC.name], null, function (err) { - if (err) { - LogSvc.warn('[CstaSvc]: Failed to move the hanging call. ', err); - _that.hangupRemote(call); - } else { - LogSvc.info('[CstaSvc]: Hanging call has been successfully moved'); - } - }); - } else { - LogSvc.info('[CstaSvc]: Clear the hanging call'); - _that.hangupRemote(call); - } - } - - function isSecondaryLine(deviceId) { - return (deviceId.indexOf('keyId=') > 0); - } - - function isMyDeviceId(deviceId) { - if (!deviceId) { - return false; - } - if (deviceId.indexOf(_osmoData.onsFQN) < 0) { - return false; - } - if (deviceId.indexOf('uid=') > 0) { - return false; - } - return !isSecondaryLine(deviceId); - } - - function isVMDeviceId(deviceId) { - return !!deviceId && (getDisplayInfo(deviceId).dn === _osmoData.vm); - } - - function getCallPosition(deviceId, epid) { - LogSvc.debug('[CstaSvc]: Get call position for ' + deviceId); - - if (!isMyDeviceId(deviceId)) { - LogSvc.error('[CstaSvc]: Invalid device ID (' + deviceId + '). Cannot determine position.'); - return Targets.Other; - } - - var position = Targets.Other; - var tmp = /ond=(\+?\d+)/.exec(deviceId); - if (!tmp || !tmp[1]) { - position = Targets.Desk; - } else { - var ond = tmp[1]; - if (ond === _osmoData.osmoFQN) { - if (epid && epid !== _osmoData.epid) { - // This is a call on another OSMO - position = Targets.Other; - } else { - // This is a call on WebRTC - position = Targets.WebRTC; - } - - } else if (ond === _osmoData.cell) { - position = Targets.Cell; - } else if (ond === _osmoData.parkingDn) { - position = Targets.Queue; - } - } - - LogSvc.debug('[CstaSvc]: Call position is:', position.name); - return position; - } - - function getDisplayInfo(deviceId) { - var display = { - fqn: '', // Fully Qualified Number - dn: NUMBER_NOT_AVAILABLE, // Display Number - name: '' // Display Name - }; - - // deviceId is not valid if the CSTA device Id is <notKnown/> or <restricted/> - if (deviceId) { - // CSTA Device Id Pattern - var pattern = /N<([^>]*)>([^;]*)((?:[^;]*;)*(?:displayNumber=(\d*)))?(?:;.*)*/; - var tmp = pattern.exec(deviceId); - if (tmp) { - display.fqn = tmp[1] || ''; - display.name = tmp[2] || ''; - display.dn = tmp[4] || tmp[1] || 'Number not available'; - } else { - //4K sends N+1122334455 if name is not available - pattern = /N([^;]*)/; - tmp = pattern.exec(deviceId); - if (tmp) { - display.fqn = tmp[1] || ''; - display.dn = tmp[1] || 'Number not available'; - } else { - display.fqn = deviceId; - display.dn = deviceId; - } - } - } - - - LogSvc.debug('[CstaSvc]: Retrieved partner display: ', display); - return display; - } - - function findWebRTCCall(callId) { - refreshData(); - // Find the local WebRTC call that matches the callId - var calls = CircuitCallControlSvc.getPhoneCalls(); - var unassociatedCalls = []; - for (var idx = 0; idx < calls.length; idx++) { - if (!calls[idx].atcCallInfo || !calls[idx].atcCallInfo.getCstaCallId()) { - calls[idx].atcCallInfo = new AtcCallInfo(); - unassociatedCalls.push(calls[idx]); - } else if (calls[idx].atcCallInfo.getCstaCallId() === callId) { - _activeCall = calls[idx]; - return _activeCall; - } - } - if (_alertingCall && _alertingCall.atcCallInfo.getCstaCallId() === callId) { - return _alertingCall; - } else if (_alertingCall && (!_alertingCall.atcCallInfo.getCstaCallId())) { - unassociatedCalls.push(_alertingCall); - } - - return unassociatedCalls[0]; - } - - function snapshotRemoteCalls() { - clearAtcRemoteCalls(); - refreshData(); - _that.snapshotDevice(function (err, snapshotDevResp) { - if (err) { - if (err.indexOf('deviceOutOfService') > -1) { - _onsUnregistered = true; - PubSubSvc.publish('/csta/deviceChange'); - } - return; - } else { - _onsUnregistered = false; - PubSubSvc.publish('/csta/deviceChange'); - } - _snapshotPerformed = true; - if (!snapshotDevResp.activeCalls) { - return; - } - - try { - snapshotDevResp.activeCalls.forEach(function (call) { - if (call.localCallState.length < 2) { - return; - } - - if (call.localCallState.length === 2) { - if ((VALID_LOCAL_STATES.indexOf(call.localCallState[0]) === -1) || - (VALID_PARTNER_STATES.indexOf(call.localCallState[1]) === -1)) { - return; - } - } - - _that.snapshotCall(call.connectionId, function (err, snapshotCallResp) { - if (err) { - return; - } - - if (snapshotCallResp) { - var newCall = createCallFromSnapshot(snapshotCallResp); - if (newCall) { - // Notify the UI about the remote call - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [newCall]); - } - } - }); - }); - - } catch (e) { - LogSvc.error(e); - } - }); - } - - function snapshotRemoteCallsAndGetFeatureData() { - snapshotRemoteCalls(); - _that.getForwarding(handleGetForwardingResp); - _that.getAgentState(handleGetAgentStateResponse); - } - - function buildNewDestination(target, destination, withoutEpid) { - var newDestination = _osmoData.onsFQN + ';ond='; - - switch (target) { - case Targets.WebRTC: - if (withoutEpid) { - newDestination += _osmoData.osmoFQN; - } else { - newDestination += _osmoData.osmoFQN + ';epid=' + _osmoData.epid; - } - break; - - case Targets.Cell: - if (_osmoData.cell) { - newDestination += _osmoData.cell; - } else { - LogSvc.error('[CstaSvc]: Cell phone is not configured'); - return null; - } - break; - - case Targets.Desk: - newDestination += _osmoData.onsFQN; - break; - - case Targets.VM: - newDestination = _osmoData.vm; - break; - - case Targets.Other: - if (destination) { - newDestination += destination; - } else { - // If destination is not set, Targets.Other means deflect to Voice Mail in the UI - newDestination = _osmoData.vm; - } - break; - - default: - LogSvc.error('[CstaSvc]: Invalid target: ' + target); - return null; - } - return newDestination; - } - - // Calculate transfer-failed CallId - function calculateTransferFailCallId(newCallId) { - // FailedCallId = transferredCallId's first 2 hex byte + 2 - var pre = (parseInt(newCallId.substring(0, 2), 16) + 2).toString(16).toUpperCase(); - var post = newCallId.substring(2); - var failedCallId; - if (pre.length < 2) { - failedCallId = '0' + pre + post; - } else { - failedCallId = pre + post; - } - LogSvc.debug('[CstaSvc]: FailedCallId to be tracked: ' + failedCallId); - return failedCallId; - } - - function getRegistrationData(cb) { - Object.assign(_osmoData, AtcRegistrationSvc.getAtcRegistrationData()); - _regData = { - subscriber: $rootScope.localUser.cstaNumber - }; - if (!_regData.subscriber || !_osmoData) { - cb && cb('There is no registered user.'); - return false; - } - return true; - } - - function sendCstaRequest(msg, cb) { - var xml = _cstaParser.genCstaRequest(msg); - if (!xml) { - LogSvc.error('[CstaSvc]: Failed to build CSTA request: ' + msg.request); - cb && cb('Failed to send the request'); - return; - } - - if (_regState !== AtcRegistrationState.Registered) { - cb && cb('Client is not registered with ATC'); - return; - } - - var data = { - content: { - type: AtcMessage.CSTA, - phoneNumber: $rootScope.localUser.cstaNumber, - CSTA: xml - }, - destUserId: $rootScope.localUser.associatedTelephonyUserID - }; - - // Send request - _userToUserHandler.sendAtcRequest(data, function (error, rs) { - $rootScope.$apply(function () { - if (error) { - LogSvc.warn('[CstaSvc]: ', error); - cb && cb(error); - } else { - var parsedResp = _cstaParser.parseResponse(msg.request, rs.CSTA); - if (!parsedResp && parsedResp !== '') { - cb && cb('Failed to parse response from server'); - } else if (parsedResp.error) { - if (parsedResp.errorCategory === 'Operation Error' && parsedResp.errorValue === 'invalidConnectionID') { - if (msg.reason !== 'busy' && msg.request !== 'AcceptCall' && msg.request !== 'DeflectCall') { - snapshotRemoteCalls(); - } - } - cb && cb(parsedResp.errorCategory + ' - ' + parsedResp.errorValue); - } else { - cb && cb(null, parsedResp); - } - } - }); - - }); - } - - // Store Transferred Calls initiated from the client - // For SingleStepTransfer and SilentHandover, we need to track the transfer-failed CallId - // To determine the transfer result - function storeTransferredCall(callId, rs) { - if (rs.transferredCall && !rs.seamlessHandover) { - _transferredCalls[callId] = { - newCallId: rs.transferredCall.cID, - failedCallId: calculateTransferFailCallId(rs.transferredCall.cID), - timeout: $timeout(function () { - delete _transferredCalls[callId]; - }, TRANSFER_TIMEOUT) - }; - } - } - - function isTargetAllowed(call, target) { - if (!call || !getRegistrationData()) { - return false; - } - - if (target !== Targets.Other && call.atcCallInfo.position === target) { - return false; - } - - var myDevices = getMyDevices(); - if (myDevices.includes(target)) { - return true; - } else { - LogSvc.error('[CstaSvc]: isTargetAllowed invoked with invalid target: ' + target); - return false; - } - } - - function syncRoutingOption() { - LogSvc.debug('[CstaSvc]: Synchronize the selected routing option'); - _that.updateRoutingOption($rootScope.localUser.selectedRoutingOption); - } - - /////////////////////////////////////////////////////////////////////////// - // User to User event handlers - /////////////////////////////////////////////////////////////////////////// - _userToUserHandler.on('ATC.CSTA', function (data) { - LogSvc.info('[CstaSvc]: Received CSTA event: ', data); - if (data.type === AtcMessage.CSTA && data.CSTA) { - $rootScope.$apply(function () { - handleCstaEvent(data.CSTA); - }); - } - }); - - _userToUserHandler.on('ATC.PRIMARYCLIENT', function (data) { - LogSvc.info('[CstaSvc]: Received PRIMARYCLIENT event:', data); - if (data.type === AtcMessage.PRIMARYCLIENT && data.primaryClientId) { - handlePrimaryClientEvent(data.primaryClientId); - } - }); - - /////////////////////////////////////////////////////////////////////////// - // Event handlers - /////////////////////////////////////////////////////////////////////////// - function handleCstaEvent(json) { - var eventData = _cstaParser.parseEvent(json); - if (!eventData) { - return; - } - - LogSvc.debug('[CstaSvc]: Parsed CSTA Event: ', eventData); - switch (eventData.category) { - case 'CallControl': - case 'CallAssociatedFeature': - _that.handleCallEvent(eventData); - break; - case 'LogicalDeviceFeature': - _that.handleLogicalDeviceEvent(eventData); - break; - case 'PhysicalDeviceFeature': - break; - case 'DeviceMaintenance': - _that.handleDeviceEvent(eventData); - break; - default: - LogSvc.debug('[CstaSvc]: Unsupported CSTA Event category: ' + eventData.category); - break; - } - } - - function handlePrimaryClientEvent(primaryClientId) { - if ($rootScope.localUser.clientId !== primaryClientId) { - LogSvc.debug('[CstaSvc]: This is not the Primary ATC client'); - _primaryClient = false; - } else { - LogSvc.debug('[CstaSvc]: This is the primary ATC client'); - _primaryClient = true; - } - } - - function handleCallInformationEvent(event) { - // CallInformation usually updates the services permitted - var callId = event.connection.cID; - var call = null; - if (event.device && event.device.includes('Pickup Group Number')) { - // This a Group Pickup notification - if (!$rootScope.circuitLabs.GROUP_PICKUP || CircuitCallControlSvc.getActiveCall()) { - LogSvc.debug('[CstaSvc]: Ignore group pickup notification'); - return; - } - - call = _atcRemoteCalls[callId]; - var calledDisplay = getDisplayInfo(event.connection.dID); - var callingDisplay = getDisplayInfo(event.callingDevice); - if (!call) { - LogSvc.debug('[CstaSvc]: New pickup call for: ', calledDisplay.dn); - call = createAtcRemoteCall(callId); - call.atcCallInfo.setPosition(Targets.Other); - call.atcCallInfo.setPartnerDisplay(callingDisplay); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.setCstaState(CstaCallState.Ringing); - call.direction = CallDirection.INCOMING; - call.atcCallInfo.setCstaConnection(event.connection); - setRedirectingUser(call, calledDisplay.dn, calledDisplay.fqn, calledDisplay.name, RedirectionTypes.CallPickupNotification); - call.atcCallInfo.setOriginalPartnerDisplay(calledDisplay); - _atcRemoteCalls[callId] = call; - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else { - LogSvc.debug('[CstaSvc]: Existing pickup call for: ', calledDisplay.dn); - call.atcCallInfo.setCstaConnection(event.connection); - } - return; - } - var position = getCallPosition(event.connection.dID, event.epid); - call = position === Targets.WebRTC ? findWebRTCCall(callId) : _atcRemoteCalls[callId]; - - if (!call) { - LogSvc.warn('[CstaSvc]: Could not find call corresponding to CallInformation Event.'); - return; - } - call.atcCallInfo.setCstaConnection(event.connection); - if (event.callingDevice && !isMyDeviceId(event.callingDevice)) { - // callingDevice is the partner - var display = getDisplayInfo(event.callingDevice); - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - } - - if (event.servicesPermitted) { - // Update Services Permitted - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - if (event.servicesPermitted.eSP && event.servicesPermitted.eSP.sST) { - // FRN5227 is a prerequisite and SST is not allowed if it's in a conference - // So when SST is allowed, it's not in a conference anymore - if (call.checkCstaState(CstaCallState.ConferenceHolding)) { - call.setCstaState(CstaCallState.Holding); - } else if (call.checkCstaState(CstaCallState.Conference)) { - call.setCstaState(CstaCallState.Active); - } - } - } - - // Update the UI - if (call.checkCstaState([CstaCallState.Offered, CstaCallState.ExtendedRinging])) { - LogSvc.debug('[CstaSvc]: Do not publish /atccall/info event at Offered state'); - } else { - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - } - - function handleConferencedEvent(event) { - if (event.secondaryOldCall) { - // We are the conferencing party, remove the secondaryOldCall - removeAtcRemoteCall(event.secondaryOldCall.cID); - } - - // update the call in primaryOldCall with the one in conferenceConnections - var call = null; - var callId = event.primaryOldCall.cID; - - var position = getCallPosition(event.primaryOldCall.dID, event.epid); - if (position === Targets.WebRTC) { - call = findWebRTCCall(callId); - } else { - call = _atcRemoteCalls[callId]; - } - - if (!call) { - LogSvc.debug('[CstaSvc]: Could not find a call with Call Id = ' + callId); - return; - } - - // Update the CSTA Connection ID for the call. - event.conferenceConnections.forEach(function (confConn) { - if ((confConn.endpoint && isMyDeviceId(confConn.endpoint)) || (confConn.connection && isMyDeviceId(confConn.connection.dID))) { - call.atcCallInfo.setCstaConnection(confConn.connection); - if (call.isAtcRemote) { - var atcCallInfo = call.atcCallInfo; - var peerUser = call.peerUser; - var participants = call.participants; - var direction = call.direction; - call.setCstaState(CstaCallState.Idle); - - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - delete _atcRemoteCalls[callId]; - - call = createAtcRemoteCall(confConn.connection.cID); - call.atcCallInfo = atcCallInfo; - call.peerUser = peerUser; - call.participants = participants; - call.direction = direction; - _atcRemoteCalls[confConn.connection.cID] = call; - } - LogSvc.info('[CstaSvc]: Updated the CSTA connection ID for the call: ', confConn.connection); - } else { - var participantDisplay = confConn.endpoint && getDisplayInfo(confConn.endpoint) || getDisplayInfo(confConn.connection.dID); - _conferenceParticipants.push({ - call: call, - userId: confConn.connection.dID, - participantId: confConn.connection.dID, - phoneNumber: participantDisplay.dn, - displayName: participantDisplay.name - }); - - if (!_resolving) { - _resolving = true; - resolveParticipants(); - } - } - }); - - if (event.localConnectionInfo === 'hold') { - call.setCstaState(CstaCallState.ConferenceHolding); - } else { - call.setCstaState(CstaCallState.Conference); - } - - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleConnectionClearedEvent(event) { - var callId = event.droppedConnection.cID; - var call = findWebRTCCall(callId) || _atcRemoteCalls[callId]; - _incomingCallConnection = {}; - - if (call) { - if (call.pickupNotification && call.isHandoverInProgress) { - return; - } - if (event.localConnectionInfo !== 'null') { - LogSvc.info('[CstaSvc]: Local connection info is not null.Update services permitted.'); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - - if (call.participants.length > 1) { - call.participants.some(function (p) { - if (p.participantId === event.droppedConnection.dID) { - call.removeParticipant(p.userId); - return true; - } - }); - if ((call.participants.length === 1 && call.isRemote) || (call.participants.length === 2 && !call.isRemote)) { - var lastParticipant = call.participants[call.isRemote ? 0 : 1]; - call.removeParticipant(lastParticipant.userId); - setCallPeerUser(call, lastParticipant.phoneNumber, lastParticipant.phoneNumber, lastParticipant.displayName); - if (call.checkCstaState(CstaCallState.Conference)) { - call.setCstaState(CstaCallState.Active); - } else if (call.checkCstaState(CstaCallState.ConferenceHolding)) { - call.setCstaState(CstaCallState.Holding); - } - } - } - if (!call.atcCallInfo.getMissedReason() && !call.establishedTime) { - if (call.direction === CallDirection.INCOMING) { - call.atcCallInfo.setMissedReason(isMyDeviceId(event.releasingDevice) ? MissedReasonTypes.DECLINED : MissedReasonTypes.CANCELLED); - } else { - call.atcCallInfo.setMissedReason(isMyDeviceId(event.releasingDevice) ? MissedReasonTypes.CANCELLED : MissedReasonTypes.DECLINED); - } - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - return; - } else { - LogSvc.info('[CstaSvc]: Local connection info is null.'); - - if (!call.atcCallInfo.getMissedReason() && !call.establishedTime) { - call.atcCallInfo.setMissedReason(call.direction === CallDirection.INCOMING && event.cause === 'normalClearing' ? MissedReasonTypes.DECLINED : MissedReasonTypes.CANCELLED); - } - call.clearAtcHandoverInProgress(); - } - if (call.checkCstaState(CstaCallState.Busy) && call.atcCallInfo.isCallBackAllowed()) { - $timeout(function () { - LogSvc.debug('[CstaSvc]: Callback presenting timer expired - terminate call'); - removeAtcRemoteCall(call.atcCallInfo.getCstaCallId()); - }, CALLBACK_NONRELATED_TIMEOUT); - return; - } - } - - var transferCallId = _trackTransferCallIds[event.droppedConnection.cID] || _trackTransferCallIds[DEFAULT_CALCULATED_TRANSFER_CALLID]; - if (transferCallId) { - call = createAtcRemoteCall(transferCallId); - call.atcCallInfo.setCstaConnection(event.droppedConnection); - // cause = normal indicate Transfer is successful - // the other causes will reflect the failure reason of transfer - FRN5227 is a prerequisite to set cause correctly - if (event.cause !== 'normalClearing') { - call.setCstaState(CstaCallState.TransferFailed); - call.atcCallInfo.setTransferCallFailedCause(event.cause); - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - - delete _trackTransferCallIds[event.droppedConnection.cID]; - delete _trackTransferCallIds[DEFAULT_CALCULATED_TRANSFER_CALLID]; - } - removeAtcRemoteCall(event.droppedConnection.cID); - } - - function handleDeliveredEvent(event) { - var localConnection; - var partnerDeviceId; - var calledDeviceId; - var callState; - var direction; - var call = null; - - _incomingCallConnection = {}; - - if (isMyDeviceId(event.callingDevice)) { - // outgoing call - My device is the calling party - callState = CstaCallState.Delivered; - partnerDeviceId = event.alertingDevice; - calledDeviceId = event.calledDevice; - localConnection = { - cID: event.connection.cID, - dID: event.callingDevice - }; - direction = CallDirection.OUTGOING; - } else if (isMyDeviceId(event.alertingDevice)) { - // incoming call - My device is the alerting party - callState = CstaCallState.Ringing; - partnerDeviceId = event.callingDevice; - localConnection = event.connection; - direction = CallDirection.INCOMING; - } else { - LogSvc.error('[CstaSvc]: ONS DN is neither in callingDevice nor in alertingDevice?!?'); - return; - } - - if (event.cause === 'enteringDistribution') { - LogSvc.debug('[CstaSvc]: Ignoring Delivered event with cause: ', event.cause); - return; - } - var position = getCallPosition(localConnection.dID, event.epid); - var display = getDisplayInfo(partnerDeviceId); - if (position === Targets.WebRTC) { - // Local WebRTC call, check only _alertingCall - call = findWebRTCCall(localConnection.cID); - if (call) { - call.atcCallInfo.setCstaConnection(localConnection); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.setCstaState(CstaCallState.Ringing); - position = call.isRemote ? Targets.Other : position; - call.atcCallInfo.setPosition(position); - if (display) { - LogSvc.debug('[CstaSvc]: Update partner display to ', display); - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - var remoteCall = findAtcRemoteCall(localConnection.cID); - if (remoteCall) { - if (remoteCall.forwarded) { - setRedirectingUser(call, remoteCall.redirectingUser.phoneNumber, remoteCall.redirectingUser.fqNumber, remoteCall.redirectingUser.displayName, remoteCall.redirectingUser.redirectionType); - } - remoteCall.atcCallInfo.setIgnoreCall(true); - removeAtcRemoteCall(localConnection.cID); - } - } - // update UI - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - return; - } else { - // This is the racing condition scenario where the Delivered event was received after the call was terminated - if (_lastEndedCall && _lastEndedCall.atcCallInfo && _lastEndedCall.atcCallInfo.getCstaCallId() === event.connection.cId) { - LogSvc.warn('[CstaSvc]: The Delivered event is for a local call that was just terminated. Ignore it'); - return; - } - // This is most likely a scenario where the CSTA Delivered event arrives before - // the Circuit INVITE. See note under /call/incoming event handler for details. - LogSvc.warn('[CstaSvc]: There is no local call associated with the Delivered Event'); - // Let the code proceed below so we create a remote ATC call, but keep the position - // set to WebRTC. This will be needed in case we get the /call/incoming event. - } - } - - // Remote call - var callId = localConnection.cID; - call = _atcRemoteCalls[callId]; - if (!call) { - call = createAtcRemoteCall(callId); - _atcRemoteCalls[callId] = call; - LogSvc.debug('[CstaSvc]: Created new Remote Call object for callID= ', callId); - } - call.setCstaState(callState); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.atcCallInfo.setPosition(position); - call.atcCallInfo.setCstaConnection(localConnection); - call.clearAtcHandoverInProgress(); - call.direction = direction; - call.receivedAlerting = true; - if (display) { - // Update partner's display information if presented - call.atcCallInfo.setPartnerDisplay(display); - if (calledDeviceId) { - var calledDisplay = getDisplayInfo(calledDeviceId); - if (calledDisplay.fqn !== display.fqn) { - call.atcCallInfo.setOriginalPartnerDisplay(calledDisplay); - } else { - call.atcCallInfo.setOriginalPartnerDisplay(display); - } - } - - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName, function () { - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - }); - return; - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleDigitsGeneratedEvent(event) { - var callId = event.connection.cID; - var call = findWebRTCCall(callId); - if (!call) { - // find again from _atcRemoteCalls - call = _atcRemoteCalls[callId]; - } - - if (call && isMyDeviceId(event.connection.dID)) { - // DTMF digits have been generated - // Raise an event so we can request additional digits to be generated (if pending) - LogSvc.info('[CstaSvc]: Digits played', event.digitGeneratedList); - PubSubSvc.publish('/call/singleDigitSent', [call]); - } - } - - function handleDivertedEvent(event) { - var divertingDeviceDisplay, newDestinationDisplay; - var call = findWebRTCCall(event.connection.cID); - if (call) { - if (call.direction === CallDirection.OUTGOING && FORWARD_EXPR.test(event.cause)) { - divertingDeviceDisplay = getDisplayInfo(event.divertingDevice); - setRedirectingUser(call, divertingDeviceDisplay.dn, divertingDeviceDisplay.fqn, divertingDeviceDisplay.name, RedirectionTypes.CallForward); - newDestinationDisplay = getDisplayInfo(event.newDestination); - call.setPeerUser(newDestinationDisplay.dn, newDestinationDisplay.name); - } - return; - } - if (event.cause === 'distributed' && isMyDeviceId(event.divertingDevice) && getCallPosition(event.newDestination) === Targets.Desk) { - LogSvc.debug('[CstaSvc]: Ignore Diverted event with cause = distributed and newDestination = desk'); - return; - } - call = _atcRemoteCalls[event.connection.cID]; - if (!call) { - // Even though the remotecall hasn't been created yet, we need to - // notify the CircuitCallControlSvc about this event - call = createAtcRemoteCall(event.connection.cID); - } - if (event.localConnectionInfo === 'null' && isMyDeviceId(event.divertingDevice)) { - if (!isMyDeviceId(event.newDestination)) { - // If the call is diverted to VM or it's currently on the client and is - // diverted to another user (e.g. forwarding, pickup), the client should - // not create a journal entry. - if (isVMDeviceId(event.newDestination) || getCallPosition(event.connection.dID) === Targets.WebRTC) { - call.atcCallInfo.setIgnoreCall(true); - } - call.setCstaState(CstaCallState.Idle); - call.direction = CallDirection.INCOMING; - call.atcCallInfo.setMissedReason(MissedReasonTypes.CANCELLED); - if (FORWARD_EXPR.test(event.cause)) { - _atcRemoteCalls[event.connection.cID] = call; - var callingDisplay = getDisplayInfo(event.callingDevice); - LogSvc.debug('[CstaSvc]: Update partner display to ', callingDisplay); - call.atcCallInfo.setPartnerDisplay(callingDisplay); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else if (isMyDeviceId(event.newDestination)) { - if (call) { - if (call.pickupNotification) { - return; - } - if ((event.cause === 'redirected' || event.cause === 'distributed') && event.lastRedirectionDevice && !isMyDeviceId(event.lastRedirectionDevice)) { - var lastRedirectionDeviceDisplay = getDisplayInfo(event.lastRedirectionDevice); - setRedirectingUser(call, lastRedirectionDeviceDisplay.dn, lastRedirectionDeviceDisplay.fqn, lastRedirectionDeviceDisplay.name, RedirectionTypes.CallForward); - _atcRemoteCalls[event.connection.cID] = call; - } - if (call.forwarded) { - return; - } - if (call.checkCstaState(CstaCallState.Ringing) && getCallPosition(event.newDestination) === call.atcCallInfo.getPosition()) { - LogSvc.debug('[CstaSvc]: new destination has the same position as the current alerting position. Ignore event'); - return; - } - // This is a deflect to another OND - call.atcCallInfo.setIgnoreCall(true); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - if (getCallPosition(event.newDestination) === Targets.WebRTC) { - _incomingCallConnection = { cID: event.connection.cID, dID: event.newDestination}; - } - } - if (call && !call.isHandoverInProgress) { - removeAtcRemoteCall(event.connection.cID); - } - - } else if (!isMyDeviceId(event.divertingDevice)) { - if (call) { - var display = getDisplayInfo(event.divertingDevice); - if (display) { - call.atcCallInfo.setOriginalPartnerDisplay(display); - } - if (call.direction === CallDirection.OUTGOING && FORWARD_EXPR.test(event.cause)) { - divertingDeviceDisplay = getDisplayInfo(event.divertingDevice); - setRedirectingUser(call, divertingDeviceDisplay.dn, divertingDeviceDisplay.fqn, divertingDeviceDisplay.name, RedirectionTypes.CallForward); - newDestinationDisplay = getDisplayInfo(event.newDestination); - call.setPeerUser(newDestinationDisplay.dn, newDestinationDisplay.name); - return; - } - call.atcCallInfo.setMissedReason(MissedReasonTypes.CANCELLED); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - } - } - - function copyDataFromHandoverCall(call, callId) { - if (_handoverCall && _handoverCall.newCallId === callId) { - call.direction = _handoverCall.direction; - setRedirectingUser(call, _handoverCall.redirectingUser.phoneNumber, _handoverCall.redirectingUser.fqNumber, _handoverCall.redirectingUser.displayName, _handoverCall.redirectingUser.redirectionType); - _handoverCall = {}; - } - } - - function handleEstablishedEvent(event) { - var localConnection; - var partnerDeviceId; - var direction; - var call; - var originalPartnerDisplay; - _incomingCallConnection = {}; - - if (isMyDeviceId(event.callingDevice)) { - // We are the calling party - localConnection = { - cID: event.establishedConnection.cID, - dID: event.callingDevice - }; - partnerDeviceId = event.answeringDevice; - direction = CallDirection.OUTGOING; - originalPartnerDisplay = getDisplayInfo(event.calledDevice); - - } else if (isMyDeviceId(event.answeringDevice)) { - // We are the answering party - localConnection = event.establishedConnection; - partnerDeviceId = event.callingDevice; - direction = CallDirection.INCOMING; - originalPartnerDisplay = getDisplayInfo(event.callingDevice); - } else { - LogSvc.error('[CstaSvc]: ONS DN is neither in callingDevice nor in answeringDevice?!?'); - return; - } - - LogSvc.debug('[CstaSvc]: LocalConnection: ', localConnection); - LogSvc.debug('[CstaSvc]: Partner Device ID: ', partnerDeviceId); - - var display = getDisplayInfo(partnerDeviceId); - - // determine call position - var position = getCallPosition(localConnection.dID, event.epid); - if (position === Targets.WebRTC) { - LogSvc.info('[CstaSvc]: The Established Event is for a local WebRTC call'); - - // In some scenarios, like handover, the active call doesn't have a CSTA Connection ID - // until the Established Event is received, so we must fallback to the active call in - // case we cannot find a local call with the received CSTA Call ID. - call = findWebRTCCall(localConnection.cID); - if (call) { - call.atcCallInfo.setCstaConnection(localConnection); - LogSvc.info('[CstaSvc]: Set the CSTA connection ID for the active call: ', localConnection); - - if (call.isRemote) { - LogSvc.warn('[CstaSvc]: There is no local call associated with the Established Event'); - position = Targets.Other; - } - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - if (event.localConnectionInfo === 'hold') { - call.setCstaState(CstaCallState.Holding); - } else { - call.setCstaState(CstaCallState.Active); - } - call.atcCallInfo.setPartnerDisplay(display); - call.atcCallInfo.setPosition(position); - call.clearAtcHandoverInProgress(); - call.receivedAlerting = true; - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - copyDataFromHandoverCall(call, localConnection.cID); - var remoteCall = findAtcRemoteCall(localConnection.cID); - if (remoteCall && (remoteCall.forwarded || remoteCall.pickupNotification)) { - setRedirectingUser(call, remoteCall.redirectingUser.phoneNumber, remoteCall.redirectingUser.fqNumber, remoteCall.redirectingUser.displayName, remoteCall.redirectingUser.redirectionType === RedirectionTypes.CallForward ? RedirectionTypes.CallForward : RedirectionTypes.CallPickedUp); - remoteCall.atcCallInfo.setIgnoreCall(true); - removeAtcRemoteCall(localConnection.cID); - } - if (!call.isRemote) { - call.updateCallState(); - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - return; - } else { - if (event.cause === 'recall') { - call = CircuitCallControlSvc.findActivePhoneCall(); - LogSvc.info('[CstaSvc]: Recalled call callId:', call.atcCallInfo.getCstaCallId()); - call.atcCallInfo.setCstaConnection(localConnection); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - return; - } - // If the call is answered by the VM, the established event has no epid. This call should - // not be shown - if (isMyDeviceId(event.answeringDevice) && !event.epid) { - LogSvc.debug('[CstaSvc]: This is a call answered by VM. Ignore it'); - return; - } - - // This is the racing condition scenario where the Established event was received after the call was terminated - if (_lastEndedCall && _lastEndedCall.atcCallInfo && _lastEndedCall.atcCallInfo.getCstaCallId() === localConnection.cId) { - LogSvc.warn('[CstaSvc]: The Established event is for a local call that was just terminated. Ignore it'); - return; - } - } - } - - var newCall = false; - call = _atcRemoteCalls[localConnection.cID]; - if (!call) { - // This is a new call - newCall = true; - call = createAtcRemoteCall(localConnection.cID); - copyDataFromHandoverCall(call, localConnection.cID); - } - - call.atcCallInfo.setPosition(position); - call.atcCallInfo.setPartnerDisplay(display); - call.atcCallInfo.setOriginalPartnerDisplay(originalPartnerDisplay); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.atcCallInfo.setCstaConnection(localConnection); - call.direction = direction; - call.clearAtcHandoverInProgress(); - call.receivedAlerting = true; - - if (event.localConnectionInfo === 'hold') { - call.setCstaState(CstaCallState.Holding); - } else { - call.setCstaState(CstaCallState.Active); - } - - if (event.cause === 'callPickup' && event.lastRedirectionDevice) { - setRedirectingUser(call, getDisplayInfo(event.lastRedirectionDevice).dn, getDisplayInfo(event.lastRedirectionDevice).fqn, getDisplayInfo(event.lastRedirectionDevice).name, RedirectionTypes.CallPickedUp); - } - - if (newCall) { - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', call); - // Add call to _atcRemoteCalls - _atcRemoteCalls[localConnection.cID] = call; - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleHeldEvent(event) { - var callId = event.heldConnection.cID; - - var call = findWebRTCCall(callId); - if (!call) { - // find again from _atcRemoteCalls - call = _atcRemoteCalls[callId]; - - if (!call) { - LogSvc.warn('[CstaSvc]: Could not find call corresponding to Held Event.'); - return; - } - } - - // Update Call State - if (isMyDeviceId(event.holdingDevice)) { - // The subscriber held the call - call.atcCallInfo.setCstaConnection(event.heldConnection); - switch (call.getCstaState()) { - case CstaCallState.Held: - call.setCstaState(CstaCallState.HoldOnHold); - break; - case CstaCallState.Conference: - call.setCstaState(CstaCallState.ConferenceHolding); - break; - default: - call.setCstaState(CstaCallState.Holding); - break; - } - call.clearHoldInProgress(); - } else { - // The subscriber was held - if (event.localConnectionInfo === 'hold') { - if (!call.checkCstaState(CstaCallState.ConferenceHolding)) { - call.setCstaState(CstaCallState.HoldOnHold); - } - } else { - if (!call.checkCstaState(CstaCallState.Conference)) { - call.setCstaState(CstaCallState.Held); - } - } - } - - // Update Services Permitted - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - - // Update the UI - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleOfferedEvent(event) { - var localConnection; - var partnerDeviceId; - var data; - - if (isMyDeviceId(event.offeredDevice)) { - // This is a call offered to the ONS user - localConnection = event.offeredConnection; - partnerDeviceId = event.callingDevice; - } else { - LogSvc.debug('[CstaSvc]: ONS DN is not the offered device. Ignore Offered event.'); - return; - } - - LogSvc.debug('[CstaSvc]: LocalConnection: ', localConnection); - LogSvc.debug('[CstaSvc]: Partner Device ID: ', partnerDeviceId); - - if ($rootScope.localUser.callForwardingEnabled) { - LogSvc.debug('[CstaSvc]: Immediate call forwarding is enabled. Accept call'); - data = { - request: 'AcceptCall', - callToBeAccepted: localConnection - }; - sendCstaRequest(data); - return; - } - - // Get partner's display information - var display = getDisplayInfo(partnerDeviceId); - - var call = _atcRemoteCalls[localConnection.cID]; - if (call && call.pickupNotification) { - return; - } - call = createAtcRemoteCall(localConnection.cID); - call.atcCallInfo.setPosition(Targets.Desk); - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.setCstaState(CstaCallState.Offered); - call.direction = CallDirection.INCOMING; - call.atcCallInfo.setCstaConnection(localConnection); - if (FORWARD_EXPR.test(event.cause)) { - // If in case of multiple forwarding we want to display the original called party, then we should use the calledDevice - // The OpenStage phones show the last redirecting party - var lastRedirectionDeviceDisplay = getDisplayInfo(event.lastRedirectionDevice); - setRedirectingUser(call, lastRedirectionDeviceDisplay.dn, lastRedirectionDeviceDisplay.fqn, lastRedirectionDeviceDisplay.name, RedirectionTypes.CallForward); - } - - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', call); - - // Add call to _atcRemoteCalls - _atcRemoteCalls[localConnection.cID] = call; - _incomingCallConnection = {cID: localConnection.cID, dID: buildNewDestination(Targets.WebRTC, null, true)}; - if ($rootScope.localUser.selectedRoutingOption === RoutingOptions.DeskPhone.name) { - handleFirstCall(call); - return; - } - if (CircuitCallControlSvc.isSecondCall() || (_telephonyConversation.call && _telephonyConversation.call.isAtcRemote)) { - handleSecondCall(call); - } else { - data = { - request: 'AcceptCall', - callToBeAccepted: call.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data); - _incomingCallConnection = {}; - } - - } - - function handleQueuedEvent(event) { - var localConnection; - var partnerDeviceId; - - if (isMyDeviceId(event.queue)) { - // This is a call parked to the user's private queue - localConnection = event.queuedConnection; - partnerDeviceId = event.callingDevice; - } else { - LogSvc.debug('[CstaSvc]: ONS DN is not the queue device. Ignore Queued event.'); - return; - } - - LogSvc.debug('[CstaSvc]: LocalConnection: ', localConnection); - LogSvc.debug('[CstaSvc]: Partner Device ID: ', partnerDeviceId); - - // Determine call position - var position = getCallPosition(localConnection.dID, event.epid); - - // Get partner's display information - var display = getDisplayInfo(partnerDeviceId); - var callId = localConnection.cID; - var call = _atcRemoteCalls[callId]; - if (!call) { - if (event.cause === 'noAvailableAgents') { - // This event indicates that the MLHG that the ONS belongs to is busy. Needs to be ignored - LogSvc.debug('[CstaSvc]: Cause=noAvailableAgents. Ignore Queued event'); - return; - } - call = createAtcRemoteCall(callId); - _atcRemoteCalls[callId] = call; - LogSvc.debug('[CstaSvc]: Created new Remote Call object for callID= ', callId); - } - call.atcCallInfo.setPosition(position); - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - call.setCstaState(CstaCallState.Parked); - call.atcCallInfo.setCstaConnection(localConnection); - - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleRetrievedEvent(event) { - var callId = event.retrievedConnection.cID; - - var call = findWebRTCCall(callId); - if (!call) { - // find again from _atcRemoteCalls - call = _atcRemoteCalls[callId]; - - if (!call) { - LogSvc.warn('[CstaSvc]: Could not find call corresponding to Retrieved Event.'); - return; - } - } - - // Update Call State - if (isMyDeviceId(event.retrievingDevice)) { - call.atcCallInfo.setCstaConnection(event.retrievedConnection); - // The subscriber retrieved the call - if (call.checkCstaState(CstaCallState.HoldOnHold)) { - call.setCstaState(CstaCallState.Held); - - // Prior to V7, the OSV has some problems with Hold-On-Hold scenarios and - // it doesn't report all the events as expected. - // Therefore, we should do a Snapshot Call to make sure we don't get stuck - // in Held state. For now, as a workaround, just check whether Silent Handover - // is allowed for this call. If it is, this means we are actually in Active state - if (event.servicesPermitted.eSP && event.servicesPermitted.eSP.zsiHo) { - call.setCstaState(CstaCallState.Active); - } - } else if (call.checkCstaState(CstaCallState.ConferenceHolding)) { - // Still in a conference - call.setCstaState(CstaCallState.Conference); - - // But if SST is allowed, Per FRN5227, it is not in a conference anymore - if (event.servicesPermitted.eSP && event.servicesPermitted.eSP.sST) { - call.setCstaState(CstaCallState.Active); - } - } else { - call.setCstaState(CstaCallState.Active); - } - call.clearRetrieveInProgress(); - } else { - // The subscriber was retrieved - if (call.checkCstaState([CstaCallState.ConferenceHolding, CstaCallState.Conference])) { - if (event.localConnectionInfo === 'hold') { - call.setCstaState(CstaCallState.ConferenceHolding); - } else { - call.setCstaState(CstaCallState.Conference); - } - } else { - if (event.localConnectionInfo === 'hold') { - call.setCstaState(CstaCallState.Holding); - } else { - call.setCstaState(CstaCallState.Active); - } - - if (call.isRemote) { - // Call may have been retrieved by different party. Update the display just in case. - var display = getDisplayInfo(event.retrievingDevice); - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - } - } - } - - // Update Services Permitted - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - - // Update the UI - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleServiceInitiatedEvent(event) { - /* - * Process the Service Initiated Event to find out the CSTA Connection ID - * for outgoing calls from the WebRTC client or Remote Device. - */ - var call; - var position = getCallPosition(event.initiatingDevice, event.epid); - if (position === Targets.WebRTC) { - call = findWebRTCCall(event.initiatedConnection.cID); - // During Consultation, the 2nd new call becomes _activeCall - // In 3PCC MakeCall/ConsultationCall scenarios, local state is Ringing when ServiceIniated event comes - if (call) { - call.atcCallInfo.setCstaConnection(event.initiatedConnection); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - if (!call.isRemote && call.checkState([CallState.Initiated, CallState.Ringing, CallState.Delivered])) { - LogSvc.info('[CstaSvc]: Set the CSTA connection ID for the call: ', event.initiatedConnection); - - call.setCstaState(CstaCallState.Initiated); - // update UI - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else if (call.isRemote) { - LogSvc.info('[CstaSvc]: Set the CSTA connection ID for the remote call: ', event.initiatedConnection); - return; - } else { - LogSvc.error('[CstaSvc]: Call Initiated for WebRTC client has no active call or not in Initiated/Ringing state'); - } - return; - } - } - - call = _atcRemoteCalls[event.initiatedConnection.cID]; - if (!call) { - // This is a new remote call - call = createAtcRemoteCall(event.initiatedConnection.cID); - call.setCstaState(CstaCallState.Initiated); - } - - call.atcCallInfo.setPosition(position); - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - - call.direction = CallDirection.OUTGOING; - call.atcCallInfo.setCstaConnection(event.initiatedConnection); - _atcRemoteCalls[event.initiatedConnection.cID] = call; - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', call); - } - - function handleTransferredEvent(event) { - if (event.localConnectionInfo === 'null') { - // We are the transferring party - if (event.secondaryOldCall) { - removeAtcRemoteCall(event.primaryOldCall.cID); - var secondaryCall = _atcRemoteCalls[event.secondaryOldCall.cID]; - if (secondaryCall) { - secondaryCall.atcCallInfo.setMissedReason(MissedReasonTypes.TRANSFERRED); - } - $timeout(function () { - removeAtcRemoteCall(event.secondaryOldCall.cID); - }, 1000); - } else { - if (isMyDeviceId(event.transferringDevice) && isMyDeviceId(event.transferredToDevice)) { - var remoteCall = _atcRemoteCalls[event.primaryOldCall.cID]; - if (remoteCall) { - remoteCall.atcCallInfo.setIgnoreCall(true); - } - } - removeAtcRemoteCall(event.primaryOldCall.cID); - } - - if (event.cause === 'singleStepTransfer') { - // For SingleStepTransfer and SilentHandover, TransferredEvent(null) does not indicate the transfer is successful or not - // We have to track the Failed (fail, blocked) + ConnectionCleared event in order to determine the result - // Only track the transfer operation that is initiated through EVO - var transferredCall = _that.getTransferredCall(event.primaryOldCall.cID); - if (transferredCall) { - // remove stored transferredCall from _cstaReqHandler and track the failedCallId locally - _that.clearTransferredCall(event.primaryOldCall.cID); - _trackTransferCallIds[transferredCall.failedCallId] = event.primaryOldCall.cID; - } - } else if (event.cause === 'transfer') { - // To popup a success message after consult transfer - var tmpCall = createAtcRemoteCall(event.secondaryOldCall.cID); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [tmpCall]); - } - - return; - } - - // We are the transferred or transferred-to party - var display = null; - var callId = event.primaryOldCall.cID; - var call = findWebRTCCall(callId) || _atcRemoteCalls[callId]; - - if (!call) { - LogSvc.debug('[CstaSvc]: Could not find a call with Call Id = ' + callId); - return; - } - - // Update the CSTA Connection ID for the call. - event.transferredConnections.forEach(function (xferConn) { - if ((xferConn.endpoint && isMyDeviceId(xferConn.endpoint)) || (xferConn.connection && isMyDeviceId(xferConn.connection.dID))) { - call.atcCallInfo.setCstaConnection(xferConn.connection); - LogSvc.info('[CstaSvc]: Updated the CSTA connection ID for the call: ', xferConn.connection); - } else { - if (call.isAtcRemote) { - var atcCallInfo = call.atcCallInfo; - call.setCstaState(CstaCallState.Idle); - var peerUser = call.peerUser; - var direction = call.direction; - var establishedTime = call.establishedTime; - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - delete _atcRemoteCalls[callId]; - - call = createAtcRemoteCall(xferConn.connection.cID); - call.atcCallInfo = atcCallInfo; - setCallPeerUser(call, peerUser.phoneNumber, peerUser.phoneNumber, peerUser.displayName); - call.direction = direction; - call.establishedTime = establishedTime; - var originalPartnerDisplay = getDisplayInfo(event.transferringDevice); - call.atcCallInfo.setOriginalPartnerDisplay(originalPartnerDisplay); - - _atcRemoteCalls[call.callId] = call; - } - // Update the partner display - display = (xferConn.endpoint && getDisplayInfo(xferConn.endpoint)) || (xferConn.connection.dID && getDisplayInfo(xferConn.connection.dID)); - } - }); - - if (!call.isRemote && !call.isEstablished()) { - LogSvc.debug('[CstaSvc]: This is a WebRTC call that has not been answered. Update the display'); - if (display) { - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName, function () { - // Wait for reverse lookup of peer user display data to update the call object before publishing the event. - // Note, the callControlSvc listens for this event and will in this case publish a call state event used - // (at least by iOS) to update call display data. - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - }); - } - return; - } - - switch (event.localConnectionInfo) { - case 'hold': - call.setCstaState(CstaCallState.Holding); - break; - case 'connected': - call.setCstaState(CstaCallState.Active); - break; - case 'alerting': - call.setCstaState(CstaCallState.Ringing); - break; - default: - LogSvc.debug('[CstaSvc]: Unexpected localConnectionInfo: ', event.localConnectionInfo); - break; - } - - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - if (display) { - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName, function () { - // Wait for reverse lookup of peer user display data to update the call object before publishing the event. - // Note, the callControlSvc listens for this event and will in this case publish a call state event used - // (at least by iOS) to update call display data. - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - }); - } - - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleFailedEvent(event) { - var call, callId, display, position; - - if (event.localConnectionInfo === 'fail' && isMyDeviceId(event.failingDevice)) { - call = _atcRemoteCalls[event.failedConnection.cID]; - if (isMyDeviceId(event.callingDevice)) { - display = getDisplayInfo(event.calledDevice); - if (display.dn !== NUMBER_NOT_AVAILABLE && call && display.fqn !== Utils.cleanPhoneNumber(call.atcCallInfo.peerFQN)) { - setCallPeerUser(call, display.dn, display.fqn, display.name); - call.atcCallInfo.setPartnerDisplay(display); - } - } - switch (event.cause) { - case 'blocked': - // track the transfer result - if (_trackTransferCallIds[event.failedConnection.cID]) { - LogSvc.debug('[CstaSvc]: Received Failed event for transfer, waiting for ConnectionCleared event'); - } - if (!$rootScope.localUser.isOSV) { - callId = event.failedConnection.cID; - position = getCallPosition(event.failedConnection.dID); - call = position === Targets.WebRTC ? findWebRTCCall(callId) : _atcRemoteCalls[callId]; - if (call && call.isHandoverInProgress) { - LogSvc.debug('[CstaSvc]: Received Failed event from non OSV PBX while handover is in progress'); - call.clearAtcHandoverInProgress(); - LogSvc.debug('[CstaSvc]: Publish /atccall/moveFailed event'); - PubSubSvc.publish('/atccall/moveFailed'); - } - } - break; - - case 'doNotDisturb': - // ONS has DND active, the incoming call is blocked - // Raise an event to inform the user about the blocked call - call = _atcRemoteCalls[event.failedConnection.cID]; - if (call) { - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else { - LogSvc.warn('[CstaSvc]: DND call does not exist. Ignore Failed event'); - } - break; - case 'reorderTone': - call = _atcRemoteCalls[event.failedConnection.cID]; - if (call) { - call.atcCallInfo.setMissedReason(MissedReasonTypes.REORDER_TONE); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else { - LogSvc.warn('[CstaSvc]: Outgoing call failed with reorder tone'); - } - break; - case 'busy': - call = _atcRemoteCalls[event.failedConnection.cID]; - if (call) { - call.atcCallInfo.setMissedReason(MissedReasonTypes.BUSY); - // OS4K case where the call fails in the Offered state due to busy - if (call.checkCstaState(CstaCallState.Offered)) { - call.setCstaState(CstaCallState.Failed); - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } else { - LogSvc.warn('[CstaSvc]: Busy call does not exist. Ignore Failed event'); - } - break; - default: - break; - } - } else if (event.localConnectionInfo === 'connected' && isMyDeviceId(event.callingDevice)) { - callId = event.failedConnection.cID; - call = findWebRTCCall(callId); - if (!call || call.atcCallInfo.getCstaCallId() !== callId) { - call = _atcRemoteCalls[callId]; - } - if (!call) { - LogSvc.warn('[CstaSvc]: Could not find call corresponding to handle FailedEvent Event.'); - return; - } - call.atcCallInfo.setServicesPermitted(event.servicesPermitted); - if (!call.atcCallInfo.peerFQN) { - display = getDisplayInfo(event.failingDevice || event.calledDevice); - if (display) { - // Update partner's display information if presented - call.atcCallInfo.setPartnerDisplay(display); - setCallPeerUser(call, call.atcCallInfo.peerDn, call.atcCallInfo.peerFQN, call.atcCallInfo.peerName); - } - } - switch (event.cause) { - case 'busy': - if (call.isRemote) { - // Raise an event to inform the user about the busy call - call.setCstaState(CstaCallState.Busy); - call.atcCallInfo.setMissedReason(MissedReasonTypes.BUSY); - } - break; - case 'reorderTone': - call.atcCallInfo.setMissedReason(MissedReasonTypes.REORDER_TONE); - break; - case 'blocked': - call.atcCallInfo.setMissedReason(MissedReasonTypes.DECLINED); - break; - case 'destOutOfOrder': - call.atcCallInfo.setMissedReason(MissedReasonTypes.DEST_OUT_OF_ORDER); - break; - default: - call.atcCallInfo.setMissedReason(MissedReasonTypes.DEFAULT); - } - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - } - - function handleServiceCompletionFailureEvent(event) { - var callId = event.primaryCall.connectionID.cID; - var position = getCallPosition(event.primaryCall.connectionID.dID, _osmoData.epid); - var call = position === Targets.WebRTC ? findWebRTCCall(callId) : _atcRemoteCalls[callId]; - - if (!call) { - LogSvc.debug('[CstaSvc]: Could not find a call with Call Id = ' + callId); - LogSvc.debug('[CstaSvc]: Publish /atccall/hangingcall event'); - PubSubSvc.publish('/atccall/hangingcall', callId); - return; - } - - LogSvc.debug('[CstaSvc]: Publish /atccall/moveFailed event'); - PubSubSvc.publish('/atccall/moveFailed'); - // If the original call is already in Terminated state, it needs to be recreated - if (call.checkState([CallState.Terminated, CallState.Idle])) { - var atcCallInfo = call.atcCallInfo; - var peerUser = call.peerUser; - var establishedTime = call.establishedTime; - var direction = call.direction; - delete _atcRemoteCalls[callId]; - - call = createAtcRemoteCall(callId); - call.atcCallInfo = atcCallInfo; - call.peerUser = peerUser; - call.establishedTime = establishedTime; - call.direction = direction; - _atcRemoteCalls[callId] = call; - } - call.clearAtcHandoverInProgress(); - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - } - - function handleBackInServiceEvent() { - _onsUnregistered = false; - LogSvc.debug('[CstaSvc]: Publish /csta/deviceChange event'); - PubSubSvc.publish('/csta/deviceChange'); - } - - function handleOutOfServiceEvent() { - _onsUnregistered = true; - LogSvc.debug('[CstaSvc]: Publish /csta/deviceChange event'); - PubSubSvc.publish('/csta/deviceChange'); - } - - function handleForwardingEvent(data) { - if (!data) { - return; - } - var found = _osmoData.forwardList.some(function (element) { - if (element.forwardingType === data.forwardingType) { - element.forwardStatus = data.forwardStatus; - element.forwardDN = data.forwardTo; - return true; - } - }); - if (!found) { - _osmoData.forwardList.push({ - forwardDN: data.forwardTo, - forwardStatus: data.forwardStatus, - forwardType: data.forwardingType - }); - } - - if (data.forwardingType === 'forwardImmediate') { - $rootScope.localUser.callForwardingAvailable = true; - if (data.forwardTo !== _osmoData.vm) { - $rootScope.localUser.callForwardingNumber = PhoneNumberFormatter.format(data.forwardTo); - $rootScope.localUser.callForwardingEnabled = data.forwardStatus; - $rootScope.localUser.callForwardingToVMEnabled = false; - } else { - $rootScope.localUser.callForwardingToVMEnabled = data.forwardStatus; - $rootScope.localUser.callForwardingEnabled = false; - } - } - - if (data.hasOwnProperty('staticOndActive')) { - if (data.staticOndActive) { - _savedStaticOnd = data.staticOndDN; - if (data.staticOndDN === Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber)) { - $rootScope.localUser.selectedRoutingOption = RoutingOptions.AlternativeNumber.name; - } else { - $rootScope.localUser.selectedRoutingOption = RoutingOptions.Other.name; - $rootScope.localUser.otherRoutingNumber = PhoneNumberFormatter.format(data.staticOndDN); - } - } else { - _savedStaticOnd = ''; - if ($rootScope.localUser.selectedRoutingOption === RoutingOptions.AlternativeNumber.name || - $rootScope.localUser.selectedRoutingOption === RoutingOptions.Other.name) { - $rootScope.localUser.selectedRoutingOption = RoutingOptions.DefaultRouting.name; - } - } - } - - if (data.hasOwnProperty('voicemailActive')) { - $rootScope.localUser.voicemailPBXEnabled = true; - $rootScope.localUser.voicemailActive = data.voicemailActive; - $rootScope.localUser.voicemailRingDuration = data.voicemailRingDuration; - _savedVMRingDuration = data.voicemailRingDuration; - } - - if (data.hasOwnProperty('mainRingDuration')) { - $rootScope.localUser.ringDurationEnabled = true; - $rootScope.localUser.mainRingDuration = data.mainRingDuration; - $rootScope.localUser.clientRingDuration = data.clientRingDuration; - $rootScope.localUser.cellRingDuration = data.cellRingDuration; - _savedMainRingDuration = data.mainRingDuration; - _savedClientRingDuration = data.clientRingDuration; - _savedCellRingDuration = data.cellRingDuration; - $rootScope.localUser.alternativeNumber = data.alternativeNumber; - $rootScope.localUser.routeToCell = data.routeToCell; - if (data.hasOwnProperty('overrideProfile')) { - $rootScope.localUser.ringDurationConfigurable = data.overrideProfile; - } - } - - LogSvc.debug('[CstaSvc]: Publish /localUser/update event'); - PubSubSvc.publish('/localUser/update', [$rootScope.localUser]); - - LogSvc.debug('[CstaSvc]: Publish /csta/forwardingEvent'); - PubSubSvc.publish('/csta/forwardingEvent'); - } - - function handleAgentReadyEvent() { - if ($rootScope.localUser && $rootScope.localUser.isAgent && !$rootScope.localUser.agentStateReady) { - $rootScope.localUser.agentStateReady = true; - LogSvc.debug('[CstaSvc]: Publish /agent/state/ready event'); - PubSubSvc.publish('/agent/state/ready', [$rootScope.localUser.agentStateReady]); - } - } - - function handleAgentNotReadyEvent() { - if ($rootScope.localUser && $rootScope.localUser.isAgent && $rootScope.localUser.agentStateReady) { - $rootScope.localUser.agentStateReady = false; - LogSvc.debug('[CstaSvc]: Publish /agent/state/ready event'); - PubSubSvc.publish('/agent/state/ready', [$rootScope.localUser.agentStateReady]); - } - } - - function handover(call, target, destination, cb) { - if (!getRegistrationData(cb)) { - return; - } - - if (!call.atcCallInfo || ((!call.checkCstaState([CstaCallState.Active])) && (!call.atcCallInfo.isHandoverAllowed() && !call.atcCallInfo.isDeflectAllowed()))) { - cb && cb('res_MoveCallFailed'); - return; - } - if (!isTargetAllowed(call, target)) { - cb && cb('The selected target is not available for this call.'); - return; - } - - var newDestination = buildNewDestination(target, destination); - if (!newDestination) { - cb && cb('Failed to create new destination.'); - return; - } - - var data = { - autoAnswer: (target === Targets.Desk || target === Targets.WebRTC) - }; - - if (call.checkCstaState([CstaCallState.Ringing, CstaCallState.Parked])) { - // Need to send a CSTA Deflect Call request instead of CSTA SST request - data.request = 'DeflectCall'; - data.callToBeDiverted = call.atcCallInfo.getCstaConnection(); - data.newDestination = newDestination; - - // 4K sends the DeflectCall response after sending the new INVITE to the destination, so we need to inform - // circuitCallControlSvc earlier about the incoming handover call - if (!$rootScope.localUser.isOSV) { - LogSvc.debug('[CstaSvc]: Publish /csta/handover event'); - PubSubSvc.publish('/csta/handover', call.atcCallInfo.getCstaCallId()); - } - call.setAtcHandoverInProgress(); - sendCstaRequest(data, function (err) { - if (err) { - cb && cb('res_MoveCallFailed'); - call.clearAtcHandoverInProgress(); - if (!$rootScope.localUser.isOSV) { - // Inform circuitCallControlSvc that the handover failed - LogSvc.debug('[CstaSvc]: Publish /atccall/moveFailed event'); - PubSubSvc.publish('/atccall/moveFailed', [true]); - } - } else { - if ($rootScope.localUser.isOSV) { - LogSvc.debug('[CstaSvc]: Publish /csta/handover event'); - PubSubSvc.publish('/csta/handover', call.atcCallInfo.getCstaCallId()); - } - // 4K sends the Diverted after the call has been redirected, which is too late to set the - // ignoreCall flag - if (target === Targets.WebRTC) { - call.atcCallInfo.setIgnoreCall(true); - } - cb && cb(null); - } - }); - } else { - data.request = 'SingleStepTransferCall'; - data.activeCall = call.atcCallInfo.getCstaConnection(); - data.transferredTo = newDestination; - data.seamlessHandover = call.atcCallInfo.isSeamlessHandoverAllowed(); - - // 4K sends the SST response after sending the new INVITE to the destination, so we need to inform - // circuitCallControlSvc earlier about the incoming handover call - if (!$rootScope.localUser.isOSV) { - LogSvc.debug('[CstaSvc]: Publish /csta/handover event'); - PubSubSvc.publish('/csta/handover', call.atcCallInfo.getCstaCallId()); - } - call.setAtcHandoverInProgress(); - - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - cb && cb('res_MoveCallFailed'); - call.clearAtcHandoverInProgress(); - if (!$rootScope.localUser.isOSV) { - // Inform circuitCallControlSvc that the handover failed - LogSvc.debug('[CstaSvc]: Publish /atccall/moveFailed event'); - PubSubSvc.publish('/atccall/moveFailed', [true]); - } - } else { - storeTransferredCall(data.activeCall.cID, rs); - _handoverCall = { - primaryCallId: data.activeCall.cID, - newCallId: rs.transferredCall.cID, - direction: call.direction, - redirectingUser: call.redirectingUser - }; - - if ($rootScope.localUser.isOSV) { - LogSvc.debug('[CstaSvc]: Publish /csta/handover event'); - PubSubSvc.publish('/csta/handover', call.atcCallInfo.getCstaCallId()); - } - cb && cb(null); - } - }); - } - } - - function createEarlyAtcRemoteCall(destination, target) { - var earlyAtcRemoteCall = createAtcRemoteCall(destination); - setCallPeerUser(earlyAtcRemoteCall, destination, destination); - earlyAtcRemoteCall.setCstaState(CstaCallState.Delivered); - earlyAtcRemoteCall.direction = CallDirection.OUTGOING; - earlyAtcRemoteCall.atcCallInfo.setPosition(target); - var display = { - dn: destination, - fqn: destination, - name: '' - }; - earlyAtcRemoteCall.atcCallInfo.setPartnerDisplay(display); - earlyAtcRemoteCall.atcCallInfo.setIgnoreCall(true); - _atcRemoteCalls[destination] = earlyAtcRemoteCall; - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [earlyAtcRemoteCall]); - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', earlyAtcRemoteCall); - } - - function updateEarlyAtcRemoteCall(connection, destination, target) { - var call = findAtcRemoteCall(destination); - if (!call) { - call = createAtcRemoteCall(connection.cID); - setCallPeerUser(call, destination, destination); - call.setCstaState(CstaCallState.Delivered); - call.direction = CallDirection.OUTGOING; - call.atcCallInfo.setCstaConnection(connection); - call.atcCallInfo.setPosition(target); - var display = { - dn: destination, - fqn: destination, - name: '' - }; - call.atcCallInfo.setPartnerDisplay(display); - } else { - call.atcCallInfo.setCstaConnection(connection); - call.setCallIdForTelephony(connection.cID); - call.setCstaState(CstaCallState.Delivered); - call.atcCallInfo.setIgnoreCall(false); - delete _atcRemoteCalls[destination]; - } - _atcRemoteCalls[connection.cID] = call; - LogSvc.debug('[CstaSvc]: Publish /atccall/info event'); - PubSubSvc.publish('/atccall/info', [call]); - LogSvc.debug('[CstaSvc]: Created new Remote Call object: ', call); - } - - function createFailedCallEntry(destination, target, err) { - var call = createAtcRemoteCall(Utils.generateCallId()); - setCallPeerUser(call, destination, destination); - call.direction = CallDirection.OUTGOING; - call.atcCallInfo.setPosition(target); - if (err.endsWith('invalidCalledDeviceID')) { - call.atcCallInfo.setMissedReason(MissedReasonTypes.REORDER_TONE); - } else if (err.endsWith('requestIncompatibleWithCallingDevice') || err.endsWith('invalidDeviceID')) { - return; - } else { - call.atcCallInfo.setMissedReason(MissedReasonTypes.DEST_OUT_OF_ORDER); - } - createJournalEntry(call); - } - - function getCallDevices() { - var devices = []; - if (WebRTCAdapter.enabled) { - devices.push(Targets.WebRTC); - } - if (!_onsUnregistered) { - devices.push(Targets.Desk); - } - if (_osmoData && _osmoData.cell) { - devices.push(Targets.Cell); - } - return devices; - } - - function getPushDevices() { - var devices = []; - if (!_onsUnregistered) { - devices.push(Targets.Desk); - } - if (_osmoData && _osmoData.cell) { - devices.push(Targets.Cell); - } - return devices; - } - - function getAnswerDevices(call) { - var devices = []; - if (call) { - if (!_onsUnregistered) { - devices.push(Targets.Desk); - } - if (_osmoData && _osmoData.cell && call.getPosition() !== Targets.Cell) { - devices.push(Targets.Cell); - } - if (_osmoData && _osmoData.vm) { - devices.push(Targets.VM); - } - } - return devices; - } - - function getMyDevices() { - var devices = getCallDevices(); - if (_osmoData && _osmoData.vm) { - devices.push(Targets.VM); - } - devices.push(Targets.Other); - return devices; - } - - function handleFirstCall(call) { - var connection = (call.atcCallInfo && call.atcCallInfo.getCstaConnection()) || _incomingCallConnection; - if (_primaryClient) { - var data = { - request: 'DeflectCall', - callToBeDiverted: connection, - newDestination: buildNewDestination(Targets.Desk) - }; - sendCstaRequest(data); - } - } - - function handleSecondCall(call) { - var data = null; - if ($rootScope.localUser.selectedBusyHandlingOption !== BusyHandlingOptions.DefaultRouting.name) { - var connection = (call.atcCallInfo && call.atcCallInfo.getCstaConnection()) || _incomingCallConnection; - if (_primaryClient) { - switch ($rootScope.localUser.selectedBusyHandlingOption) { - case BusyHandlingOptions.SendToAlternativeNumber.name: - data = { - request: 'DeflectCall', - callToBeDiverted: connection, - newDestination: buildNewDestination(Targets.Cell) - }; - break; - case BusyHandlingOptions.BusySignal.name: - data = { - request: 'ClearConnection', - connectionToBeCleared: connection, - reason: 'busy' - }; - break; - case BusyHandlingOptions.SendToVM.name: - data = { - request: 'DeflectCall', - callToBeDiverted: connection, - newDestination: buildNewDestination(Targets.VM) - }; - break; - default: - return; - } - sendCstaRequest(data, function (err) { - if (!err) { - if (data.request === 'ClearConnection') { - if (call.atcCallInfo) { - call.atcCallInfo.setMissedReason(MissedReasonTypes.BUSY); - // This is the case that the second call was rejected upon the Offered event. The CSTA - // state needs to be changed - if (call.checkCstaState(CstaCallState.Offered)) { - call.setCstaState(CstaCallState.Failed); - } - } else if (AtcRegistrationSvc.isClearConnectionBusySupported()) { - var remoteCall = _atcRemoteCalls[connection.cID] || createAtcRemoteCall(connection.cID); - remoteCall.peerUser = call.peerUser; - remoteCall.direction = CallDirection.INCOMING; - remoteCall.creationTime = call.creationTime; - remoteCall.atcCallInfo.setMissedReason(MissedReasonTypes.BUSY); - _atcRemoteCalls[connection.cID] = remoteCall; - removeAtcRemoteCall(connection.cID); - } - } - } - }); - _incomingCallConnection = {}; - } - } else if (call.checkCstaState(CstaCallState.Offered)) { - data = { - request: 'AcceptCall', - callToBeAccepted: call.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data); - _incomingCallConnection = {}; - } - } - - function handleGetForwardingResp(err, data) { - if (err || !data) { - return; - } - - $rootScope.localUser.callForwardingAvailable = !$rootScope.localUser.isOSV; - if (data.forwardList && data.forwardList.length > 0) { - data.forwardList.forEach(function (element) { - if (element.forwardingType === 'forwardImmediate') { - $rootScope.localUser.callForwardingAvailable = true; - if (element.forwardDN !== _osmoData.vm) { - $rootScope.localUser.callForwardingNumber = PhoneNumberFormatter.format(element.forwardDN); - $rootScope.localUser.callForwardingEnabled = element.forwardStatus; - } else { - $rootScope.localUser.callForwardingToVMEnabled = element.forwardStatus; - } - } - }); - } - - _osmoData.forwardList = data.forwardList || []; - - if (data.voicemailActive) { - $rootScope.localUser.voicemailPBXEnabled = true; - $rootScope.localUser.voicemailActive = data.voicemailActive; - $rootScope.localUser.voicemailRingDuration = data.voicemailRingDuration; - _savedVMRingDuration = data.voicemailRingDuration; - } - - if (data.mainRingDuration) { - $rootScope.localUser.ringDurationEnabled = true; - $rootScope.localUser.mainRingDuration = data.mainRingDuration; - $rootScope.localUser.clientRingDuration = data.clientRingDuration; - $rootScope.localUser.cellRingDuration = data.cellRingDuration; - _savedMainRingDuration = data.mainRingDuration; - _savedClientRingDuration = data.clientRingDuration; - _savedCellRingDuration = data.cellRingDuration; - $rootScope.localUser.alternativeNumber = data.alternativeNumber || Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber); - $rootScope.localUser.routeToCell = data.routeToCell; - if (data.hasOwnProperty('overrideProfile')) { - $rootScope.localUser.ringDurationConfigurable = data.overrideProfile; - } - } - LogSvc.debug('[CstaSvc]: Publish /localUser/update event'); - PubSubSvc.publish('/localUser/update', [$rootScope.localUser]); - - LogSvc.debug('[CstaSvc]: Publish /csta/getForwardingResponse'); - PubSubSvc.publish('/csta/getForwardingResponse'); - - // Due to OSV-3432 bug, the Get Forwarding Response does not contain staticOND - // data, so we cannot rely on it for now. - syncRoutingOption(); - } - - function handleGetAgentStateResponse(err, data) { - if (err || !data) { - return; - } - $rootScope.localUser.isAgent = data.loggedOnState; - if ($rootScope.localUser.isAgent) { - $rootScope.localUser.agentStateReady = (data.agentState === AgentState.Ready); - LogSvc.debug('[CstaSvc]: Publish /agent/state/ready event'); - PubSubSvc.publish('/agent/state/ready', [$rootScope.localUser.agentStateReady]); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - PubSubSvc.subscribe('/atcRegistration/state', function (newState) { - LogSvc.debug('[CstaSvc]: Received /atcRegistration/state event.'); - _regState = newState; - if ($rootScope.localUser.isATC) { - if (!_snapshotPerformed && newState === AtcRegistrationState.Registered && _receivedActiveSessions) { - snapshotRemoteCallsAndGetFeatureData(); - } else { - _snapshotPerformed = false; - } - } - }); - - PubSubSvc.subscribe('/activeSessions/received', function () { - LogSvc.debug('[CstaSvc]: Received /activeSessions/received event'); - _receivedActiveSessions = true; - if (!_snapshotPerformed) { - if (_regState === AtcRegistrationState.Registered) { - snapshotRemoteCallsAndGetFeatureData(); - } - } - }); - - PubSubSvc.subscribe('/registration/state', function (state) { - LogSvc.debug('[CstaSvc]: Received /registration/state event'); - if (state !== RegistrationState.Registered) { - _receivedActiveSessions = false; - } - }); - - PubSubSvc.subscribe('/call/incoming', function (call) { - if (call && _telephonyConversation && call.convId === _telephonyConversation.convId) { - LogSvc.debug('[CstaSvc]: Received /call/incoming event.'); - // ANS-11246: Since we still don't support a PROGRESS(alerting) message, it is - // possible for the CSTA Delivered event to arrive before the Circuit INVITE - // message. This happens because the ATC immediately sends the SIP 180 Ringing - // response back to the switch without waiting for the alerting indication from - // the client. Let's check if this is the case here! - Object.keys(_atcRemoteCalls).some(function (callId) { - var call = _atcRemoteCalls[callId]; - if (call.checkCstaState(CstaCallState.Ringing) && call.atcCallInfo.getPosition() === Targets.WebRTC) { - // Found it - _alertingCall = findAlertingCall(); - if (_alertingCall) { - LogSvc.debug('[CstaSvc]: Found a matching ATC remote call. Move call info to alerting call.'); - _alertingCall.atcCallInfo = call.atcCallInfo; - call.atcCallInfo = new AtcCallInfo(); - call.atcCallInfo.setIgnoreCall(true); - removeAtcRemoteCall(callId); - } - return true; - } - }); - } - }); - - PubSubSvc.subscribe('/atccall/firstcall', function (call) { - LogSvc.debug('[CstaSvc]: Received /atccall/firstcall event'); - handleFirstCall(call); - }); - - PubSubSvc.subscribe('/atccall/secondcall', function (call) { - LogSvc.debug('[CstaSvc]: Received /atccall/secondcall event.'); - handleSecondCall(call); - }); - - PubSubSvc.subscribe('/conversation/item/add', function (item) { - if (item && _telephonyConversation && item.convId === _telephonyConversation.convId && item.rtc && - item.rtc.rtcParticipants && item.rtc.rtcParticipants.length === 2) { - _lastTwoCallPartners.unshift(item.rtc.rtcParticipants[0].type === 'TELEPHONY' ? item.rtc.rtcParticipants[0] : item.rtc.rtcParticipants[1]); - if (_lastTwoCallPartners.length > 2) { - _lastTwoCallPartners.pop(); - } - - } - }); - - PubSubSvc.subscribe('/call/ended', function (call) { - if (call && call.isTelephonyCall && !call.isRemote) { - _lastEndedCall = call; - $timeout.cancel(_endedCallWaitTimer); - _endedCallWaitTimer = $timeout(function () { - _lastEndedCall = null; - _endedCallWaitTimer = null; - }, MAX_ENDED_CALL_WAIT_TIME); - } - refreshData(); - }); - - PubSubSvc.subscribe('/routing/alternativeNumber/changed', function () { - LogSvc.debug('[CstaSvc]: Received /routing/alternativeNumber/changed event'); - syncRoutingOption(); - }); - - PubSubSvc.subscribe('/localUser/update', function () { - if ($rootScope.localUser.isATC) { - LogSvc.debug('[CstaSvc]: Received /localUser/update event'); - syncRoutingOption(); - } - }); - - /////////////////////////////////////////////////////////////////////////// - // Public interfaces - /////////////////////////////////////////////////////////////////////////// - this.alternate = function (heldCall, activeCall, cb) { - if (!getRegistrationData(cb)) { - return; - } - if (!heldCall || !activeCall) { - cb && cb('Invalid calls to alternate'); - return; - } - heldCall.setRetrieveInProgress(); - activeCall.setHoldInProgress(); - var data = { }; - data.request = 'AlternateCall'; - data.activeCall = activeCall.atcCallInfo.getCstaConnection(); - data.heldCall = heldCall.atcCallInfo.getCstaConnection(); - sendCstaRequest(data, function (err) { - if (err) { - // If for some reason the Alternate request failed, reset the inProgress indication - heldCall.clearRetrieveInProgress(); - activeCall.clearHoldInProgress(); - cb && cb('res_RetrieveCallFailed'); - } else { - cb && cb(null); - } - }); - - }; - - this.conference = function (heldCall, activeCall, cb) { - if (!getRegistrationData(cb)) { - return; - } - if (!heldCall || !activeCall) { - cb && cb('Invalid calls to conference'); - return; - } - var data = {}; - data.request = 'ConferenceCall'; - data.activeCall = activeCall.atcCallInfo.getCstaConnection(); - data.heldCall = heldCall.atcCallInfo.getCstaConnection(); - - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - cb && cb('res_MergeCallFailed'); - } - }); - }; - - this.answer = function (callId, cb) { - if (!getRegistrationData(cb)) { - return; - } - - var call = findAtcRemoteCall(callId); - if (!call) { - cb && cb('There is no call to be answered'); - return; - } - - var data = { - request: 'AnswerCall', - callToBeAnswered: call.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data, cb); - }; - - this.pickupCall = function (callId, device, cb) { - if (!getRegistrationData(cb)) { - return; - } - var call = findAtcRemoteCall(callId); - if (call && call.pickupNotification) { - device = device || Targets.WebRTC; - var data = { - request: 'GroupPickupCall', - newDestination: buildNewDestination(device) - }; - sendCstaRequest(data, cb); - LogSvc.debug('[CstaSvc]: Publish /atccall/pickUpInProgress'); - PubSubSvc.publish('/atccall/pickUpInProgress', [call]); - call.setAtcHandoverInProgress(); - } else { - cb && cb('There is no call to be picked up'); - } - }; - - this.clearTransferredCall = function (callID) { - if (!_transferredCalls[callID]) { - return; - } - $timeout.cancel(_transferredCalls[callID].timeout); - delete _transferredCalls[callID]; - }; - - this.consultationTransfer = function (activeCall, heldCall, cb) { - if (!getRegistrationData(cb)) { - return; - } - - var data = { - request: 'TransferCall', - heldCall: heldCall.atcCallInfo.getCstaConnection(), - activeCall: activeCall.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - cb && cb(TransferCallFailedCauses.Unreachable.ui); - } else { - cb && cb(null); - } - }); - }; - - this.deflect = function (callId, target, destination, cb) { - if (!getRegistrationData(cb)) { - return; - } - - var call = findWebRTCCall(callId) || CircuitCallControlSvc.getIncomingCall() || findAtcRemoteCall(callId); - - if (!call) { - cb && cb('There is no call to be deflected'); - return; - } - - if (!isTargetAllowed(call, target)) { - cb && cb('The selected target is not available for this call.'); - return; - } - - var newDestination = buildNewDestination(target, destination); - - if (!newDestination) { - cb && cb('Failed to create new destination.'); - return; - } - - var data = { - request: 'DeflectCall', - callToBeDiverted: call.atcCallInfo.getCstaConnection(), - newDestination: newDestination, - autoAnswer: (target === Targets.Desk || target === Targets.WebRTC) - }; - sendCstaRequest(data, function (err) { - if (err) { - cb && cb('res_MoveCallFailed'); - return; - } - if (target !== Targets.VM) { - call.setAtcHandoverInProgress(); - } - }); - }; - - this.generateDigits = function (call, digits, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GenerateDigits', - connectionToSendDigits: call.atcCallInfo.getCstaConnection(), - charactersToSend: digits - }; - sendCstaRequest(data, cb); - } - }; - - this.getDoNotDisturb = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GetDoNotDisturb', - device: _osmoData.ons - }; - sendCstaRequest(data, cb); - } - }; - - this.getForwarding = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GetForwarding', - device: $rootScope.localUser.isOSV ? Utils.normalizeDn(_osmoData.onsFQN) : _osmoData.onsFQN - }; - sendCstaRequest(data, cb); - } - }; - - this.getMessageWaiting = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GetMessageWaitingIndicator', - device: _osmoData.ons - }; - sendCstaRequest(data, cb); - } - }; - - this.getTransferredCall = function (callID) { - return _transferredCalls[callID]; - }; - - this.pullRemoteCall = function (callId, undefined, cb) { - var call = findAtcRemoteCall(callId); - if (call) { - if (call.pickupNotification) { - _that.pickupCall(callId, null, cb); - } else { - handover(call, Targets[Targets.WebRTC.name], null, cb); - } - } else { - LogSvc.warn('[CstaSvc]: pullRemoteCall invoked without a valid call'); - cb('No active remote call'); - } - }; - - this.pushLocalCall = function (callId, target, cb) { - refreshData(); - var call = _activeCall && _activeCall.callId === callId ? _activeCall : _heldCall && _heldCall.callId === callId ? _heldCall : null; - if (call) { - handover(call, Targets[target], null, cb); - } else { - LogSvc.warn('[CstaSvc]: pushLocalCall invoked without a valid call'); - cb('No active local call'); - } - }; - - this.endRemoteCall = function (callId, cb) { - var call = findAtcRemoteCall(callId); - if (call) { - this.hangupRemote(call, cb); - } else { - LogSvc.warn('[CstaSvc]: endRemoteCall invoked without a valid call'); - cb('No active remote call'); - } - }; - - this.hangupRemote = function (call, cb) { - if (call.isRemote && getRegistrationData(cb)) { - var data = { - request: 'ClearConnection', - connectionToBeCleared: call.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data, function (err) { - if (err) { - cb && cb(err); - } else { - if (!call.getCstaState().established && !call.atcCallInfo.getMissedReason()) { - if (call.direction === CallDirection.INCOMING) { - call.atcCallInfo.setMissedReason(MissedReasonTypes.DECLINED); - } else { - call.atcCallInfo.setMissedReason(MissedReasonTypes.CANCELLED); - } - } - removeAtcRemoteCall(call.atcCallInfo.getCstaCallId()); - cb && cb(); - } - }); - } - }; - - this.hold = function (call, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'HoldCall', - callToBeHeld: call.atcCallInfo.getCstaConnection() - }; - call.setHoldInProgress(); - sendCstaRequest(data, function (err) { - if (err) { - // If for some reason the Hold request failed, the holdInProgress indication needs to be reset - call.clearHoldInProgress(); - cb && cb('res_HoldCallFailed'); - } else { - cb && cb(null); - } - }); - } - }; - - this.reconnect = function (activeCall, heldCall, cb) { - if (!getRegistrationData(cb)) { - return; - } - - var data = { - request: 'ReconnectCall', - heldCall: heldCall.atcCallInfo.getCstaConnection(), - activeCall: activeCall.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data, cb); - }; - - this.retrieve = function (call, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'RetrieveCall', - callToBeRetrieved: call.atcCallInfo.getCstaConnection() - }; - call.setRetrieveInProgress(); - sendCstaRequest(data, function (err) { - if (err) { - // If for some reason the Retrieve request failed, the holdInProgress indication needs to be reset - call.clearRetrieveInProgress(); - cb && cb('res_RetrieveCallFailed'); - } else { - cb && cb(null); - } - }); - } - }; - - this.setDoNotDisturb = function (doNotDisturbOn, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'SetDoNotDisturb', - device: _osmoData.ons, - doNotDisturbOn: doNotDisturbOn - }; - sendCstaRequest(data, cb); - } - }; - - this.setForwarding = function (data, cb) { - if (getRegistrationData(cb)) { - - var msg = { - request: 'SetForwarding', - device: $rootScope.localUser.isOSV ? Utils.normalizeDn(_osmoData.onsFQN) : _osmoData.onsFQN, - activateForward: data.activateForward, - forwardDN: data.forwardDN || '', - forwardingType: data.forwardingType, - ringCount: data.ringCount - }; - - sendCstaRequest(msg, cb); - } - }; - - this.snapshotCall = function (conn, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'SnapshotCall', - snapshotObject: conn - }; - sendCstaRequest(data, cb); - } - }; - - this.snapshotDevice = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'SnapshotDevice', - snapshotObject: _osmoData.onsFQN, - osmo: _osmoData.osmo - }; - sendCstaRequest(data, cb); - } - }; - - this.transfer = function (call, destination, cb) { - if (!getRegistrationData(cb)) { - return; - } - - var data = {}; - - if (call.checkCstaState([CstaCallState.Ringing, CstaCallState.Parked])) { - // Need to send a CSTA Deflect Call request instead of CSTA SST request - data.request = 'DeflectCall'; - data.callToBeDiverted = call.atcCallInfo.getCstaConnection(); - data.newDestination = destination; - - sendCstaRequest(data, cb); - } else { - data.request = 'SingleStepTransferCall'; - data.activeCall = call.atcCallInfo.getCstaConnection(); - data.transferredTo = destination; - - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - if (err && err.indexOf('internalResourceBusy') !== -1) { - // If the renegotiation, before the transfer, takes too long, OSV sends CSTA Error with - // internalResourceBusy, but the transfer may succeed afterwards. So we need to continue - // tracking this call - storeTransferredCall(data.activeCall.cID, {transferredCall: {cID: DEFAULT_TRANSFER_CALLID}}); - cb && cb(null); - return; - } - cb && cb(TransferCallFailedCauses.Unreachable.ui); - } else { - storeTransferredCall(data.activeCall.cID, rs); - cb && cb(null); - } - }); - } - }; - - this.consultationCall = function (call, destination, cb) { - if (!getRegistrationData(cb)) { - return; - } - var data = {}; - data.request = 'ConsultationCall'; - data.consultedDevice = destination; - data.existingCall = call.atcCallInfo.getCstaConnection(); - createEarlyAtcRemoteCall(destination, call.atcCallInfo.getPosition()); - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - cb && cb('res_MakeCallFailed'); - removeAtcRemoteCall(destination); - createFailedCallEntry(destination, null, err); - } else { - // Update the new call with the consultationCall response in order to show it in the UI - updateEarlyAtcRemoteCall(rs.iC, destination, call.atcCallInfo.getPosition()); - cb && cb(); - } - }); - }; - - this.makeCall = function (target, destination, cb) { - if (!getRegistrationData(cb)) { - return; - } - var data = {}; - data.request = 'MakeCall'; - data.calledDirectoryNumber = destination; - data.callingDevice = buildNewDestination(target); - data.autoOriginate = (target === Targets.Desk || target === Targets.WebRTC) ? 'doNotPrompt' : 'prompt'; - createEarlyAtcRemoteCall(destination, target); - sendCstaRequest(data, function (err, rs) { - if (err || !rs) { - cb && cb('res_MakeCallFailed'); - removeAtcRemoteCall(destination); - createFailedCallEntry(destination, target, err); - } else { - // Update the new call with the makeCall response in order to show it in the UI - updateEarlyAtcRemoteCall(rs.caD, destination, target); - cb && cb(); - } - }); - }; - - this.ignoreCall = function (call, cb) { - removeAtcRemoteCall(call.callId); - cb && cb(); - }; - - this.callBackCallRelated = function (call, cb) { - if (!getRegistrationData(cb)) { - return; - } - var data = { - request: 'CallBackCallRelated', - callBackConnection: call.atcCallInfo.getCstaConnection() - }; - sendCstaRequest(data, cb); - }; - - this.callBackNonCallRelated = function (target, cb) { - if (!getRegistrationData(cb)) { - return; - } - var data = { - request: 'CallBackNonCallRelated', - orD: _osmoData.onsFQN, - tD: target - }; - sendCstaRequest(data, cb); - }; - - this.cancelCallBack = function (target, cb) { - if (!getRegistrationData(cb)) { - return; - } - var data = { - request: 'CancelCallBack', - orD: _osmoData.onsFQN, - td: target || undefined - }; - sendCstaRequest(data, cb); - }; - - this.setMicrophoneMute = function (mute, cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'SetMicrophoneMute', - device: _osmoData.ons, - mute: mute - }; - sendCstaRequest(data, cb); - } - }; - - this.getMicrophoneMute = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GetMicrophoneMute', - device: _osmoData.ons - }; - sendCstaRequest(data, cb); - } - }; - - this.callLogSnapShot = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'CallLogSnapShot', - device: _osmoData.ons - }; - sendCstaRequest(data, cb); - } - }; - - this.snapshot = function () { - snapshotRemoteCalls(); - }; - - this.getAgentState = function (cb) { - if (getRegistrationData(cb)) { - var data = { - request: 'GetAgentState', - device: _osmoData.onsFQN - }; - sendCstaRequest(data, cb); - } - }; - - this.setAgentState = function (ready, cb) { - ready = !!ready; - if (ready !== $rootScope.localUser.agentStateReady) { - if (getRegistrationData(cb)) { - var data = { - request: 'SetAgentState', - device: _osmoData.onsFQN, - state: ready ? AgentState.Ready : AgentState.NotReady - }; - sendCstaRequest(data, cb); - } - } - }; - - this.handleCallEvent = function (eventData) { - if (!eventData) { - return; - } - refreshData(); - - try { - switch (eventData.name) { - case 'CallInformationEvent': - handleCallInformationEvent(eventData); - break; - case 'ConferencedEvent': - handleConferencedEvent(eventData); - break; - case 'ConnectionClearedEvent': - handleConnectionClearedEvent(eventData); - break; - case 'DeliveredEvent': - handleDeliveredEvent(eventData); - break; - case 'DigitsGeneratedEvent': - handleDigitsGeneratedEvent(eventData); - break; - case 'DivertedEvent': - handleDivertedEvent(eventData); - break; - case 'EstablishedEvent': - handleEstablishedEvent(eventData); - break; - case 'FailedEvent': - handleFailedEvent(eventData); - break; - case 'HeldEvent': - handleHeldEvent(eventData); - break; - case 'OfferedEvent': - handleOfferedEvent(eventData); - break; - case 'QueuedEvent': - handleQueuedEvent(eventData); - break; - case 'RetrievedEvent': - handleRetrievedEvent(eventData); - break; - case 'ServiceCompletionFailureEvent': - handleServiceCompletionFailureEvent(eventData); - break; - case 'ServiceInitiatedEvent': - handleServiceInitiatedEvent(eventData); - break; - case 'TransferredEvent': - handleTransferredEvent(eventData); - break; - default: - LogSvc.debug('[CstaSvc]: Ignore CSTA Call Event (' + eventData.name + ')'); - break; - } - } catch (error) { - LogSvc.error(error); - } - }; - - this.handleDeviceEvent = function (eventData) { - if (!eventData) { - return; - } - - try { - switch (eventData.name) { - case 'BackInServiceEvent': - handleBackInServiceEvent(); - break; - case 'OutOfServiceEvent': - handleOutOfServiceEvent(); - break; - default: - LogSvc.debug('[CstaSvc]: Ignore CSTA Device Event (' + eventData.name + ')'); - break; - } - } catch (error) { - LogSvc.error(error); - } - }; - - this.handleLogicalDeviceEvent = function (eventData) { - if (!eventData) { - return; - } - - try { - switch (eventData.name) { - case 'ForwardingEvent': - handleForwardingEvent(eventData); - break; - case 'AgentReadyEvent': - handleAgentReadyEvent(); - break; - case 'AgentNotReadyEvent': - case 'AgentBusyEvent': - handleAgentNotReadyEvent(); - break; - default: - LogSvc.debug('[CstaSvc]: Ignore CSTA Logical Device Event (' + eventData.name + ')'); - break; - } - } catch (error) { - LogSvc.error(error); - } - }; - - - this.isPrimaryClient = function () { - return _primaryClient; - }; - - this.getDisplayInfo = getDisplayInfo; - - this.getCallDevices = getCallDevices; - - this.getPushDevices = getPushDevices; - - this.getAnswerDevices = getAnswerDevices; - - this.updateStaticOnd = function (status, cb) { - if (getRegistrationData(cb)) { - status = !!status; - LogSvc.debug('[CstaSvc]: Update static OND status to ', status); - var data = { - staticOndActive: status - }; - - if (status) { - data.staticOndDN = Utils.cleanPhoneNumber($rootScope.localUser.reroutingPhoneNumber); - if (!data.staticOndDN) { - cb && cb('Cannot activate static OND without the static OND DN'); - return; - } - } - - var hasSavedStaticOnd = !!_savedStaticOnd; - if (status !== hasSavedStaticOnd || (status && data.staticOndDN !== _savedStaticOnd)) { - var msg = { - request: 'SetForwarding', - device: Utils.normalizeDn(_osmoData.onsFQN), - staticOndActive: data.staticOndActive, - staticOndDN: data.staticOndDN - }; - - sendCstaRequest(msg, function (err) { - if (err) { - cb && cb(err); - } else { - _savedStaticOnd = data.staticOndDN || ''; - cb && cb(null); - } - }); - } else { - cb && cb(null); - } - } - }; - - this.updateRoutingOption = function (option, cb) { - switch (option) { - case RoutingOptions.AlternativeNumber.name: - _that.updateStaticOnd(true, function (err) { - if (!err) { - _that.setForwardingToVM(false, cb); - } else { - cb && cb(err); - } - }); - break; - case RoutingOptions.VM.name: - _that.setForwardingToVM(true, function (err) { - if (err) { - cb && cb(err); - } else { - _that.updateStaticOnd(false, cb); - } - }); - break; - default: - _that.setForwardingToVM(false, function (err) { - if (err) { - cb && cb(err); - } else { - _that.updateStaticOnd(false, cb); - } - }); - } - }; - - this.setForwardingImmediate = function (status, number, cb) { - number = number || $rootScope.localUser.callForwardingNumber; - var data = { - activateForward: !!status, - forwardDN: Utils.cleanPhoneNumber(number), - forwardingType: 'forwardImmediate' - }; - _that.setForwarding(data, cb); - }; - - this.setForwardingToVM = function (status, cb) { - if (_that.getForwardingVMStatus() !== status) { - var data = { - activateForward: !!status, - forwardDN: Utils.cleanPhoneNumber(_osmoData.vm), - forwardingType: 'forwardImmediate' - }; - _that.setForwarding(data, cb); - } else { - cb && cb(); - } - }; - - this.getForwardingData = function () { - return _osmoData.forwardList; - }; - - this.getForwardingImmediate = function () { - var forwardImmediate = _osmoData.forwardList.find(function (element) { - return (element.forwardingType === 'forwardImmediate' && element.forwardDN !== _osmoData.vm); - }); - - return { - status: !!(forwardImmediate && forwardImmediate.forwardStatus), - forwardDN: forwardImmediate && forwardImmediate.forwardDN - }; - }; - - this.getForwardingVMStatus = function () { - return _osmoData.forwardList.some(function (element) { - return (element.forwardingType === 'forwardImmediate' && element.forwardDN === _osmoData.vm && element.forwardStatus); - }); - }; - - this.setVoicemail = function (status, duration, cb) { - if (getRegistrationData(cb)) { - duration = duration || _savedVMRingDuration; - if ($rootScope.localUser.voicemailPBXEnabled) { - LogSvc.debug('[CstaSvc]: Set voice mail duration to: ', duration); - if (status !== $rootScope.localUser.voicemailActive || _savedVMRingDuration !== duration) { - var msg = { - request: 'SetForwarding', - device: Utils.normalizeDn(_osmoData.onsFQN), - voicemailActive: status !== $rootScope.localUser.voicemailActive ? status : undefined, - voicemailRingDuration: status ? duration : undefined - }; - - sendCstaRequest(msg, function (err) { - if (err) { - cb && cb(err); - } else { - _savedVMRingDuration = duration; - cb && cb(null); - } - }); - } else { - cb && cb(null); - } - } else { - cb && cb('Cannot configure VM on PBX'); - } - } - }; - - this.setRoutingTimers = function (timers, cb) { - if (getRegistrationData(cb)) { - if ($rootScope.localUser.ringDurationEnabled) { - LogSvc.debug('[CstaSvc]: Set routing timers to: ', timers); - if (_savedMainRingDuration !== timers.mainRingDuration || _savedCellRingDuration !== timers.cellRingDuration || - _savedClientRingDuration !== timers.clientRingDuration || !$rootScope.localUser.routeToCell) { - var msg = { - request: 'SetForwarding', - device: Utils.normalizeDn(_osmoData.onsFQN), - mainRingDuration: timers.mainRingDuration || undefined, - clientRingDuration: timers.clientRingDuration || undefined, - cellRingDuration: timers.cellRingDuration || undefined, - routeToCell: true - }; - - sendCstaRequest(msg, function (err) { - if (err) { - cb && cb(err); - } else { - _savedMainRingDuration = timers.mainRingDuration || _savedMainRingDuration; - _savedClientRingDuration = timers.clientRingDuration || _savedClientRingDuration; - _savedCellRingDuration = timers.cellRingDuration || _savedCellRingDuration; - cb && cb(null); - } - }); - } - } else { - LogSvc.debug('[CstaSvc]: Cannot set routing timers'); - cb && cb('Cannot configure routing timers on PBX'); - } - } - }; - - return this; - } - - // Exports - circuit.CstaSvcImpl = CstaSvcImpl; - - return circuit; - -})(Circuit); - -// Define global variables for JSHint - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var CallState = circuit.Enums.CallState; - var CstaCallState = circuit.Enums.CstaCallState; - var Constants = circuit.Constants; - var Enums = circuit.Enums; - var Targets = circuit.Enums.Targets; - var Utils = circuit.Utils; - var UserToUserHandler = circuit.UserToUserHandlerSingleton; - - /** - * CallControlSvc Implementation. A Call control service to control and manage calls. - * - * @class - */ - function CallControlSvcImpl($rootScope, $q, LogSvc, PubSubSvc, CircuitCallControlSvc, ConversationSvc, CstaSvc, MeetingPointSvc, NotificationSvc, UserSvc, UserProfileSvc) { - - LogSvc.debug('New Service: CallControlSvc'); - - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Variables - /////////////////////////////////////////////////////////////////////////////////////// - var _atcRemoteCalls = []; - var _that = this; - var _userToUserHandler = UserToUserHandler.getInstance(); - var _defaultCallDevice; - /////////////////////////////////////////////////////////////////////////////////////// - // Internal Functions - /////////////////////////////////////////////////////////////////////////////////////// - function addAtcRemoteCallToList(call) { - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (call.sameAs(_atcRemoteCalls[idx])) { - LogSvc.debug('[CallControlSvc]: Updating call with callId = ' + call.callId); - _atcRemoteCalls[idx] = call; - return; - } - } - _atcRemoteCalls.push(call); - } - - function removeAtcRemoteCallFromList(call) { - // Remove it from the call list - var idx; - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (call.sameAs(_atcRemoteCalls[idx])) { - _atcRemoteCalls.splice(idx, 1); - break; - } - } - } - - function findAtcRemoteCall(callId) { - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].callId === callId) { - return _atcRemoteCalls[idx]; - } - } - } - - function findAtcRemoteCallOnTarget(target) { - var existingCall = null; - _atcRemoteCalls.some(function (call) { - if (call.atcCallInfo.getPosition() === target) { - existingCall = call; - return true; - } - }); - return existingCall; - } - - function findActiveAtcRemoteCall() { - var activeRemoteConference = null; - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].state === CallState.ActiveRemote) { - // Found an active call. Return it. - return _atcRemoteCalls[idx]; - } - if (_atcRemoteCalls[idx].checkCstaState(CstaCallState.Conference)) { - // Keep the remote conference as backup, but continue looking - activeRemoteConference = _atcRemoteCalls[idx]; - } - } - return activeRemoteConference; - } - - function findRingingAtcRemoteCall() { - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].state === CallState.Ringing) { - // Found a ringing call. Return it. - return _atcRemoteCalls[idx]; - } - } - } - - function publishCallState(call) { - LogSvc.debug('[CallControlSvc]: Publish /call/state event. callId = ' + call.callId + ', state = ' + call.state.name); - PubSubSvc.publish('/call/state', [call]); - } - - function terminateCall(call) { - call.terminate(); - } - - function findConversationByCallId(callId) { - var conversation = ConversationSvc.getConversationByRtcSession(callId); - if (conversation) { - return conversation; - } - - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].callId === callId) { - return ConversationSvc.getConversationFromCache(_atcRemoteCalls[idx].convId); - } - } - return null; - } - - function getAtcHandoverInProgressCall() { - for (var idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].isHandoverInProgress) { - return _atcRemoteCalls[idx]; - } - } - return null; - } - - function publishConversationUpdate(conversation) { - if (conversation) { - LogSvc.debug('[CallControlSvc]: Publish /conversation/update event. convId = ', conversation.convId); - PubSubSvc.publish('/conversation/update', [conversation]); - } - } - - function handleAtcCallInfo(call) { - if (!call) { - LogSvc.warn('[CallControlSvc]: Invalid call object in /atccall/info event'); - return; - } - if (!call.isAtcRemote) { - publishCallState(call); - return; - } - // If the call.isAtcRemote (which means that it's on the desk or another OND device) then we publish the - // /call/state event after mapping the CSTA state to a circuit call state. - var conversation = ConversationSvc.getConversationFromCache(call.convId); - if (!conversation || !conversation.isTelephonyConv) { - LogSvc.warn('[CallControlSvc]: Call in /atccall/info event is not for telephony conversation'); - return; - } - - if (!call.checkCstaState([CstaCallState.Idle, CstaCallState.Terminated])) { - addAtcRemoteCallToList(call); - if (!conversation.call || (conversation.call.isHandoverInProgress && !conversation.call.sameAs(call)) || (conversation.call.isAtcRemote && conversation.call.isHolding())) { - if (conversation.call && conversation.call.isHandoverInProgress) { - var oldCall = conversation.call; - oldCall.clearAtcHandoverInProgress(); - call.establishedTime = oldCall.establishedTime; - if (oldCall.isAtcRemote) { - removeAtcRemoteCallFromList(oldCall); - } else { - CircuitCallControlSvc.removeCallFromList(oldCall); - } - // We're terminating a local call, so set a terminate reason - oldCall.terminateReason = Enums.CallClientTerminatedReason.USER_ENDED; - terminateCall(oldCall); - LogSvc.debug('[CallControlSvc]: Publish /call/ended event.'); - PubSubSvc.publish('/call/ended', [oldCall, false]); - } - conversation.call = call; - } - } - - switch (call.getCstaState()) { - case CstaCallState.Initiated: - case CstaCallState.Offered: - case CstaCallState.ExtendedRinging: - call.setState(CallState.Started); - break; - case CstaCallState.Delivered: - call.setState(CallState.Delivered); - break; - case CstaCallState.Busy: - call.setState(CallState.Busy); - break; - case CstaCallState.Ringing: - if (!call.checkState(CallState.Ringing)) { - call.setState(CallState.Ringing); - // Show the notification for this remote ATC call - NotificationSvc && NotificationSvc.show({ - type: Circuit.Enums.NotificationType.INCOMING_VOICE_CALL, - user: call.peerUser, - extras: { - conversation: conversation, - call: call - } - }); - } - break; - case CstaCallState.Idle: - case CstaCallState.TransferFailed: - case CstaCallState.Failed: - call.setState(CallState.Idle); - break; - case CstaCallState.Terminated: - call.setState(CallState.Terminated); - break; - default: - call.setState(CallState.ActiveRemote); - break; - } - if (call.state !== CallState.Idle && call.state !== CallState.Terminated && call.state !== CallState.Started) { - publishConversationUpdate(conversation); - publishCallState(call); - // Set presence to busy if there is an active ATC remote call - if (call.state !== CallState.Ringing) { - UserProfileSvc.setPresenceWithLocation(Constants.PresenceState.BUSY); - } - } else if (call.state === CallState.Idle || call.state === CallState.Terminated) { - terminateCall(call); - removeAtcRemoteCallFromList(call); - - LogSvc.debug('[CallControlSvc]: Publish /call/ended event'); - PubSubSvc.publish('/call/ended', [call, false]); - - if (!call.isHandoverInProgress) { - if (conversation.call && conversation.call.sameAs(call)) { - conversation.call = findActiveAtcRemoteCall(); - // Change presence to available, if the current ATC remote call is terminated and there is no - // other call - if (!conversation.call && !CircuitCallControlSvc.getActiveCall()) { - UserProfileSvc.setPresenceWithLocation(Constants.PresenceState.AVAILABLE); - } - } - - } - publishConversationUpdate(conversation); - } - } - - function handleAtcCallReplace(call) { - var oldCall = getAtcHandoverInProgressCall(); - if (oldCall) { - var conversation = ConversationSvc.getConversationFromCache(call.convId); - call.establishedTime = oldCall.establishedTime; - terminateCall(oldCall); - removeAtcRemoteCallFromList(oldCall); - if (conversation && conversation.call) { - conversation.call = call; - publishConversationUpdate(conversation); - } - } - } - - function handleAtcHangingCall(callId) { - ConversationSvc.getTelephonyConversation(function (err, conversation) { - if (!err && conversation && conversation.call && conversation.call.atcCallInfo.getCstaCallId() === callId) { - conversation.call = null; - publishConversationUpdate(conversation); - } - }); - } - - function isLocalCall(callId) { - var localCall = CircuitCallControlSvc.getActiveCall(); - return !!localCall && callId === localCall.callId; - } - - function publishMobileBreakoutEvent(details) { - // Send the 'nativeDial' event up so the UI can initiate the call - LogSvc.debug('[CallControlSvc]: Publish /call/nativeDial event'); - PubSubSvc.publish('/call/nativeDial', [details]); - } - - function handleMobileBreakoutEvent(evt) { - if (!evt || !evt.phoneNumber) { - return; - } - LogSvc.debug('[CallControlSvc]: Received MobileBreakout.DIAL event'); - - var details = { - phoneNumber: evt.phoneNumber, - autoDial: evt.autoDial, - userName: evt.userName - }; - - var promises = []; - if (evt.conversationId) { - promises.push(ConversationSvc.getConversationPromise(evt.conversationId)); - } - if (evt.userId) { - promises.push(UserSvc.getUserPromise(evt.userId)); - } - if (promises.length) { - // Wait for all promises to be fulfilled before publishing the event - $q.all(promises).then(function (results) { - results && results.forEach(function (r) { - if (r.userId) { - // User object - details.user = r; - } else { - // By elimination, we're assuming it's a conversation object - details.conversation = r; - } - }); - - publishMobileBreakoutEvent(details); - }); - } else { - // We have no detailed user or conversation information - publishMobileBreakoutEvent(details); - } - } - - /////////////////////////////////////////////////////////////////////////////////////// - // PubSubSvc Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - PubSubSvc.subscribe('/atccall/info', function (call) { - LogSvc.debug('[CallControlSvc]: Received /atccall/info event'); - handleAtcCallInfo(call); - }); - - PubSubSvc.subscribe('/atccall/replace', function (call) { - LogSvc.debug('[CallControlSvc]: Received /atccall/replace event'); - handleAtcCallReplace(call); - }); - - PubSubSvc.subscribe('/call/ended', function (call) { - LogSvc.debug('[CallControlSvc]: Received /call/ended event'); - - if (call.isAtcRemote) { - call.clearAtcHandoverInProgress(); - removeAtcRemoteCallFromList(call); - } else { - var conversation = ConversationSvc.getConversationFromCache(call.convId); - if (conversation && conversation.isTelephonyConv && !conversation.call) { - conversation.call = findActiveAtcRemoteCall() || findRingingAtcRemoteCall(); - if (conversation.call) { - publishConversationUpdate(conversation); - } - } - if (!call.isRemote && !CircuitCallControlSvc.getActiveCall()) { - // Set the user state back to available if there are no remote ATC calls and - // the user is not away. - if (!findActiveAtcRemoteCall()) { - UserProfileSvc.setPresenceWithLocation(Constants.PresenceState.AVAILABLE); - } - // Cancel the auto snooze if applicable - UserProfileSvc.cancelAutoSnooze(); - } - } - }); - - PubSubSvc.subscribe('/atccall/hangingcall', function (callid) { - LogSvc.debug('[CallControlSvc]: Received /atccall/hangingcall event'); - handleAtcHangingCall(callid); - }); - - PubSubSvc.subscribe('/call/state', function (call) { - LogSvc.debug('[CallControlSvc]: Received /call/state event'); - if (call && call.state !== Enums.CallState.Terminated) { - if (!call.isRemote) { - UserProfileSvc.setPresenceWithLocation(Constants.PresenceState.BUSY); - } - } - }); - - PubSubSvc.subscribe('/screenshare/started', function () { - LogSvc.info('[CallControlSvc]: Received /screenshare/started event. Screenshare was started. Start Auto-Snooze.'); - UserProfileSvc.startAutoSnooze(); - }); - - PubSubSvc.subscribe('/screenshare/ended', function () { - LogSvc.info('[CallControlSvc]: Received /screenshare/ended event. Screenshare was ended. Resume from Auto-Snooze.'); - UserProfileSvc.cancelAutoSnooze(); - }); - - PubSubSvc.subscribe('/call/action', function (data) { - LogSvc.debug('[CallControlSvc]: Received /call/action event with action ', data.type); - switch (data.type) { - case Enums.NotificationActionType.ANSWER: - var mediaType = { audio: true, video: false, desktop: false }; - _that.answerCall(data.callId, mediaType); - break; - case Enums.NotificationActionType.DECLINE: - _that.endCall(data.callId); - return; - } - // Navigate to conversation - var conversation = _that.findConversationByCallId(data.callId); - if (conversation) { - LogSvc.debug('[CallControlSvc]: Publish /conversation/navigate event'); - PubSubSvc.publish('/conversation/navigate', [conversation]); - } - }); - - /////////////////////////////////////////////////////////////////////////////////////// - // User To User Event Handlers - /////////////////////////////////////////////////////////////////////////////////////// - if (Utils.isMobile()) { - _userToUserHandler.on('MOBILE_BREAKOUT.DIAL', function (evt) { - LogSvc.info('[CallControlSvc]: Received UserToUser MOBILE_BREAKOUT.DIAL event.'); - $rootScope.$apply(function () { - handleMobileBreakoutEvent(evt); - }); - }); - } - - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Interface - /////////////////////////////////////////////////////////////////////////////////////// - - /** - * Used by SDK to initialize active sessions. - * Returns a promise that is fullfilled when all sessions have been processed - * and the different calls arrays are populated. - */ - this.initActiveSessionsForSdk = CircuitCallControlSvc.initActiveSessionsForSdk; - - /** - * Set indication of whether incoming remote video should be disabled by default for - * new incoming and outgoing Circuit calls. - . * - * @param {boolean} true indicates that incoming video is disabled by default. - */ - this.setDisableRemoteVideoByDefault = CircuitCallControlSvc.setDisableRemoteVideoByDefault; - - /** - * Get indication of whether incoming remote video is currently disabled by default. - . * - * @returns {boolean} true indicates that incoming video is disabled by default. - */ - this.getDisableRemoteVideoByDefault = CircuitCallControlSvc.getDisableRemoteVideoByDefault; - - /** - * Set indication of whether Client Diagnostics are enabled/disabled - . * - * @param {boolean} true=disabled; false=enabled - */ - this.setClientDiagnosticsDisabled = CircuitCallControlSvc.setClientDiagnosticsDisabled; - - /** - * Get all of the calls. - * - * @returns {Array} An array of Call object. - */ - this.getCalls = CircuitCallControlSvc.getCalls; - - /** - * Get all of the phone calls. - * - * @returns {Array} An array of Call object - */ - this.getPhoneCalls = CircuitCallControlSvc.getPhoneCalls; - - /** - * Get the active local WebRTC call. - * - * @returns {LocalCall} The LocalCall object. - */ - this.getActiveCall = CircuitCallControlSvc.getActiveCall; - - /** - * Get the ringing incoming WebRTC call, - * null will be returned if there is no incoming call. - * - * @returns {LocalCall} The LocalCall object. - */ - this.getIncomingCall = CircuitCallControlSvc.getIncomingCall; - - /** - * Get the active remote WebRTC call. - * - * @returns {Array} The RemoteCall object array. - */ - this.getActiveRemoteCall = CircuitCallControlSvc.getActiveRemoteCall; - - /** - * Join an existing group call or conference - * - * @param {Object} call The existing group call or conference - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.joinGroupCall = CircuitCallControlSvc.joinGroupCall; - - /** - * Make a new outgoing call in an existing conversation. - * - * @param {String} convId The conversation ID for the conversation to start the call. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.makeCall = CircuitCallControlSvc.makeCall; - - /** - * Make a new CSTA call via the ATC. - * - * @param {String} target The target device where the call will be started. - * @param {String} destination The destination number for the call. - * @param {Function} cb A callback function replying with an error - */ - this.makeAtcCall = function (target, destination, cb) { - destination = Utils.cleanPhoneNumber(destination); - var existingCallOnTarget = findAtcRemoteCallOnTarget(target); - if (existingCallOnTarget) { - CstaSvc.consultationCall(existingCallOnTarget, destination, cb); - } else { - CstaSvc.makeCall(target, destination, cb); - } - }; - - /** - * Make a test call. - * - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} onConversationCreated A callback function that is immediately invoked with the rtcSessionId for the test call. - * @param {Function} onCallStarted A callback function when the call is started or in case there is an error. - */ - this.makeEchoTestCall = CircuitCallControlSvc.makeEchoTestCall; - - /** - * Make a new outgoing call to a given DN. - * - * @param {String} dialedDn The dialed number. - * @param {String} toName The display name of the called circuit user - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error, executed when the call is successfully placed. - */ - - this.dialNumber = CircuitCallControlSvc.dialNumber; - - /** - * Make a new outgoing call to a given DN. - * - * @param {Object} [destination] Object The destination to be called. - * @param {String} [destination.dialedDn] The dialed number. - * @param {String} [destination.toName] The display name of the called circuit user. - * @param {String} [destination.userId] Id of the dialed user (if available). Otherwise null or empty string. - * @param {Object} [destination.mediaType] The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error, executed when the call is successfully placed. - */ - - this.dialPhoneNumber = CircuitCallControlSvc.dialPhoneNumber; - - /** - * Make a new outgoing call to the selected device. - * - * Remarks: this function is intended to replace PhoneCallCtrl.dial function - * - * @param {Object} [destination] Object The destination to be called. - * @param {String} [destination.dialedDn] The dialed number. - * @param {String} [destination.toName] The display name of the called circuit user. - * @param {String} [destination.userId] Id of the dialed user (if available). Otherwise null or empty string. - * @param {Object} [destination.mediaType] The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error, executed when the call is successfully placed. - */ - this.dialUsingDefaultDevice = function (destination, cb) { - cb = cb || function () {}; - - var number = destination && destination.dialedDn; - if (!number || !Utils.PHONE_DIAL_PATTERN.test(number)) { - cb('Missing or wrong number'); - return; - } - - LogSvc.debug('[CallControlSvc]: Calling ' + number + ' with name: ' + destination.toName); - ConversationSvc.getTelephonyConversation(function (err, conversation) { - if (err || !conversation) { - LogSvc.debug('[CallControlSvc]: error on getTelephonyConversation ', err); - return; - } - - if (conversation.call && conversation.call.consultation) { - var target = conversation.call.getPosition() || _defaultCallDevice; - if (target && target !== Targets.WebRTC) { - LogSvc.info('[CallControlSvc]: Start consultation from ' + target.name + ' to ', number); - _that.makeAtcCall(target, number, cb); - conversation.call.consultation = false; - return; - } - } else if (_defaultCallDevice && _defaultCallDevice !== Targets.WebRTC) { - LogSvc.info('[CallControlSvc]: Make call from ' + _defaultCallDevice.name + ' to ', number); - _that.makeAtcCall(_defaultCallDevice, number, cb); - return; - } - - destination.mediaType = destination.mediaType || {audio: true, video: false, desktop: false}; - _that.dialPhoneNumber(destination, cb); - }); - }; - - /** - * Set the default device to be used for new outgoing call. - * - * @param {Object} [device] The default device to be used. - */ - this.setDefaultCallDevice = function (device) { - if (!device || !device.name) { - return; - } - // Make sure this is a valid Target device - Object.keys(Targets).some(function (key) { - if (device.name === Targets[key].name) { - _defaultCallDevice = Targets[key]; - LogSvc.debug('[CallControlSvc]: Set defaultCallDevice to ', _defaultCallDevice.name); - return true; - } - }); - }; - - /** - * start a conference in an existing group conversation. - * - * @param {Conversation} conversation The existing group conversation to start the conference. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.startConference = CircuitCallControlSvc.startConference; - - /** - * Answer the incoming call. Any existing active call will be terminated. - * - * @param {String} callId The call ID of incoming call to be answered. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Function} cb A callback function replying with an error - */ - this.answerCall = function (callId, mediaType, cb) { - var localPhoneCalls = CircuitCallControlSvc.getEstablishedLocalPhoneCalls(); - if (localPhoneCalls.length > 1) { - LogSvc.warn('[CallControlSvc]: answerCall - There are already 2 local phone calls'); - cb && cb('Can not answer the call'); - return; - } - var call = CircuitCallControlSvc.findCall(callId); - if (call) { - CircuitCallControlSvc.answerCall(callId, mediaType, cb); - return; - } - - call = findAtcRemoteCall(callId); - var activeCall = CircuitCallControlSvc.getActiveCall(); - var incomingCall = CircuitCallControlSvc.getIncomingCall(); - if (call) { - if (activeCall && !activeCall.isTelephonyCall) { - CircuitCallControlSvc.endActiveCall(function () { - CstaSvc.pullRemoteCall(callId, null, cb); - }); - } else if (incomingCall && !incomingCall.isTelephonyCall) { - CircuitCallControlSvc.endCallWithCauseCode(incomingCall.callId, Constants.InviteRejectCause.BUSY, function () { - CstaSvc.pullRemoteCall(callId, null, cb); - }); - } else { - CstaSvc.pullRemoteCall(callId, null, cb); - } - - } else { - LogSvc.warn('[CallControlSvc]: answerCall - There is no alerting call'); - cb && cb('No alerting call'); - } - }; - - /** - * Answer the incoming ATC call on any device. Any existing active call will be terminated - * - * @param {String} callId The call ID of incoming call to be answered. - * @param {Object} mediaType The media type object, e.g. {audio: true, video: false}. - * @param {Object} device The device object - * @param {Function} cb A callback function replying with an error - */ - this.answerCallOnDevice = function (callId, mediaType, device, cb) { - if (!device || device === Targets.WebRTC) { - return _that.answerCall(callId, mediaType, cb); - } - - var call = CircuitCallControlSvc.findCall(callId); - if (call) { - if (call.isTelephonyCall) { - CstaSvc.deflect(callId, device, null, cb); - } else { - LogSvc.warn('[CallControlSvc]: answerCallOnDevice - The active call is not a telephony call'); - } - return; - } - - call = findAtcRemoteCall(callId); - if (call) { - if (call.pickupNotification) { - CstaSvc.pickupCall(callId, device, cb); - } else if (call.getPosition() !== device) { - CstaSvc.deflect(callId, device, null, cb); - } else { - CstaSvc.answer(callId, cb); - } - } else { - LogSvc.warn('[CallControlSvc]: answerCallOnDevice - There is no alerting call'); - cb && cb('No alerting call'); - } - }; - - /** - * End a call. This could be a local or remote Circuit call, or an ATC call. - * - * @param {String} callId The call ID of the ongoing call to be terminated. - * @param {Function} cb A callback function replying with an error - */ - this.endCall = function (callId, cb) { - _that.endCallWithCauseCode(callId, null, cb); - }; - - /** - * End a call specifying the cause. This could be a local or remote Circuit call, or an ATC call. - * - * @param {String} callId The call ID of local call to be terminated. - * @param {Constants.InviteRejectCause} cause Cause code for terminating the call. - * @param {Function} cb A callback function replying with an error - */ - this.endCallWithCauseCode = function (callId, cause, cb) { - var call = CircuitCallControlSvc.findCall(callId); - if (call) { - if (call.isRemote) { - CircuitCallControlSvc.endRemoteCall(callId, cb); - } else { - if (cause) { - CircuitCallControlSvc.endCallWithCauseCode(callId, cause, cb); - } else { - CircuitCallControlSvc.endCall(callId, cb); - } - } - return; - } - - call = findAtcRemoteCall(callId); - - if (call && _atcRemoteCalls.length > 1) { - if (_that.reconnectCall(callId, cb)) { - return; - } - } - if (call) { - CstaSvc.endRemoteCall(callId, cb); - } else { - LogSvc.warn('[CallControlSvc]: endCall invoked without a valid call. callId = ', callId); - cb && cb('Call not found'); - } - }; - - /** - * End conference using local Circuit call. - */ - this.endConference = CircuitCallControlSvc.endConference; - - /** - * Start a session with screen-share. - * - * @param {Conversation} conversation The existing conversation to start the call. - * @param {Function} cb A callback function replying with an error - */ - this.startScreenShare = CircuitCallControlSvc.startScreenShare; - - /** - * Add screen-share to an existing RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {Function} cb A callback function replying with an error - */ - this.addScreenShare = CircuitCallControlSvc.addScreenShare; - - /** - * Select new desktop media to share in an existing screen-share RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {Function} cb A callback function replying with an error - */ - this.selectMediaToShare = CircuitCallControlSvc.selectMediaToShare; - - /** - * Remove screen-share from an existing RTC session. - * - * @param {String} callId The call ID of call to remove screen share from. - * @param {Function} cb A callback function replying with an error - */ - this.removeScreenShare = CircuitCallControlSvc.removeScreenShare; - - /** - * Enable audio in an existing RTC session. - * - * @param {String} callId The call ID of call to add audio to. - * @param {Function} cb A callback function replying with an error - */ - this.addAudio = CircuitCallControlSvc.addAudio; - - /** - * Disable audio in an existing RTC session. - * - * @param {String} callId The call ID of call to remove audio from. - * @param {Function} cb A callback function replying with an error - */ - this.removeAudio = CircuitCallControlSvc.removeAudio; - - /** - * Enable remote audio stream in an active call - * - * @param {String} callId The call ID to be muted - */ - this.enableRemoteAudio = CircuitCallControlSvc.enableRemoteAudio; - - /** - * Disable remote audio stream in an active call - * - * @param {String} callId The call ID to be muted - */ - this.disableRemoteAudio = CircuitCallControlSvc.disableRemoteAudio; - - /** - * Enable video in an existing RTC session. - * - * @param {String} callId The call ID of call to add video to. - * @param {Function} cb A callback function replying with an error - */ - this.addVideo = CircuitCallControlSvc.addVideo; - - /** - * Disable video in an existing RTC session. - * - * @param {String} callId The call ID of call to remove video from. - * @param {Function} cb A callback function replying with an error - */ - this.removeVideo = CircuitCallControlSvc.removeVideo; - - /** - * Toggle (allow or block) remote video streams in an existing RTC session. - * - * @param {String} callId The call ID of call to remove remote video from. - * @param {Function} cb A callback function replying with an error - */ - this.toggleRemoteVideo = CircuitCallControlSvc.toggleRemoteVideo; - - /** - * Toggle the video in an existing RTC session. - * - * @param {String} callId The call ID of call for which video will be toggled. - * @param {Function} cb A callback function replying with an error - */ - this.toggleVideo = CircuitCallControlSvc.toggleVideo; - - /** - * Enable/disable streaming HD on an existing RTC session. - * - * @param {String} callId The call ID of call for which video will be toggled. - * @param {Boolean} hdQuality Flag to enable/disable HD. - * @param {Function} cb A callback function replying with an error - */ - this.changeHDVideo = CircuitCallControlSvc.changeHDVideo; - - /** - * Starts a media renegotiation from the client without changing the current media types. - * - * @param {String} callId The call ID of the call for which to start the media renegotiation. - * @param {Function} cb A callback function replying with an error - */ - this.renegotiateMedia = CircuitCallControlSvc.renegotiateMedia; - - /** - * Set the audio and/or video media type for the current call. - * - * @param {String} callId The call ID of the call for which to start the media renegotiation. - * @param {Object} mediaType Object with audio and video boolean attributes. - * @param {Function} cb A callback function replying with an error - */ - this.setMediaType = CircuitCallControlSvc.setMediaType; - - /** - * Add a media stream to an existing RTC session. - * - * @param {String} callId The call ID of call to add screen share to. - * @param {MediaStream} stream The media stream to share. - * @param {Function} cb A callback function replying with an error - */ - this.addMediaStream = CircuitCallControlSvc.addMediaStream; - - /** - * Remove a media stream from an existing RTC session. - * - * @param {String} callId The call ID of call to remove stream from. - * @param {Function} cb A callback function replying with an error - */ - this.removeMediaStream = CircuitCallControlSvc.removeMediaStream; - - /** - * Move a remote call to the local client - * - * @param {String} callId The call ID of existing remote call established on another client. - * @param {BOOL} fallbackToAudio flag to check whether to pull with audio only. - * @param {Function} cb A callback function replying with an error - */ - this.pullRemoteCall = function (callId, fallbackToAudio, cb) { - var call = CircuitCallControlSvc.findCall(callId); - - if (call) { - CircuitCallControlSvc.pullRemoteCall(callId, fallbackToAudio, cb); - return; - } - - call = findAtcRemoteCall(callId); - if (call) { - var activeCall = CircuitCallControlSvc.getActiveCall(); - if (activeCall) { - if (activeCall.isTelephonyCall) { - CircuitCallControlSvc.holdCall(activeCall.callId, function () { - CstaSvc.pullRemoteCall(callId, null, cb); - }); - return; - } else { - CircuitCallControlSvc.endActiveCall(function () { - CstaSvc.pullRemoteCall(callId, null, cb); - }); - return; - } - } - CstaSvc.pullRemoteCall(callId, null, cb); - } else { - LogSvc.warn('[CallControlSvc]: pullRemoteCall invoked without a valid call'); - cb && cb('No active remote call'); - } - }; - - this.pushLocalCall = function (callId, target, cb) { - CstaSvc.pushLocalCall(callId, target, cb); - }; - - /** - * Gets a list with all the ATC calls (i.e. the CSTA calls). - */ - this.getAtcCalls = function () { - return _atcRemoteCalls; - }; - - /** - * Alternates (swaps) between two calls. - * - * @param {String} callId The call ID of the ATC call that is on hold. - */ - this.swapCall = function (callId, cb) { - var call = CircuitCallControlSvc.findCall(callId); - if (call && call.isTelephonyCall && !call.isRemote) { - CircuitCallControlSvc.swapCall(callId, cb); - return; - } - - if (_atcRemoteCalls.length <= 1) { - LogSvc.warn('[CallControlSvc]: swapCall possible only with 2 calls'); - return; - } - var idx; - var heldCall = null; - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].isHolding() && _atcRemoteCalls[idx].callId === callId) { - heldCall = _atcRemoteCalls[idx]; - break; - } - } - if (heldCall) { - var position = heldCall.atcCallInfo.getPosition(); - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].checkCstaState([CstaCallState.Active, CstaCallState.Conference]) && - _atcRemoteCalls[idx].atcCallInfo.getPosition() === position && - _atcRemoteCalls[idx].callId !== callId) { - - var activeCall = _atcRemoteCalls[idx]; - CstaSvc.alternate(heldCall, activeCall, cb); - return; - } - } - } - }; - - /** - * Conferences (merges) two ATC calls. - * - * @param {String} callId The call ID of the remote ATC call to be merged. - */ - this.mergeCall = function (callId, cb) { - var localCalls = CircuitCallControlSvc.getPhoneCalls(true); - if (_atcRemoteCalls.length <= 1 && localCalls.length <= 1) { - LogSvc.warn('[CallControlSvc]: mergeCall possible only with 2 calls'); - return; - } - var activeCall, heldCall; - - if (localCalls.length > 1) { - activeCall = CircuitCallControlSvc.findActivePhoneCall(true); - heldCall = CircuitCallControlSvc.findHeldPhoneCall(true); - if (activeCall && heldCall) { - if (heldCall.isAtcConferenceCall()) { - // if the held call is a PBX conference, we need to alternate to it before adding the consulted - // party to the conference - _that.swapCall(heldCall.callId, function (err) { - if (err) { - cb(err); - } else { - CstaSvc.conference(activeCall, heldCall, cb); - return; - } - }); - } else { - CstaSvc.conference(heldCall, activeCall, cb); - return; - } - } - } - var idx; - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].checkCstaState([CstaCallState.Active, CstaCallState.Conference]) && - _atcRemoteCalls[idx].callId === callId) { - - activeCall = _atcRemoteCalls[idx]; - break; - } - } - if (activeCall) { - var position = activeCall.atcCallInfo.getPosition(); - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].isHolding() && - _atcRemoteCalls[idx].atcCallInfo.getPosition() === position && - _atcRemoteCalls[idx].callId !== callId) { - - heldCall = _atcRemoteCalls[idx]; - CstaSvc.conference(heldCall, activeCall, cb); - return; - } - } - } - }; - - /** - * Transfers ATC calls. - * - * @param {String} callId The call ID of the remote ATC call to be merged. - */ - this.transferCall = function (callId, number, cb) { - var activeCall = CircuitCallControlSvc.findCall(callId); - var localCalls = CircuitCallControlSvc.getPhoneCalls(true); - var heldCall; - - number = Utils.cleanPhoneNumber(number); - - if (activeCall && localCalls.length === 1) { - if (activeCall.isTelephonyCall) { - CstaSvc.transfer(activeCall, number, cb); - } else { - LogSvc.warn('[CallControlSvc]: The active call is not a telephony call'); - cb && cb('The active call is not a telephony call'); - } - return; - } - - if (activeCall && localCalls.length === 2) { - if (activeCall.isTelephonyCall) { - heldCall = CircuitCallControlSvc.findHeldPhoneCall(true); - CstaSvc.consultationTransfer(activeCall, heldCall, cb); - } else { - LogSvc.warn('[CallControlSvc]: The active call is not a telephony call'); - cb && cb('The active call is not a telephony call'); - } - return; - } - - if (_atcRemoteCalls.length < 1) { - LogSvc.warn('[CallControlSvc]: transferCall possible only with 1 or 2 calls'); - cb && cb('Not enough calls for the transfer'); - return; - } - - if (_atcRemoteCalls.length === 1) { - if (!_atcRemoteCalls[0].isHolding()) { - activeCall = _atcRemoteCalls[0]; - CstaSvc.transfer(activeCall, number, cb); - return; - } - } - var idx; - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (!_atcRemoteCalls[idx].isHolding() && - _atcRemoteCalls[idx].callId === callId) { - - activeCall = _atcRemoteCalls[idx]; - break; - } - } - if (activeCall) { - var position = activeCall.atcCallInfo.getPosition(); - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].isHolding() && - _atcRemoteCalls[idx].atcCallInfo.getPosition() === position && - _atcRemoteCalls[idx].callId !== callId) { - - heldCall = _atcRemoteCalls[idx]; - CstaSvc.consultationTransfer(activeCall, heldCall, cb); - return; - } - } - } - }; - - /** - * Hold an ATC call. - * - * @param {String} callId The call ID of the ATC call to be held. - */ - this.holdCall = function (callId, cb) { - var call = CircuitCallControlSvc.findCall(callId); - - if (call) { - if (!call.isHolding()) { - CircuitCallControlSvc.holdCall(callId, cb); - return; - } else { - LogSvc.warn('[CallControlSvc]: Call is not in valid state'); - cb && cb('Call is not in valid state'); - return; - } - } - - call = findAtcRemoteCall(callId); - if (call) { - if (!call.isHolding()) { - CstaSvc.hold(call, cb); - return; - } else { - LogSvc.warn('[CallControlSvc]: Call is not in valid state'); - cb && cb('Call is not in valid state'); - return; - } - } else { - LogSvc.warn('[CallControlSvc]: holdCall invoked without a valid call'); - cb && cb('No active remote call'); - } - }; - - /** - * Retrieve an ATC held call. - * - * @param {String} callId The call ID of the ATC call to be retrieved. - */ - this.retrieveCall = function (callId, cb) { - var call = CircuitCallControlSvc.findCall(callId); - - if (call) { - if (call.isHolding()) { - CircuitCallControlSvc.retrieveCall(callId, cb); - return; - } else { - LogSvc.warn('[CallControlSvc]: Call is not in valid state'); - cb && cb('Call is not in valid state'); - return; - } - - } - - call = findAtcRemoteCall(callId); - if (call) { - if (call.isHolding()) { - CstaSvc.retrieve(call, cb); - return; - } else { - LogSvc.warn('[CallControlSvc]: Call is not in valid state'); - cb && cb('Call is not in valid state'); - return; - } - } else { - LogSvc.warn('[CallControlSvc]: retrieveCall invoked without a valid call'); - cb && cb('No active remote call'); - } - }; - - /** - * Reconnect an ATC call. - * - * @param {String} callId The call ID of the ATC active call. - */ - this.reconnectCall = function (callId, cb) { - if (_atcRemoteCalls.length <= 1) { - LogSvc.warn('[CallControlSvc]: reconnectCall possible only with 2 calls'); - return false; - } - var idx, activeCall = null; - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (!_atcRemoteCalls[idx].isHolding() && - _atcRemoteCalls[idx].callId === callId) { - - activeCall = _atcRemoteCalls[idx]; - break; - } - } - if (activeCall) { - var position = activeCall.atcCallInfo.getPosition(); - for (idx = 0; idx < _atcRemoteCalls.length; idx++) { - if (_atcRemoteCalls[idx].isHolding() && - _atcRemoteCalls[idx].atcCallInfo.getPosition() === position && - _atcRemoteCalls[idx].callId !== callId) { - - var heldCall = _atcRemoteCalls[idx]; - CstaSvc.reconnect(activeCall, heldCall, cb); - return true; - } - } - } - return false; - }; - - /** - * Ignore a pickup notification call - * - * @param {String} callId The call ID of the pickup notification call - */ - this.ignoreCall = function (callId, cb) { - var call = findAtcRemoteCall(callId); - if (call.pickupNotification) { - CstaSvc.ignoreCall(call, cb); - } else { - cb && cb('No pickup call'); - } - }; - - /** - * Hide a remote call. - * - * @param {String} callId The call ID of remote call to be hidden. - */ - this.hideRemoteCall = CircuitCallControlSvc.hideRemoteCall; - - /** - * Remove participant in a group call. - * - * @param {String} callId The call ID of a group call or conference. - * @param {Object} participant The call participant object. - * @param {Function} cb A callback function replying with an error - */ - this.dropParticipant = CircuitCallControlSvc.dropParticipant; - - /** - * Add participant to a call. - * - * @param {String} callId The call ID - * @param {Object} participant The participant object. - * @param {Function} cb A callback function replying with an error - */ - this.addParticipantToCall = CircuitCallControlSvc.addParticipantToCall; - - /** - * Add participant to an RTC session. - * - * @param {String} callId The call ID - * @param {Object} participant The participant object. - * @param {Function} cb A callback function replying with an error - */ - this.addParticipantToRtcSession = CircuitCallControlSvc.addParticipantToRtcSession; - - /** - * mute self locally in an active call - * - * @param {String} callId The call ID to be muted - * @param {Function} cb A callback function replying with an error - */ - this.mute = CircuitCallControlSvc.mute; - - /** - * unmute locally only (used for large conference) - * - * @param {Function} cb A callback function called on success - */ - this.unmuteLocally = CircuitCallControlSvc.unmuteLocally; - - /** - * unmute self in an active call, which may have been muted locally or remotely - * - * @param {String} callId The call ID to be unmuted - * @param {Function} cb A callback function replying with an error - */ - this.unmute = CircuitCallControlSvc.unmute; - - /** - * toggle between mute and unmute - * - * @param {String} callId The call ID to toggle mute - * @param {Function} cb A callback function replying with an error - */ - this.toggleMute = CircuitCallControlSvc.toggleMute; - - /** - * mute remote participant - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object to be muted - * @param {Function} cb A callback function replying with an error - */ - this.muteParticipant = function (callId, participant, cb) { - if (!callId || !participant) { - return; - } - cb = cb || function () {}; - - if (!isLocalCall(callId)) { - LogSvc.warn('[CallControlSvc]: muteParticipant - There is no local call'); - cb('Call invalid'); - return; - } - - if (participant.isMeetingPointInvitee) { - if (!MeetingPointSvc) { - LogSvc.error('[CallControlSvc]: MeetingPointSvc is not initialized'); - cb('res_MuteParticipantFailed'); - return; - } - MeetingPointSvc.muteMeetingPoint(participant.userId, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to mute Meeting Point locally'); - cb('res_MuteParticipantFailed'); - } else { - CircuitCallControlSvc.muteParticipant(callId, participant, cb); - } - }); - } else { - CircuitCallControlSvc.muteParticipant(callId, participant, cb); - } - }; - - /** - * check if there are unmuted participants - * - * @param {String} callId The call ID of active call - */ - this.hasUnmutedParticipants = CircuitCallControlSvc.hasUnmutedParticipants; - - /** - * mute RTC session - * - * @param {String} callId The call ID of active call - * @param {Function} cb A callback function replying with an error - */ - this.muteRtcSession = CircuitCallControlSvc.muteRtcSession; - - /** - * unmute self which has been muted remotely - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object to be unmuted - * @param {Function} cb A callback function replying with an error - */ - this.unmuteParticipant = function (callId, participant, cb) { - if (!callId || !participant) { - return; - } - cb = cb || function () {}; - - if (!isLocalCall(callId)) { - LogSvc.warn('[CallControlSvc]: unmuteParticipant - There is no local call'); - cb('Call invalid'); - return; - } - - if (participant.isMeetingPointInvitee) { - if (!MeetingPointSvc) { - LogSvc.error('[CallControlSvc]: MeetingPointSvc is not initialized'); - cb('res_UnmuteParticipantFailed'); - return; - } - MeetingPointSvc.unmuteMeetingPoint(participant.userId, function (err) { - if (err) { - LogSvc.warn('[CircuitCallControlSvc]: Failed to unmute Meeting Point locally'); - cb('res_UnmuteParticipantFailed'); - } else { - cb(); - } - }); - } else { - CircuitCallControlSvc.unmuteParticipant(callId, participant, cb); - } - }; - - /** - * toggle between participant mute and unmute - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object for which to toggle mute - * @param {Function} cb A callback function replying with an error - */ - this.toggleMuteParticipant = function (callId, participant, cb) { - if (!callId || !participant) { - return; - } - - if (participant.muted) { - return _that.unmuteParticipant(callId, participant, cb); - } - return _that.muteParticipant(callId, participant, cb); - }; - - - /** - * toggle video on MeetingPoing participant - * - * @param {String} callId The call ID of active call - * @param {Object} participant The participant object for which to toggle video - * @param {Function} cb A callback function replying with an error - */ - this.toggleVideoParticipant = function (callId, participant, cb) { - if (!callId || !participant || !participant.userId) { - return; - } - cb = cb || function () {}; - - if (!isLocalCall(callId)) { - LogSvc.warn('[CallControlSvc]: toggleVideoParticipant - There is no local call'); - cb('Call invalid'); - return; - } - - if (participant.isMeetingPointInvitee) { - if (!MeetingPointSvc) { - LogSvc.error('[CallControlSvc]: MeetingPointSvc is not initialized'); - cb('res_StartVideoParticipantFailed'); - return; - } - MeetingPointSvc.toggleVideoMeetingPoint(participant.userId, function (err) { - if (err) { - LogSvc.warn('[CallControlSvc]: Failed to toggle video MeetingPoint participant'); - cb('res_StartVideoParticipantFailed'); - } else { - cb(); - } - }); - } else { - LogSvc.warn('[CallControlSvc]: Cannot toggle video MeetingPoint participant: ', participant.userId); - cb('res_StartVideoParticipantFailed'); - } - }; - - this.stopRingingTone = CircuitCallControlSvc.stopRingingTone; - - /** - * End active call. - * - * @param {Function} cb A callback function replying with a status - */ - this.endActiveCall = CircuitCallControlSvc.endActiveCall; - - this.startRecording = CircuitCallControlSvc.startRecording; - - this.stopRecording = CircuitCallControlSvc.stopRecording; - - this.submitCallQualityRating = CircuitCallControlSvc.submitCallQualityRating; - - /** - * send new raise-hand question for large conference - * - * @param {String} questionText question text - * @param {boolean} isAnonymous question is anonymous - * @param {Function} cb A callback function replying with an error or success - */ - this.raiseQuestion = CircuitCallControlSvc.raiseQuestion; - - /** - * send stage invitation to guest for large conference - * - * @param {String} questionNumber question number (ID) - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStage = CircuitCallControlSvc.inviteToStage; - - /** - * invitation answer from invited guest (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {boolean} accepted invitation was accepted or discarded - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStageAnswer = CircuitCallControlSvc.inviteToStageAnswer; - - /** - * cancel on stage invitation by moderator (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {Function} cb A callback function replying with an error or success - */ - this.inviteToStageCancel = CircuitCallControlSvc.inviteToStageCancel; - - /** - * remove someone from stage (large conference) - * - * @param {String} userId user ID - * @param {Function} cb A callback function replying with an error or success - */ - this.removeFromStage = CircuitCallControlSvc.removeFromStage; - - /** - * retrieve raise-hand questions (large conference) - * guests get only their own questions, moderators all - * @param {String} startQuestionNumber first question number to retrieve (starting from 1) - * @param {String} numberOfQuestions count of returned questions, 0 for all - * @param {Function} cb A callback function replying with an error or success - */ - this.getQuestions = CircuitCallControlSvc.getQuestions; - - /** - * update a raise-hand question state (large conference) - * - * @param {String} questionNumber question number (ID) - * @param {String} newState question state, Constants.QuestionState - * @param {Function} cb A callback function replying with an error or success - */ - this.updateQuestionState = CircuitCallControlSvc.updateQuestionState; - - /** - * open curtain in large conference - * - * @param {Function} cb A callback function replying with an error or success - */ - this.openCurtain = CircuitCallControlSvc.openCurtain; - - /** - * close curtain in large conference - * - * @param {String} state Curtain state (FINAL or PAUSED) - * @param {String} message Optional message shown to guests - * @param {String} pauseDuration Optional duration of the break (in seconds), backend converts into fixed time (expectedCurtainOpenTime) - * @param {Function} cb A callback function replying with an error or success - */ - this.closeCurtain = CircuitCallControlSvc.closeCurtain; - - /** - * retrieve list of nodes with states - * currently only used for testcall - * @param {String} nodeType requested node type(currently only MEDIA_ACCESS) - * @param {String} tenantId name of the tenant or empty for all - * @param {Function} cb A callback function replying with an error or success - */ - this.getNodeState = CircuitCallControlSvc.getNodeState; - - this.canSendWebRTCDigits = CircuitCallControlSvc.canSendWebRTCDigits; - - /** - * Send DTMF digits - * - * @param {String} callId The call ID of active call - * @param {String} digits The digits to be sent - * @param {Function} cb A callback function replying with a status - */ - this.sendDigits = function (callId, digits, cb) { - var call = CircuitCallControlSvc.findCall(callId); - if (call) { - if (call.sessionCtrl.canSendDTMFDigits()) { - CircuitCallControlSvc.sendDigits(callId, digits, cb); - } else if (call.atcCallInfo) { - // For ATC calls we can use CSTA to generate DTMF digits (if we can't use WebRTC) - CstaSvc.generateDigits(call, digits, function (err) { - if (err) { - cb('res_SendDtmfFailed'); - } else { - cb(); - } - }); - } else { - cb('res_SendDtmfFailed'); - } - return; - } - - call = findAtcRemoteCall(callId); - if (call) { - CstaSvc.generateDigits(call, digits, cb); - } else { - LogSvc.warn('[CallControlSvc]: SendDigits invoked without a valid call'); - cb('Call not found'); - } - }; - - /** - * Locally disable the remote incoming video (this doesn't trigger a - * media renegotiation) - * @returns {undefined} - */ - this.disableIncomingVideo = CircuitCallControlSvc.disableIncomingVideo; - - /** - * Locally enable the remote incoming video (this doesn't trigger a - * media renegotiation) - * @returns {undefined} - */ - this.enableIncomingVideo = CircuitCallControlSvc.enableIncomingVideo; - - - /** - * Enable or disable participant pointer - * - * @param {boolean} true=enabled, false=disabled. - * @returns {undefined} - */ - this.toggleParticipantPointer = CircuitCallControlSvc.toggleParticipantPointer; - - /** - * Find active or incoming local call - * @returns {LocalCall} The LocalCall object. - */ - this.findCall = CircuitCallControlSvc.findCall; - - /** - * Find the conversation for a given callId - * - * @param callId - * @returns {Conversation} The conversation object - */ - this.findConversationByCallId = findConversationByCallId; - - /** - * Sends a UserToUser message to the sharing user which then - * shows that pointer in the shared screen. - * - * @param userId ID of user sharing the screen - * @param x x-coordinates - * @param y y-coordinates - */ - this.sendParticipantPointer = CircuitCallControlSvc.sendParticipantPointer; - - /** - * Sends a UserToUser message to the sharing user which then - * shows the drawing in the shared screen. - * - * @param userId ID of user sharing the screen - * @param points Array of x/y coordinates - * @param options Object with color, tool and size property - */ - this.sendParticipantDrawing = CircuitCallControlSvc.sendParticipantDrawing; - - /** - * Returns the ATC devices that can initiate a call - * @returns {Object[]} The device list - */ - this.getCallDevices = CstaSvc.getCallDevices; - - /** - * Returns the ATC devices that the call can be pushed - * @returns {Object[]} The push device list - */ - this.getPushDevices = CstaSvc.getPushDevices; - - /** - * Returns the ATC devices the call can be answered at. - * @param callId Call ID - * @returns {Object[]} The answer device list - */ - this.getAnswerDevices = function (callId) { - if (callId && typeof callId === 'object') { - // Backwards compatibility - return CstaSvc.getAnswerDevices(callId); - } - var call = CircuitCallControlSvc.findCall(callId) || findAtcRemoteCall(callId); - return CstaSvc.getAnswerDevices(call); - }; - - /** - * Use this method to force the active call object to query the currently used - * audio/video devices. - * - * @returns {undefined} - */ - this.updateActiveCallMediaDevices = CircuitCallControlSvc.updateActiveCallMediaDevices; - - /** - * Used in development mode to simulate a conference with several participants - */ - this.addSimulatedParticipants = CircuitCallControlSvc.addSimulatedParticipants; - - /////////////////////////////////////////////////////////////////////////////////////// - // Public Factory Interface for Angular - /////////////////////////////////////////////////////////////////////////////////////// - return this; - } - - // Exports - circuit.CallControlSvcImpl = CallControlSvcImpl; - - return circuit; - -})(Circuit); - -/*global Promise, window*/ - -var Circuit = (function (circuit) { - 'use strict'; - - // Imports - var Constants = circuit.Constants; - var logger = circuit.logger; - var PhoneNumberFormatter = circuit.PhoneNumberFormatter; - var Utils = circuit.Utils; - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Helper functions used by common services - /////////////////////////////////////////////////////////////////////////////////////////////// - function extendConversation(rootScope, conv) { - if (!rootScope || !rootScope.localUser) { - logger.warn('[sdk-services]: Cannot extend Conversation object without localUser'); - return null; - } - if (!conv || conv.isExtended) { - return conv; - } - var extConv = Object.create(conv); - extConv.isExtended = true; - - extConv.moderators = extConv.moderators || []; - - var otherParticipants = conv.participants.map(function (p) { - // If the conversation has been 'publicized' we need to revert participants back to an object - return (typeof p === 'string') ? {userId: p} : p; - }).filter(function (p) { - if (p.userId === rootScope.localUser.userId) { - extConv.hasJoined = true; - } - return p.userId !== rootScope.localUser.userId; - }); - extConv.peerUsers = otherParticipants; - if (conv.type === Constants.ConversationType.DIRECT) { - extConv.peerUser = otherParticipants[0]; - } - - // Required in RtcParticipant - extConv.userIsModerator = function (user) { - if (!user) { - return false; - } - return extConv.moderators.some(function (elem) { - return elem.userId === user.userId; - }); - }; - - return extConv; - } - - function extendUserProfile(user) { - if (!user || user.isExtended) { - return user; - } - var extUser = Object.create(user); - extUser.isExtended = true; - return extUser; - } - - // Consider importing userProfile.js into the SDK so this code does not need to be copied. - function syncTelephonyConfig(user) { - var account = user && user.accounts && user.accounts[0]; - var telConfig = account && account.telephonyConfiguration; - if (telConfig) { - telConfig.phoneNumber = PhoneNumberFormatter.format(telConfig.phoneNumber) || ''; - telConfig.callerId = PhoneNumberFormatter.format(telConfig.callerId) || ''; - telConfig.reroutingPhoneNumber = PhoneNumberFormatter.format(telConfig.reroutingPhoneNumber) || ''; - - user.reroutingPhoneNumber = telConfig.reroutingPhoneNumber; - user.ondSipAuthenticationHash = telConfig.ondSipAuthenticationHash; - user.onsSipAuthenticationHash = telConfig.onsSipAuthenticationHash; - user.associatedTelephonyUserID = telConfig.associatedTelephonyUserID; - user.associatedTelephonyUserType = telConfig.associatedTelephonyUserType; - - user.previousAlternativeNumbers = (telConfig.previousAlternativeDevice || []).map(Utils.cleanPhoneNumber); - if (user.reroutingPhoneNumber) { - var cleanReroutingNumber = Utils.cleanPhoneNumber(user.reroutingPhoneNumber); - if (!user.previousAlternativeNumbers.includes(cleanReroutingNumber)) { - user.previousAlternativeNumbers.unshift(cleanReroutingNumber); - } - } - - if (user.associatedTelephonyUserID && user.associatedTelephonyUserType === Constants.GtcTrunkType.ATC_TRUNK_TYPE) { - // Set associatedGTCUserId field for backwards compatibility until mobile clients remove all references to it - user.associatedGTCUserId = user.associatedTelephonyUserID; - user.isATC = true; - } else { - user.isATC = false; - } - } else { - delete user.associatedGTCUserId; - delete user.reroutingPhoneNumber; - delete user.ondSipAuthenticationHash; - delete user.onsSipAuthenticationHash; - delete user.associatedTelephonyUserID; - delete user.associatedTelephonyUserType; - delete user.isATC; - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Angular objects - /////////////////////////////////////////////////////////////////////////////////////////////// - var $timeout = function (fn, delay /*, invokeApply*/) { - return window.setTimeout(fn, delay); - }; - $timeout.cancel = function (id) { - id && window.clearTimeout(id); - }; - - var $interval = function (fn, delay, count /*, invokeApply*/) { - if (count === undefined) { - return window.setInterval(fn, delay); - } - if (count <= 0) { - return null; - } - var currCount = 0; - var id = null; - var callback = function () { - fn(); - currCount++; - if (currCount >= count) { - window.clearInterval(id); - } - }; - id = window.setInterval(callback, delay); - return id; - }; - $interval.cancel = function (id) { - id && window.clearInterval(id); - }; - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Override ClientApiHandlerSingleton.getInstance and UserToUserHandlerSingleton.getInstance - /////////////////////////////////////////////////////////////////////////////////////////////// - var _tempClientApiHandlerInstance = null; - circuit.ClientApiHandlerSingleton.getInstance = function () { - return _tempClientApiHandlerInstance; - }; - - var _tempUserToUserHandlerInstance = null; - circuit.UserToUserHandlerSingleton.getInstance = function () { - return _tempUserToUserHandlerInstance; - }; - - - /////////////////////////////////////////////////////////////////////////////////////////////// - // SDK services - /////////////////////////////////////////////////////////////////////////////////////////////// - function SdkServices(clientApiHandler, userToUserHandler) { - // Set the temporary ClientApiHandler and UserToUserHandler instances which will be - // returned by ClientApiHandlerSingleton and UserToUserHandlerSingleton. - _tempClientApiHandlerInstance = clientApiHandler; - _tempUserToUserHandlerInstance = userToUserHandler; - - this.rootScope = { - localUser: null, - clientVersion: '', - browser: Utils.getBrowserInfo(), - circuitLabs: {}, - - // Angular interfaces - $$phase: true, - $apply: function (callback) { - callback && callback(); - }, - $digest: function () {}, - i18n: { - localize: function (res) { return res; }, - map: { - res_Unknown: 'Unknown' - } - }, - localize: function (res) { return res; } - }; - - // Set the temporary Circuit and UserProfile objects before initializing the services - circuit.Conversation = { - extend: extendConversation.bind(null, this.rootScope) - }; - - circuit.UserProfile = { - extend: extendUserProfile.bind(null), - syncTelephonyConfig: syncTelephonyConfig.bind(null) - }; - - this.PubSubSvc = new circuit.PubSubSvcImpl(logger); - - this.ExtensionSvc = new circuit.ExtensionSvcImpl( - this.rootScope, - $timeout, - window, - Promise, // $q - logger, - this.PubSubSvc, - null); // PopupSvc - - this.SdkHelperSvc = new circuit.SdkHelperSvcImpl( - this.rootScope, - Promise, // $q - logger, - this.PubSubSvc); - - this.AtcRegistrationSvc = new circuit.AtcRegistrationSvcImpl( - this.rootScope, - $timeout, - logger, - this.PubSubSvc, - this.SdkHelperSvc, // RegistrationSvc - this.SdkHelperSvc); // ConversationSvc - - this.DeviceDiagnosticSvc = new circuit.DeviceDiagnosticSvcImpl( - this.rootScope, - $timeout, - logger, - this.PubSubSvc, - null); // LocalStoreSvc - - this.CircuitCallControlSvc = new circuit.CircuitCallControlSvcImpl( - this.rootScope, - $timeout, - $interval, - window, // $window - Promise, // $q - logger, - this.PubSubSvc, - this.SdkHelperSvc, // UserSvc - this.SdkHelperSvc, // ConversationSvc - null, // NotificationSvc - this.SdkHelperSvc, // InstrumentationSvc - this.DeviceDiagnosticSvc); - - this.CstaSvc = new circuit.CstaSvcImpl( - this.rootScope, - $timeout, - logger, - this.PubSubSvc, - this.CircuitCallControlSvc, - this.AtcRegistrationSvc, - this.SdkHelperSvc, // UserSvc - this.SdkHelperSvc); // ConversationSvc - - this.CallControlSvc = new circuit.CallControlSvcImpl( - this.rootScope, - Promise, // $q - logger, - this.PubSubSvc, - this.CircuitCallControlSvc, - this.SdkHelperSvc, // ConversationSvc - this.CstaSvc, - null, // MeetingPointSvc - null, // NotificationSvc - this.SdkHelperSvc, // UserSvc - this.SdkHelperSvc); // UserProfileSvc - - // Clear the temporary Circuit and UserProfile objects before initializing the services - circuit.Conversation = null; - circuit.UserProfile = null; - - // Clear the temporary ClientApiHandler and UserToUserHandler instance - _tempClientApiHandlerInstance = null; - _tempUserToUserHandlerInstance = null; - } - - // Export - circuit.SdkServices = SdkServices; - - return circuit; - -})(Circuit); - -var Circuit = (function (circuit) { - 'use strict'; - - /** - * Injectors that can be implemented by application to extend the Conversation, Item and User objects. - * Injectors may be synchronous in which case they can return the extended object, or asynchronous in - * which case a Promise needs to be returned. - * - * See examples in method documentation. - * @class Injectors - * @static - */ - var Injectors = {}; - - /** - * Define this injector to extend the conversation objects returned in any response or included in any event - * sent by the API. This method can return the extended conversation object synchronously, or a Promise - * if this function needs to perform any asynchronous task. - * @method conversationInjector - * @param conversation {Conversation} A conversation object - * @return {Conversation|Promise} The extended conversation, or a Promise with the extended conversation as parameter - * @example - * Circuit.Injectors.conversationInjector = function (conversation) { - * return new Promise(function (resolve, reject) { - * // Get user objects for participant of this conversation - * client.getUsersById(userIds).then(function (users) { - * conversation.users = users; - * resolve(conversation); - * }, function (err) { - * reject(err); - * }); - * }); - * } - */ - - /** - * Define this injector to extend the item objects returned in any response or included in any event - * sent by the API. This method can return the extended item object synchronously, or a Promise - * if this function needs to perform any asynchronous task. - * @method itemInjector - * @param item {Item} An item object - * @return {Item|Promise} The extended item, or a Promise with the extended item as parameter - * @example - * Circuit.Injectors.itemInjector = function (item) { - * // Define a teaser attribute for text items - * if (item.type === 'TEXT') { - * // Create item.teaser attribute with replacing br and hr tags with a space - * item.teaser = item.text.content.replace(/<(br[\/]?|\/li|hr[\/]?)>/gi, ' '); - * } else { - * item.teaser = item.type; - * } - * }; - */ - - /** - * Define this injector to extend the user objects returned in any response or included in any event - * sent by the API. This method can return the extended user object synchronously, or a Promise - * if this function needs to perform any asynchronous task. - * @method userInjector - * @param user {User} An user object - * @return {User|Promise} The extended user, or a Promise with the extended user as parameter - * @example - * Circuit.Injectors.userInjector = function (user) { - * // Override the avatar if user does not have one. - * if (!user.smallImageUri) { - * user.avatar = 'http://www.aceshowbiz.com/images/photo/chuck_norris.jpg'; - * user.avatarLarge = 'http://www.aceshowbiz.com/images/photo/chuck_norris.jpg'; - * } - * }; - */ - - /** - * Define this injector to extend the participant objects returned by the GetConversationParticipants API. - * This method can return the extended participant object synchronously, or a Promise - * if this function needs to perform any asynchronous task. - * @method participantInjector - * @param user {ConversationParticipant} A conversation participant object - * @return {ConversationParticipant|Promise} The extended participant, or a Promise with the extended participant as parameter - * @example - * Circuit.Injectors.participantInjector = function (participant) { - * // Override the avatar if participant does not have one. - * if (!participant.smallImageUri) { - * participant.avatar = 'http://www.aceshowbiz.com/images/photo/chuck_norris.jpg'; - * participant.avatarLarge = 'http://www.aceshowbiz.com/images/photo/chuck_norris.jpg'; - * } - * }; - */ - // Export - circuit.Injectors = Injectors; - - return circuit; -})(Circuit || {}); - -// Define global variables for JSHint -/*global Promise, window, XMLHttpRequest*/ - -/** - * Circuit SDK module. - * - * Main class is {{#crossLink "Client"}}{{/crossLink}} which exposes the messaging and real-time APIs for a particular user. - * - * Here is an example that creates a client instance, performs a login, registers for an itemAdd event and sends a new text message. - * - * // Create Circuit client instance - * var client = new Circuit.Client({ - * client_id: '<your client_id>', - * domain: '<circuit system, defaults to circuitsandbox.net>' - * }); - * - * // Authenticate with OAuth2. User will see the Circuit OAuth2 popup. - * client.logon() - * .then(user => { - * // Listen for itemAdded event - * client.addEventListener('itemAdded', evt => console.log('Item received:', evt.item)); - * // Send text item on a specific conversation - * client.addTextItem('3154f545-1a6a-493a-8448-3c5cfa5a6b11', 'Hello World') - * .then(item => console.log('Item:', item)) - * .catch(console.error); - * }); - * - * View the examples on [circuit.github.io](https://circuit.github.io). - * - * New to [Circuit](https://www.circuit.com/)? [Register](https://developers.circuit.com/registration) for a free [developer](https://developers.circuit.com/) account. - * - * @module Circuit - * @main - */ -var Circuit = (function (circuit) { - 'use strict'; - - var ConnectionState = circuit.Enums.ConnectionState; - var ConversationType = circuit.Enums.ConversationType; - var Constants = circuit.Constants; - var logger = circuit.logger; - var PrivateData = circuit.PrivateData; - var Targets = circuit.Enums.Targets; - var Utils = circuit.Utils; - - function closeWindow() { - if (window.frameElement && window.parent) { - // Inside an iframe. Remove it from parent. - window.parent.document.body.removeChild(window.frameElement); - } else { - // Close current window - try { - window.close(); - } catch (e) {} - } - } - - // Setup response handler. Used by OAuth2 popup to call callback function on opener. - function responseHandler(window, parent) { - if (!window.location.hash && !window.location.search) { - return; - } - - // Extract URI fragments - var p = Object.assign({}, Utils.parseQS(window.location.hash), Utils.parseQS(window.location.search)); - - if (p.state) { - try { - Object.assign(p, JSON.parse(p.state)); - } catch (e) { - logger.error('Could not decode state parameter'); - } - } - - // Change the popup content to be a redirecting text, but - // only if this is the default redirect_uri - if (p.display && p.display === 'popup' && p.redirect_uri === window.location.origin + window.location.pathname) { - // Hide body since the default redirect_uri is used - window.document.addEventListener('DOMContentLoaded', function () { - // Todo: localize - window.document.body.innerHTML = 'Redirecting, please wait...'; - }); - } - - // If we have a callback, then execute it on parent window - if (p.callback) { - var cb = p.callback; - if (cb in parent) { - try { - delete p.callback; - parent[cb](p); - } catch (e) {} - } - } - - if (p.display && p.display === 'popup') { - closeWindow(); - } - } - responseHandler(window, window.opener || window.parent); - - var browserInfo = Utils.getBrowserInfo(); - - // Extension handler needs to be created before the browser tries to register the extension, - // otherwise the event listener is not yet created. We will need to see if that should be - // changed in the future. - circuit.ExtensionConnHandlerSingleton.getInstance(); - - /** - * This is the main class of the Circuit module and is the entry point for all APIs. - * - * An application creates a Client instance on which the logon is performend. After a successful - * logon, the other APIs on the client instance can be called. - * Event listeners are also exposed on the Client instance. - * - * Most applications will only require a single Client instance. node.js applications might need to impersonate - * multiple users, so the app can simply create multiple client instances. Events are raised to the correct - * instance. - * @class Client - * @constructor - * @param {Object} config Object literal containing configuration parameters - * @param {String} [config.client_id] The OAuth2 client ID you obtain from the Developer Portal. Identifies the client that is making the request. - * @param {String} [config.client_secret] The OAuth2 client secret you obtain from the Developer Portal. Applicable for `client credentials` grant type used for node.js apps. - * @param {String} [config.scope] Comma-delimited set of permissions that the application requests. Values: ALL,READ_USER_PROFILE,WRITE_USER_PROFILE,READ_CONVERSATIONS,WRITE_CONVERSATIONS,READ_USER,CALLS - * @param {Boolean} [config.autoRenewToken=false] Applicable to Client Credentials Grant only. If set to `true`, the OAuth2 access token is automatically renewed prior to expiry. - * @param {String} [config.domain='circuitsandbox.net'] The domain of the Circuit server to use. Defaults to circuitsandbox.net. - * @param {Boolean} [config.enableTelephony=false] Set to true to register for support incoming telephony calls. Requires 'calls' scope and a configured telephony connector for the tenant. - * @param {String} [config.emoticonMode='standard'] `standard` or `circuit`. In standard mode the Circuit encoding is converted to standard unicode. - * @param {Boolean} [config.removeMentionHtml=false] If `true` span element for mention is removed and only the mentioned user's name is shown. - * @param {String} [config.redirect_uri] Optional. URL to redirect to upon successfull OAuth. Prevents unnesecarry rendering or parent page URL in OAuth popup. - */ - circuit.Client = (function (config) { - - /** - * Register for Circuit events such as new a new message, incoming call, connection - * state change, etc. - * @method addEventListener - * @param {String} type Event type such as `itemAdded` - * @returns {Function} callback See event definition on parameter(s) of callback function - * @example - * // Register for new conversation items added to the feed (e.g. a new message) - * client.addEventListener('itemAdded', evt => console.log('Item added:', evt.item); - * - * // Register for connection state changes. E.g. connection going down and re-connecting - * client.addEventListener('connectionStateChanged', function (evt) { - * console.log('Received connectionStateChanged event. state: ', evt.state) - * }); - */ - circuit.BaseEventTarget.call(this, circuit.logger); - - // Internal variables - var _self = this; - var _connectionState = circuit.Enums.ConnectionState.Disconnected; - - var _config = Utils.shallowCopy(config) || {}; - _config.domain = _config.domain || 'circuitsandbox.net'; - _config.emoticonMode = _config.emoticonMode || 'standard'; - setOauthConfig(config); - - var _isNode = (window.navigator.platform === 'node'); - var _isDotNet = (window.navigator.platform === 'dotnet'); - var _isWin32 = (window.navigator.platform === 'Win32'); // UWP JS apps, e.g. WinJS - var _isMobile = (window.navigator.platform === 'iOS' || window.navigator.platform === 'Android'); - - // OAuth2 access token - var _accessToken; - var _expiresAt; - var _oauthRenewTokenTimer; - var _logonCheckTimer; - var _telephonyConvId; - var _supportConvId; - - // Queue to ensure the order of the emited events is the same as the - // order of the received events. - var _eventQueue = []; - var _processingQueue = false; - var _eventQueueIntervalId = null; - - // Create ClientAPI handler - var _clientApiHandler = new circuit.ClientApiHandler(_config); - var _userToUserHandler = new circuit.UserToUserHandler(_clientApiHandler); - var _rtcSessionController = circuit.RtcSessionController; - - // Expose handlers and controllers as private attributes on the client instance - _self._clientApiHandler = _clientApiHandler; - _self._userToUserHandler = _userToUserHandler; - _self._rtcSessionController = _rtcSessionController; - - // Instantiate the services associated with this client - var _services = new circuit.SdkServices(_clientApiHandler, _userToUserHandler); - - - /*********************************************************************************************/ - // Helper functions - - // Called after clientAPI call errors out. Fetches last clientAPI error - // and creates Circuit.Error. - function apiError(err, reject, treatNoResultAsEmptyList, postFn) { - if (err === Constants.ReturnCode.NO_RESULT && treatNoResultAsEmptyList) { - return false; - } - if (err) { - var lastError = _clientApiHandler.getLastError(); - postFn && postFn(lastError); - lastError && delete lastError.errObj; - reject(lastError); - return true; - } - return false; - } - - // Called after service call errors out. Converts error to a Circuit.Error. - function svcError(err, reject) { - if (err) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - return true; - } - return false; - } - - function fullDomain() { - return 'https://' + _self.domain + '/'; - } - - function open(url, width, height) { - var options = {}; - var stringOptions; - - options.width = width; - options.height = height; - options.left = window.screenX + (window.outerWidth - width) / 2; - options.top = window.screenY + (window.outerHeight - height) / 2; - options.scrollbars = 'yes'; - - stringOptions = Object.keys(options).map(function (key) { - return key + '=' + options[key]; - }).join(', '); - - return window.open(url, options.name, stringOptions); - } - - function hasScope(scope) { - return (_config.scope.split(',').indexOf(scope) >= 0) || - (_config.scope.split(',').indexOf('ALL') >= 0); - } - - function setOauthConfig(oauthConfig) { - oauthConfig = oauthConfig || {}; - _config.client_id = oauthConfig.client_id; - _config.client_secret = oauthConfig.client_secret; - - // OAuth2 redirect_uri defaults to current pages - _config.redirect_uri = oauthConfig.redirect_uri || window.location.origin + window.location.pathname; - - // Trim spaces in scope. Default is 'ALL' - _config.scope = (oauthConfig.scope ? oauthConfig.scope.replace(/\s+/g, '') : '') || 'ALL'; - - // GetLoggedOn API is required which uses READ_USER_PROFILE permission, so always ask for it - if (!hasScope(Constants.OAuthScope.READ_USER_PROFILE)) { - _config.scope = Constants.OAuthScope.READ_USER_PROFILE + ',' + oauthConfig.scope; - } - - // If ALL scope is asked for, then remove other scopes - if (hasScope(Constants.OAuthScope.ALL)) { - _config.scope = Constants.OAuthScope.ALL; - } - } - - /*********************************************************************************************/ - // Internal connectivity functions - - function xhr(obj) { - return new Promise(function (resolve, reject) { - var xhr = _isNode ? new XMLHttpRequest(_config) : new XMLHttpRequest(); - xhr.open(obj.type || obj.dataType || 'GET', obj.url, true); - - xhr.withCredentials = obj.xhrFields && obj.xhrFields.withCredentials; - - // If logged on using OAuth2, then include the token if no Authorization is provided - if (_accessToken && (!obj.headers || !obj.headers.Authorization)) { - xhr.setRequestHeader('Authorization', 'Bearer ' + _accessToken); - } - - // Set headers - if (obj.headers) { - for (var key in obj.headers) { - if (obj.headers.hasOwnProperty(key)) { - xhr.setRequestHeader(key, obj.headers[key]); - } - } - } - - xhr.onload = function () { - // This is called even on 404 etc, so check the status - if (xhr.status === 200) { - obj.success && obj.success(xhr.responseText); - resolve(xhr.responseText); - } else { - var err = new Circuit.Error(xhr.status, xhr.responseText || xhr.statusText); - obj.error && obj.error(err); - reject(err); - } - }; - xhr.onerror = function () { - var err = new Circuit.Error(Constants.ErrorCode.AUTHORIZATION_FAILED, 'Network error or incorrect credentials'); - obj.error && obj.error(err); - reject(err); - }; - - var formData; - if (obj.data) { - formData = Utils.toQS(obj.data); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - } - xhr.send(formData); - }); - } - - function sdkLogin(options) { - return new Promise(function (resolve, reject) { - var url = '/sdklogin'; - xhr({ - type: 'POST', - url: 'https://' + _self.domain + url, - data: { - token: options.token || undefined, - accessToken: options.accessToken || undefined - }, - xhrFields: { withCredentials: true }, - crossDomain: true - }) - .then(function () { - // Set the access token if that was just used to login - options.accessToken && (_accessToken = options.accessToken); - resolve(); - }) - .catch(reject); - }); - } - - function validateSession() { - return xhr({ - url: 'https://' + _self.domain + '/validateSession', - xhrFields: { withCredentials: true }, - crossDomain: true - }); - } - - function getDeviceSubtype() { - switch (window.navigator.platform) { - case 'node': - return 'NODE_SDK'; - case 'dotnet': - return 'DOT_NET_SDK'; - case 'Win32': - return 'WIN_JS_SDK'; - case 'iOS': - return 'IOS_SDK'; - case 'Android': - return 'ANDROID_SDK'; - default: - return 'JS_SDK'; - } - } - - function setupWebSocket() { - return new Promise(function (resolve, reject) { - // Add some protection in case the sdk user tries to connect again - if (_eventQueueIntervalId) { - window.clearInterval(_eventQueueIntervalId); - _eventQueueIntervalId = null; - _clientApiHandler.disconnect(); - } - - // Use the accessToken as query parameter in WSS /api request - _clientApiHandler.setToken(_accessToken); - _clientApiHandler.setClientInfo({ - deviceType: Constants.DeviceType.SDK, - deviceSubtype: getDeviceSubtype(), - clientVersion: circuit.version - }); - - _clientApiHandler.connect(function () { - logger.info('[SDK]: Successfully connected to access server.'); - // Start the event queue timer - _eventQueueIntervalId = window.setInterval(processEventQueue, 50); - resolve(); - }, function (err) { - // Unfortunately the err is only an error string without any error code - logger.error('[SDK]: Failed to connect: ', err); - _clientApiHandler.disconnect(); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - }, _self.domain); - }); - } - - /*********************************************************************************************/ - // OAuth2 related internal functions - - function storeToken() { - return new Promise(function (resolve, reject) { - if (!_config.client_id) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'client_id missing in client object')); - return; - } - if (!window.localStorage) { - // localStorage not available, e.g. node.js - resolve(); - return; - } - var apps = window.localStorage.getItem('oauth'); - try { - apps = JSON.parse(apps); - } catch (e) { - window.localStorage.removeItem('oauth'); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Error parsing OAuth app in localStorage')); - return; - } - if (!apps || typeof apps !== 'object') { - apps = {}; - } - apps[_config.client_id] = { - access_token: _accessToken, - domain: _self.domain, - expires_at: _expiresAt - }; - window.localStorage.setItem('oauth', JSON.stringify(apps)); - logger.debug('[SDK]: Stored access_token for client_id: ' + _config.client_id); - resolve(); - }); - } - - function retrieveToken() { - return new Promise(function (resolve, reject) { - var apps = window.localStorage.getItem('oauth'); - if (!apps) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'OAuth access token not found in localStorage')); - return; - } - try { - apps = JSON.parse(apps); - } catch (e) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Error parsing OAuth app in localStorage')); - return; - } - if (typeof apps !== 'object') { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'invalid data in localStorage')); - return; - } - var app = apps[_config.client_id]; - if (app && app.access_token) { - _accessToken = app.access_token; - _expiresAt = app.expires_at; - logger.debug('[SDK]: Retrieved access_token for client_id: ' + _config.client_id); - resolve(_accessToken); - } else { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'client_id not found in localStorage')); - } - }); - } - - // Register a global listener which can be called by OAuth2 popup - function attachOauthCallback(callback) { - var guid = '_circuit_' + parseInt(Math.random() * 1e12, 10).toString(36); - window[guid] = function () { - try { - if (callback.apply(this, arguments)) { - delete window[guid]; - } - } catch (e) { - logger.error(e); - } - }; - return guid; - } - - // Open OAuth 2.0 popup for authentication and grant permissions (i.e. /oauth/authorize) - function oauthAuthorize() { - return new Promise(function (resolve, reject) { - var interval = null; - - // `client_id` has to be passed - if (!_config.client_id) { - if (_self.oauthwin) { - _self.oauthwin.close(); - _self.oauthwin = null; - - } - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'client_id missing in client object')); - return; - } - - // Clear access_token - _accessToken = undefined; - - var callbackId = attachOauthCallback(function (obj) { - interval && window.clearInterval(interval); - if (!obj || obj.error) { - // Clear access_token and reject login - _accessToken = undefined; - var err = obj ? obj.error : 'The authentication was not completed'; - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - } else { - _accessToken = obj.access_token; - _expiresAt = obj.expires_in && (Date.now() + (obj.expires_in * 1000)); - - // Save access_token and fulfill login - storeToken() - .catch(function (err) { - // Still resolve promise since OAuth was successful, but log - // and error for failed storing of token - logger.error('[SDK]: Failed to store the OAuth access token.', err); - }) - .then(function () { - resolve({username: obj.username, accessToken: obj.access_token}); - }); - } - }); - - var popupOptions = { - client_id: _config.client_id, - redirect_uri: _config.redirect_uri, - response_type: 'token', - scope: _config.scope || '' - }; - - // Add transparent state information - popupOptions.state = JSON.stringify({ - callback: callbackId, - display: 'popup', - redirect_uri: popupOptions.redirect_uri - }); - - var url = 'https://' + _config.domain + '/oauth/authorize?' + Utils.toQS(popupOptions); - - // Use the already opened window to prevent popup blocker - var win = _self.oauthwin || open(url, 450, 500); - if (!win) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'OAuth popup blocked')); - return; - } - - _self.oauthwin = null; - win.location.href = url; - - interval = window.setInterval(function () { - if (!win || win.closed) { - interval && window.clearInterval(interval); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'OAuth popup cancelled')); - } - }, 500); - }); - } - - // Show the authentication popup (login) only (i.e. /oauth/login) - function oauthLogin() { - return new Promise(function (resolve, reject) { - var interval = null; - - var callbackId = attachOauthCallback(function (obj) { - interval && window.clearInterval(interval); - if (!obj || obj.error) { - var err = obj ? obj.error : 'The authentication was not completed'; - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - } else { - resolve({username: obj.username}); - } - }); - - // Add transparent state information - var state = JSON.stringify({ - callback: callbackId, - display: 'popup', - redirect_uri: _config.redirect_uri - }); - - var popupOptions = { - return_to: _config.redirect_uri + '#state=' + state - }; - - var url = 'https://' + _config.domain + '/oauth/login?' + Utils.toQS(popupOptions); - - // Use the already opened window to prevent popup blocker - var win = _self.oauthwin || open(url, 450, 500); - _self.oauthwin = null; - win.location.href = url; - - interval = window.setInterval(function () { - if (!win || win.closed) { - interval && window.clearInterval(interval); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'OAuth popup cancelled')); - } - }, 500); - }); - } - - // Show the password change popup - function oauthChangePassword() { - return new Promise(function (resolve, reject) { - var interval = null; - - var callbackId = attachOauthCallback(function (obj) { - interval && window.clearInterval(interval); - if (!obj || obj.error) { - var err = obj ? obj.error : 'The password change failed'; - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - } else { - resolve(); - } - }); - - var popupOptions = { - client_id: _config.client_id, - redirect_uri: _config.redirect_uri, - response_type: 'token', - scope: _config.scope || '' - }; - - // Add transparent state information - popupOptions.state = JSON.stringify({ - callback: callbackId, - display: 'popup', - redirect_uri: popupOptions.redirect_uri - }); - - // Complete popup options needed in case redirect to login page is needed - var url = 'https://' + _config.domain + '/oauth/changePassword?' + Utils.toQS(popupOptions); - - // Use the already opened window to prevent popup blocker - var win = _self.oauthwin || open(url, 450, 500); - _self.oauthwin = null; - win.location.href = url; - - interval = window.setInterval(function () { - if (!win || win.closed) { - interval && window.clearInterval(interval); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'OAuth popup cancelled')); - } - }, 500); - }); - } - - // Validates a bearer token - function validateToken(accessToken) { - accessToken = accessToken || _accessToken; - return xhr({ - type: 'GET', - url: 'https://' + _self.domain + '/oauth/token/' + accessToken, - crossDomain: true - }); - } - - function resetOAuthRenewTimer() { - if (_config.autoRenewToken && _expiresAt) { - // Start timer to auto-renew OAuth2 token 1 min prior to expiry - _oauthRenewTokenTimer && window.clearTimeout(_oauthRenewTokenTimer); - var duration = _expiresAt - Date.now() - (60 * 1000); - if (duration > 0) { - _oauthRenewTokenTimer = window.setTimeout(renewToken, duration); - } - } - } - - function renewToken() { - return new Promise(function (resolve, reject) { - var obj = { - type: 'POST', - url: 'https://' + _self.domain + '/oauth/token', - data: { - scope: _config.scope, - grant_type: 'implicit' - } - }; - - if (_config.client_secret) { - obj.headers = { - Authorization: 'Basic ' + window.btoa(_config.client_id + ':' + _config.client_secret) - }; - obj.data.grant_type = 'client_credentials'; - } - - xhr(obj) - .then(function (data) { - var obj = JSON.parse(data); - if (obj && obj.access_token && obj.token_type === 'Bearer') { - _accessToken = obj.access_token; - _clientApiHandler.setToken(_accessToken); - _expiresAt = obj.expires_in && (Date.now() + (obj.expires_in * 1000)); - resetOAuthRenewTimer(); - addToEventQueue({type: 'accessTokenRenewed'}); - resolve(_accessToken); - } else { - _accessToken = null; - _expiresAt = null; - var err = new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Error renewing OAuth2 access token'); - addToEventQueue({type: 'renewAccessTokenFailed', error: err}); - reject(err); - } - }) - .catch(reject); - }); - } - - function getStuff() { - return new Promise(function (resolve, reject) { - var types = [ - Constants.GetStuffType.USER, - Constants.GetStuffType.SUPPORT_CONVERSATION_ID, - Constants.GetStuffType.TELEPHONY_CONVERSATION_ID, - Constants.GetStuffType.ACCOUNTS, - Constants.GetStuffType.PRESENCE_STATE - ]; - _clientApiHandler.getStuff(types, function (err, stuff) { - if (apiError(err, reject, true)) { return; } - _telephonyConvId = stuff.telephonyConvId; - _supportConvId = stuff.supportConvId; - publicizeUser(stuff.user) - .then(function (user) { - _self.loggedOnUser = user; - _services.SdkHelperSvc.setLocalUser(user); - resolve(); - }) - .catch(reject); - }); - }); - } - - function initActiveSessions() { - if (!hasScope(Constants.OAuthScope.CALLS)) { - return Promise.resolve(); - } - return _services.CallControlSvc.initActiveSessionsForSdk(); - } - - function initAtc() { - if (hasScope(Constants.OAuthScope.CALLS) && _config.enableTelephony) { - _services.AtcRegistrationSvc.initAtcForSdk(); - } - return Promise.resolve(); - } - - function initWs() { - return getUserData() - .then(getStuff) - .then(initActiveSessions) - .then(initAtc); - } - - // Setup the websocket, then get the userData, the loggedOn user and init the active sessions. - function wsLogon() { - return setupWebSocket() - .then(initWs) - .then(function () { - return _self.loggedOnUser; - }); - } - - // Authenticate using Client Credentials Grant - function authenticateClientCredentials() { - return new Promise(function (resolve, reject) { - xhr({ - type: 'POST', - url: 'https://' + _self.domain + '/oauth/token', - headers: { - Authorization: 'Basic ' + window.btoa(_config.client_id + ':' + _config.client_secret) - }, - data: { - grant_type: 'client_credentials', - scope: _config.scope - } - }) - .then(function (data) { - var obj = JSON.parse(data); - if (obj && obj.access_token && obj.token_type === 'Bearer') { - _accessToken = obj.access_token; - _expiresAt = obj.expires_in && (Date.now() + (obj.expires_in * 1000)); - resetOAuthRenewTimer(); - resolve({accessToken: _accessToken}); - } else { - _accessToken = null; - _expiresAt = null; - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Internal error with Client Credentials Grant authentication')); - } - }) - .catch(reject); - }); - } - - // Authenticate using Resource Owner Grant - function authenticateResourceOwner(username, password) { - return new Promise(function (resolve, reject) { - xhr({ - type: 'POST', - url: 'https://' + _self.domain + '/oauth/token', - headers: { - Authorization: 'Basic ' + window.btoa(_config.client_id) - }, - data: { - username: username, - password: password, - grant_type: 'password', - scope: _config.scope - } - }) - .then(function (data) { - var obj = JSON.parse(data); - if (obj && obj.access_token && obj.token_type === 'Bearer') { - _accessToken = obj.access_token; - _expiresAt = obj.expires_in && (Date.now() + (obj.expires_in * 1000)); - resetOAuthRenewTimer(); - resolve({accessToken: _accessToken}); - } else { - _accessToken = null; - _expiresAt = null; - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Internal error with Client Credentials Grant authentication')); - } - }) - .catch(reject); - }); - } - - /*********************************************************************************************/ - // API functions - - function logout(force) { - if (!force) { - // Clear access_token in memory - _accessToken = null; - _expiresAt = null; - - // Disconnect websocket connection - _clientApiHandler.disconnect(); - return Promise.resolve(); - } - - return new Promise(function (resolve) { - var logoutCb = function () { - // Clear access_token in memory - _accessToken = null; - _expiresAt = null; - - // Disconnect websocket connection - _clientApiHandler.disconnect(); - window.clearInterval(_eventQueueIntervalId); - - // Raise 'Disconnected' since the eventQueue is already stopped - _self.dispatch({type: 'connectionStateChanged', state: ConnectionState.Disconnected}); - resolve(); - }; - - // Start 2s timer waiting for logout response. If no response after 2s, continue logging out anyway. - var logoutPromise = window.setTimeout(function () { - logoutPromise = null; - logoutCb(); - }, 2000); - - _clientApiHandler.logout(function () { - if (logoutPromise) { - window.clearTimeout(logoutPromise); - logoutCb(); - } - }); - }); - } - - function revokeToken(token) { - return new Promise(function (resolve, reject) { - token = token || _accessToken; - - // Clear access_token in memory and localStorage - // even if the API call below fails for some reason - _accessToken = null; - _expiresAt = null; - storeToken(); - - _clientApiHandler.revokeAccessToken(token, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getUserData() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getUserData(function (err, userData) { - if (apiError(err, reject)) { return; } - _rtcSessionController.processClientSettings(userData.clientSettings); - resolve(userData); - }); - }); - } - - function getLoggedOnUser() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getLoggedOnUser(function (err, getLoggedOn) { - if (apiError(err, reject)) { return; } - publicizeUser(getLoggedOn.user) - .then(function (user) { - _self.loggedOnUser = user; - _services.SdkHelperSvc.setLocalUser(user); - resolve(user); - }); - }); - }); - } - - function getTenantUsers(options) { - return new Promise(function (resolve, reject) { - options = options || {}; - options.pageSize = options.pageSize || 25; - options.sorting = options.sorting || Constants.GetAccountsSorting.BY_FIRST_NAME; - _clientApiHandler.getAccounts(options, function (err, resp) { - if (apiError(err, reject)) { return; } - Promise.all(resp.accounts.map(function (item) { return publicizeUser(item.user); })) - .then(resolve) - .catch(reject); - }); - }); - } - - function getConversations(options) { - return new Promise(function (resolve, reject) { - options = options || {}; - options.numberOfConversations = options.numberOfConversations || 25; - options.direction = options.direction || Constants.SearchDirection.BEFORE; - - _clientApiHandler.getConversations(_self.loggedOnUser.userId, options.timestamp, options.direction, options.numberOfConversations, Constants.ConversationFilter.ALL, function (err, conversations) { - if (apiError(err, reject)) { return; } - Promise.all(conversations.map(publicizeConversation)) - .then(resolve) - .catch(reject); - }); - }); - } - - function getMarkedConversations() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getMarkedConversationsList(function (err, markedConversationsList) { - if (apiError(err, reject, true)) { return; } - resolve(markedConversationsList || []); - }); - }); - } - - function getFavoriteConversationIds() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getFavoriteConversationIds(function (err, favoriteConversationIds) { - if (apiError(err, reject, true)) { return; } - resolve(favoriteConversationIds || []); - }); - }); - } - - function favoriteConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.markConversation({convId: convId, markType: Constants.ConversationMarkFilter.FAVORITE}, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function unfavoriteConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.unmarkConversation({convId: convId, markType: Constants.ConversationMarkFilter.FAVORITE}, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function archiveConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.markConversation({convId: convId, markType: Constants.ConversationMarkFilter.MUTE}, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function unarchiveConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.unmarkConversation({convId: convId, markType: Constants.ConversationMarkFilter.MUTE}, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getAllLabels() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getAllLabels(function (err, labels) { - if (apiError(err, reject, true)) { return; } - resolve(labels || []); - }); - }); - } - - function getConversationsByFilter(options) { - return new Promise(function (resolve, reject) { - if (!options || !options.filterConnector) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'options.filterConnector')); - return; - } - - options.retrieveAction = options.retrieveAction || Circuit.Enums.RetrieveAction.CONVERSATIONS; - - if (options.retrieveAction !== Circuit.Enums.RetrieveAction.CONVERSATIONS && - options.retrieveAction !== Circuit.Enums.RetrieveAction.CONVERSATION_IDS) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Invalid retrieveAction')); - } - - _clientApiHandler.getConversationsByFilter(options, function (err, res) { - if (apiError(err, reject, true)) { return; } - - if (options.retrieveAction === Circuit.Enums.RetrieveAction.CONVERSATIONS) { - var conversations = res.conversations || []; - Promise.all(conversations.map(publicizeConversation)) - .then(resolve) - .catch(reject); - } else if (options.retrieveAction === Circuit.Enums.RetrieveAction.CONVERSATION_IDS) { - resolve(res.conversationIds || []); - } - }); - }); - } - - function getConversationsByLabel(labelId, options) { - if (!labelId) { - return Promise.reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'labelId')); - } - - options = options || {}; - options.filterConnector = { - conditions: [{ - filterTarget: Constants.FilterTarget.LABEL_ID, - expectedValue: [labelId] - }] - }; - options.retrieveAction = Circuit.Enums.RetrieveAction.CONVERSATIONS; - return getConversationsByFilter(options); - } - - function getArchivedConversations(options) { - options = options || {}; - options.filterConnector = { - conditions: [{ - filterTarget: Constants.FilterTarget.LABEL_ID, - expectedValue: [Constants.SystemLabel.ARCHIVED] - }] - }; - options.retrieveAction = Circuit.Enums.RetrieveAction.CONVERSATIONS; - return getConversationsByFilter(options); - } - - function getConversationById(convId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getConversationById(convId, function (err, conversation) { - if (apiError(err, reject)) { return; } - publicizeConversation(conversation) - .then(resolve) - .catch(reject); - }); - }); - } - - function getConversationsByIds(convIds) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getConversationsByIds(convIds, function (err, conversations) { - if (apiError(err, reject)) { return; } - Promise.all(conversations.map(publicizeConversation)) - .then(resolve) - .catch(reject); - }); - }); - } - - function getConversationParticipants(convId, options) { - return new Promise(function (resolve, reject) { - options = options || {}; - _clientApiHandler.getConversationParticipants(convId, options, function (err, res) { - if (apiError(err, reject)) { return; } - Promise.all(res.participants.map(publicizeConversationParticipant)) - .then(function (participants) { - res.participants = participants; - resolve(res); - }) - .catch(reject); - }); - }); - } - - function getItemById(itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getItemById(itemId, function (err, item) { - if (apiError(err, reject)) { return; } - publicizeItem(item) - .then(resolve) - .catch(reject); - }); - }); - } - - function getConversationItems(convId, options) { - return new Promise(function (resolve, reject) { - options = options || {}; - options.modificationDate = options.modificationDate || undefined; - options.creationDate = options.creationDate || options.creationTime || undefined; - options.direction = options.direction || Constants.SearchDirection.BEFORE; - options.numberOfItems = options.numberOfItems || 25; - - _clientApiHandler.getConversationItems(convId, options.modificationDate, options.creationDate, options.direction, options.numberOfItems, function (err, items) { - if (apiError(err, reject)) { return; } - Promise.all(items.map(publicizeItem)) - .then(resolve) - .catch(reject); - }); - }); - } - - function getConversationFeed(convId, options) { - return new Promise(function (resolve, reject) { - options = options || {}; - _clientApiHandler.getConversationFeed(convId, options.timestamp, options.minTotalItems, options.maxTotalUnread, options.commentsPerThread, options.maxUnreadPerThread, function (err, threads, hasOlderThreads) { - if (apiError(err, reject)) { return; } - Promise.all(threads.map(publicizeThread)) - .then(function (threads) { - resolve({ - threads: threads, - hasOlderThreads: hasOlderThreads - }); - }) - .catch(reject); - }); - }); - } - - function getItemsByThread(convId, threadId, options) { - return new Promise(function (resolve, reject) { - options = options || {}; - var timestamp = options.modificationDate || options.creationDate; - var timestampFilter = options.modificationDate ? Constants.TimestampFilter.MODIFICATION : Constants.TimestampFilter.CREATION; - - _clientApiHandler.getThreadComments(convId, threadId, timestamp, timestampFilter, options.direction, options.number || options.numberOfItems, function (err, comments, hasMoreComments) { - if (apiError(err, reject)) { return; } - Promise.all(comments.map(publicizeItem)) - .then(function (items) { - resolve({ - items: items, - hasMore: hasMoreComments - }); - }) - .catch(reject); - }); - }); - } - - function getUserByEmail(email) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getUserByEmail(email, function (err, user) { - if (apiError(err, reject, null, function (errObj) { errObj && errObj.code === Constants.ReturnCode.NO_RESULT && (errObj.info = 'User does not exist'); })) { return; } - publicizeUser(user) - .then(resolve) - .catch(reject); - }); - }); - } - - function getUsersByEmail(emails) { - return new Promise(function (resolve, reject) { - if (!emails) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'email is required')); - return; - } - if (!emails.length) { - return resolve([]); - } - var filter = {emailAddresses: emails}; - _clientApiHandler.getUsersByEmails(filter, function (err, users) { - if (apiError(err, reject)) { return; } - Promise.all(users.map(publicizeUser)) - .then(resolve) - .catch(reject); - }); - }); - } - - function getUserById(userId) { - return new Promise(function (resolve, reject) { - if (!userId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'userId is required')); - return; - } - - _clientApiHandler.getUser(userId, function (err, user) { - if (apiError(err, reject)) { return; } - publicizeUser(user) - .then(resolve) - .catch(reject); - }); - }); - } - - function getUsersById(userIds, limited) { - return new Promise(function (resolve, reject) { - if (!userIds) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'userIds is required')); - return; - } - if (!userIds.length) { - return resolve([]); - } - var fn = limited ? _clientApiHandler.getPartialUsersByIds : _clientApiHandler.getUsersByIds; - fn(userIds, function (err, users) { - if (apiError(err, reject)) { return; } - Promise.all(users.map(publicizeUser)) - .then(resolve) - .catch(reject); - }); - }); - } - - function getDevices() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getDevices(function (err, devices) { - if (apiError(err, reject, true)) { return; } - resolve(devices || []); - }); - }); - } - - function getTelephonyConversationId() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getTelephonyConversationId(function (err, convId) { - if (apiError(err, reject)) { return; } - resolve(convId); - }); - }); - } - - function getSupportConversationId() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getSupportConversationId(function (err, convId) { - if (apiError(err, reject)) { return; } - resolve(convId); - }); - }); - } - - function getDirectConversationByUserId(queryObj) { - if (typeof queryObj === 'string') { - queryObj = {userId: queryObj, createIfNotExists: false}; - } - - return new Promise(function (resolve, reject) { - if (!queryObj.userId) { - reject(new Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'user ID is required')); - return; - } - - _clientApiHandler.getConversationByUser(queryObj.userId, function (err, conversation) { - if (err) { - if (err === Constants.ReturnCode.NO_RESULT) { - if (queryObj.createIfNotExists) { - createDirectConversationWithUserId(queryObj.userId).then(function (obj) { - resolve(obj && obj.conversation); - }); - } else { - resolve(); - } - } else { - apiError(err, reject); - } - return; - } - publicizeConversation(conversation) - .then(resolve) - .catch(reject); - }); - }); - } - - // Get direct conversation with a user by its email or userId - function getDirectConversationWithUser(query, createIfNotExists) { - if (typeof query !== 'string') { - return Promise.reject(new Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'email or user ID is required')); - } - if (query.match(Utils.EMAIL_ADDRESS_PATTERN)) { - return getUserByEmail(query).then(function (user) { - return {userId: user.userId, createIfNotExists: createIfNotExists}; - }).then(getDirectConversationByUserId); - } - return getDirectConversationByUserId({userId: query, createIfNotExists: createIfNotExists}); - } - - function addTextItem(convId, content) { - return new Promise(function (resolve, reject) { - function sendItem(item) { - item.mentionedUsers = Utils.createMentionedUsersArray(item.content); - _clientApiHandler.addTextItem(item, function (err, item) { - if (apiError(err, reject)) { return; } - publicizeItem(item) - .then(resolve) - .catch(reject); - }); - } - - if (!convId) { - reject(new Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - - if (!content) { - reject(new Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'content is required')); - return; - } - if (typeof content === 'string') { - // API option 1 with the content being a simple string - sendItem({ - convId: convId, - content: content, - contentType: 'RICH' - }); - } else if (typeof content === 'object') { - // API option 2 with the content being an object - var item = { - convId: convId, - parentId: content.parentId, - contentType: content.contentType || Constants.TextItemContentType.RICH, - subject: content.subject, - content: content.content, - externalAttachmentMetaData: content.externalAttachments - }; - // Upload files first, then send message with file metadata - if (content.attachments && content.attachments.length > 0) { - - // Must scope FileUpload with circuit to allow node SDK to inject it's implementation - var fileUpload = new circuit.FileUpload(_config); - - fileUpload.uploadFiles(content.attachments, _self.domain).then(function (results) { - item.attachmentMetaData = []; - results.forEach(function (result) { - item.attachmentMetaData.push(result); - }); - sendItem(item); - }).catch(function (err) { - logger.error('[SDK]: Fileupload error.', err); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - }); - } else { - // message is an object without attachments. Send item immediately - sendItem(item); - } - } else { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Invalid content')); - } - }); - } - - function updateTextItem(item) { - return new Promise(function (resolve, reject) { - function sendItem(item) { - item.mentionedUsers = Utils.createMentionedUsersArray(item.content); - _clientApiHandler.updateTextItem(item, function (err, item) { - if (apiError(err, reject)) { return; } - publicizeItem(item) - .then(resolve) - .catch(reject); - }); - } - - if (!item) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'item is required')); - return; - } - if (!item.itemId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'item.itemId is required')); - return; - } - if (typeof item === 'object') { - // Upload files first, then send message with file metadata - if (item.attachments && item.attachments.length > 0) { - - // Must scope FileUpload with circuit to allow node SDK to inject it's implementation - var fileUpload = new circuit.FileUpload(_config); - - fileUpload.uploadFiles(item.attachments, _self.domain).then(function (results) { - item.attachmentMetaData = []; - results.forEach(function (result) { - item.attachmentMetaData.push(result); - }); - sendItem(item); - }).catch(function (err) { - logger.error('[SDK]: Fileupload error.', err); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - }); - } else { - // message is an object without attachments. Send item immediately - sendItem(item); - } - } else { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Invalid content')); - } - }); - } - - function createDirectConversationWithUserId(userId) { - return new Promise(function (resolve, reject) { - if (_self.loggedOnUser.userId === userId) { - return reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'userId is of logged on user')); - } - - var conversation = { - type: Constants.ConversationType.DIRECT, - participants: [{ userId: userId }, { userId: _self.loggedOnUser.userId }] - }; - - _clientApiHandler.createConversation(conversation, function (err, newConversation, alreadyExists) { - if (apiError(err, reject)) { return; } - publicizeConversation(newConversation) - .then(function (c) { - resolve({ - conversation: c, - alreadyExists: alreadyExists - }); - }); - }); - }); - } - - function createDirectConversation(participant) { - if (!participant || typeof participant !== 'string') { - return Promise.reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'Participant email or user ID is required')); - } - if (participant.match(Utils.EMAIL_ADDRESS_PATTERN)) { - return getUserByEmail(participant) - .then(function (user) { return user.userId; }) - .then(createDirectConversationWithUserId); - } else { - return createDirectConversationWithUserId(participant); - } - } - - function createGroupConversation(participants, topic) { - return new Promise(function (resolve, reject) { - if (!participants) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'Participants is required')); - return; - } - - if (participants.indexOf(_self.loggedOnUser.userId) === -1) { - participants.unshift(_self.loggedOnUser.userId); - } - - if (participants.length < 2) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'At least one participant (other than self) is required')); - return; - } - - if (participants.indexOf('') > -1 || participants.indexOf(null) > -1) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Participant cannot be empty')); - return; - } - - participants = participants.map(function (p) { - return { userId: p }; - }); - - var conversation = { - type: Constants.ConversationType.GROUP, - participants: participants, - topic: topic - }; - _clientApiHandler.createConversation(conversation, function (err, newConversation) { - if (apiError(err, reject)) { return; } - publicizeConversation(newConversation) - .then(resolve) - .catch(reject); - }); - }); - } - - function createConferenceBridge(topic) { - return new Promise(function (resolve, reject) { - if (!topic) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'Topic is required')); - return; - } - - var conversation = { - type: Constants.ConversationType.GROUP, - participants: [{userId: _self.loggedOnUser.userId}], - topic: topic - }; - _clientApiHandler.createConversation(conversation, function (err, newConversation) { - if (apiError(err, reject)) { return; } - publicizeConversation(newConversation) - .then(resolve) - .catch(reject); - }); - }); - } - - function createCommunity(participants, topic, description) { - return new Promise(function (resolve, reject) { - if (!participants || !participants.length) { - participants = [_self.loggedOnUser.userId]; - } - participants = participants.map(function (p) { return {userId: p}; }); - var conversation = { - type: Constants.ConversationType.OPEN, - participants: participants, - topic: topic, - description: description - }; - _clientApiHandler.createConversation(conversation, function (err, newConversation) { - if (apiError(err, reject)) { return; } - publicizeConversation(newConversation) - .then(resolve) - .catch(reject); - }); - }); - } - - function addParticipant(convId, userIds, addToCall) { - return new Promise(function (resolve, reject) { - var request = { - convId: convId, - userId: userIds - }; - - _clientApiHandler.addParticipant(request, function (err, res) { - if (apiError(err, reject)) { return; } - publicizeConversation(res.conversation) - .then(resolve) - .catch(reject); - - if (addToCall) { - // If a call is ongoing dial out the added users. - _services.SdkHelperSvc.getConversationPromise(convId) - .then(function (conversation) { - if (!conversation.call) { - return; - } - if (res.conversation.convId === conversation.convId) { - var users = userIds.map(function (userId) { - return {userId: userId}; - }); - // Raise an event to CallControlSvc to add the participants to the call - _services.PubSubSvc.publish('/conversation/participants/add', [conversation.call, users]); - } else { - // New Conversation created based on a RTC conversation. - _services.PubSubSvc.publish('/conversation/upgrade', [conversation, res.conversation]); - } - }); - } - }); - }); - } - - function removeParticipant(convId, userId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.removeParticipant(convId, userId, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getFlaggedItems() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getFlaggedItems(function (err, convWithFlaggedItems) { - if (apiError(err, reject, true)) { return; } - resolve(convWithFlaggedItems || []); - }); - }); - } - - function setFlagItem(convId, itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.setFlagItem({convId: convId, itemId: itemId}, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function clearFlagItem(convId, itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.clearFlagItem(convId, itemId, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function likeItem(itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.likeTextItem(itemId, _self.loggedOnUser.userId, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function unlikeItem(itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.unlikeTextItem(itemId, _self.loggedOnUser.userId, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function markItemsAsRead(convId, creationTime) { - return new Promise(function (resolve, reject) { - creationTime = creationTime || Date.now(); - _clientApiHandler.setReadPointer(convId, creationTime, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function moderateConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.moderateConversation(convId, function (err, res) { - if (apiError(err, reject)) { return; } - if (!res || res.moderateResult !== 'LOCK_OK') { - reject(res); - return; - } - resolve(); - }); - }); - } - - function unmoderateConversation(convId) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.unmoderateConversation(convId, function (err, res) { - if (apiError(err, reject)) { return; } - if (!res || res.moderateResult !== 'UNLOCK_OK') { - reject(res); - return; - } - resolve(); - }); - }); - } - - function grantModeratorRights(convId, userId) { - return new Promise(function (resolve, reject) { - if (!convId || !userId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId and userId are required')); - return; - } - _clientApiHandler.grantModeratorRights(convId, userId, function (err, res) { - if (apiError(err, reject)) { return; } - if (!res || res.grantModeratorResult !== 'GRANT_OK') { - reject(res); - return; - } - resolve(); - }); - }); - } - - function dropModeratorRights(convId, userId) { - return new Promise(function (resolve, reject) { - if (!convId || !userId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId and userId are required')); - return; - } - _clientApiHandler.dropModeratorRights(convId, userId, function (err, res) { - if (apiError(err, reject)) { return; } - if (!res || res.dropModeratorResult !== 'DROP_OK') { - reject(res); - return; - } - resolve(); - }); - }); - } - - function updateGuestAccess(convId, disabled) { - return new Promise(function (resolve, reject) { - if (!convId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'convId is required')); - return; - } - _clientApiHandler.updateGuestAccess(convId, disabled, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function updateUser(userObj) { - return new Promise(function (resolve, reject) { - _clientApiHandler.updateUser(userObj, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function parseUserSettings(settings) { - // Map settings to SDK UserSettings object - var result = {}; - settings && settings.forEach(function (s) { - switch (s.key) { - case Constants.UserSettingKey.OPT_OUT_PRESENCE: - result.presenceOptOut = s.booleanValue; - break; - case Constants.UserSettingKey.SHARE_LOCATION: - result.shareLocation = s.booleanValue; - break; - case Constants.UserSettingKey.VOICEMAIL_ENABLED: - result.voicemailEnabled = s.booleanValue; - break; - case Constants.UserSettingKey.VOICEMAIL_TIMEOUT: - result.voicemailTimeout = s.numberValue; - break; - case Constants.UserSettingKey.VOICEMAIL_CUSTOMGREETING_ENABLED: - result.voicemailCustomGreetingEnabled = s.booleanValue; - break; - case Constants.UserSettingKey.VOICEMAIL_CUSTOMGREETING_URI: - result.voicemailCustomGreetingUri = s.stringValue ? fullDomain() + 'fileapi?fileid=' + s.stringValue : null; - break; - } - }); - return result; - } - - function setUserSettings(userSettings) { - return new Promise(function (resolve, reject) { - if (!userSettings) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'userSettings is required')); - return; - } - var settings = []; - if (userSettings.presenceOptOut !== undefined) { - settings.push({ - key: Constants.UserSettingKey.OPT_OUT_PRESENCE, - dataType: Constants.UserSettingDataType.BOOLEAN, - booleanValue: !!userSettings.presenceOptOut - }); - } - if (userSettings.shareLocation !== undefined) { - settings.push({ - key: Constants.UserSettingKey.SHARE_LOCATION, - dataType: Constants.UserSettingDataType.BOOLEAN, - booleanValue: !!userSettings.shareLocation - }); - } - if (userSettings.voicemailEnabled !== undefined) { - settings.push({ - key: Constants.UserSettingKey.VOICEMAIL_ENABLED, - dataType: Constants.UserSettingDataType.BOOLEAN, - booleanValue: !!userSettings.voicemailEnabled - }); - } - if (userSettings.voicemailTimeout !== undefined) { - settings.push({ - key: Constants.UserSettingKey.VOICEMAIL_TIMEOUT, - dataType: Constants.UserSettingDataType.NUMBER, - numberValue: userSettings.voicemailTimeout - }); - } - if (userSettings.voicemailCustomGreetingEnabled !== undefined) { - settings.push({ - key: Constants.UserSettingKey.VOICEMAIL_CUSTOMGREETING_ENABLED, - dataType: Constants.UserSettingDataType.BOOLEAN, - booleanValue: !!userSettings.voicemailCustomGreetingEnabled - }); - } - - _clientApiHandler.setUserSettings(settings, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getUserSettings() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getUserSettings([Constants.UserSettingArea.ALL], function (err, settings) { - if (apiError(err, reject)) { return; } - resolve(parseUserSettings(settings)); - }); - }); - } - - function uploadCustomVoicemailGreeting(file) { - return new Promise(function (resolve, reject) { - if (!file) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'file is required')); - return; - } - - var fileUpload = new circuit.FileUpload(_config); - fileUpload.uploadFiles([file], _self.domain).then(function (results) { - if (results && results.length) { - var settings = [{ - key: Constants.UserSettingKey.VOICEMAIL_CUSTOMGREETING_URI, - dataType: Constants.UserSettingDataType.STRING, - stringValue: results[0].fileId - }]; - _clientApiHandler.setUserSettings(settings, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - } else { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'error uploading file')); - } - }).catch(function (err) { - logger.error('[SDK]: Fileupload error.', err); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - }); - }); - } - - function setPresence(presenceObj) { - return new Promise(function (resolve, reject) { - presenceObj.userId = _self.loggedOnUser.userId; - _clientApiHandler.setPresence(presenceObj, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getPresence(userIds, full) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getPresence(userIds, full, function (err, presenceList) { - if (apiError(err, reject)) { return; } - resolve(presenceList); - }); - }); - } - - function setStatusMessage(statusMessage) { - return new Promise(function (resolve, reject) { - statusMessage = statusMessage || ''; - - // Unfortunately the client BL is responsible to send a setPresence - // and a setUserSettings request, so we need to do that as well. - _clientApiHandler.setUserSettings({ - key: Constants.UserSettingKey.STATUS_MESSAGE_TEXT, - dataType: Constants.UserSettingDataType.STRING, - stringValue: statusMessage - }, function (err) { - if (apiError(err, reject)) { return; } - - // State is required, so pass the current state - getPresence([_self.loggedOnUser.userId]) - .then(function (presence) { - return setPresence({ - state: presence.length && presence[0].state, - statusMessage: statusMessage - }); - }) - .then(resolve) - .catch(reject); - }); - }); - } - - function getStatusMessage() { - return new Promise(function (resolve, reject) { - _clientApiHandler.getUserSettings([Constants.UserSettingArea.ALL], function (err, settings) { - if (apiError(err, reject)) { return; } - var statusMessage = ''; - settings && settings.some(function (s) { - if (s.key === Constants.UserSettingKey.STATUS_MESSAGE_TEXT) { - statusMessage = s.stringValue; - return true; - } - }); - resolve(statusMessage); - }); - }); - } - - function subscribePresence(userIds) { - return new Promise(function (resolve, reject) { - _clientApiHandler.subscribePresence(userIds, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function unsubscribePresence(userIds) { - return new Promise(function (resolve, reject) { - _clientApiHandler.unsubscribePresence(userIds, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function renewSessionToken() { - return new Promise(function (resolve, reject) { - _clientApiHandler.renewToken(_self.loggedOnUser.userId, function (err) { - if (apiError(err, reject)) { - addToEventQueue({type: 'renewSessionTokenFailed', error: err}); - return; - } - logger.debug('[SDK]: Session token renewed'); - addToEventQueue({type: 'sessionTokenRenewed'}); - resolve(); - }); - }); - } - - function startUserSearch(query) { - if (typeof query === 'string') { - query = {query: query}; - } - return new Promise(function (resolve, reject) { - _clientApiHandler.startUserSearch(query, function (err, result) { - err ? reject(err) : resolve(result); - }); - }); - } - - function startBasicSearch(query) { - var searchTerm; - if (typeof query === 'string') { - searchTerm = { - scope: Constants.SearchTerm.Scope.ALL, - searchTerm: query - }; - } else { - searchTerm = query; - } - return new Promise(function (resolve, reject) { - _clientApiHandler.startBasicSearch(searchTerm, null, function (err, result) { - err ? reject(err) : resolve(result); - }); - }); - } - - // Gets the user's device by the clientId. If none provided, first webclient is returned. - function findWebClientByClientId(clientId) { - return getDevices().then(function (devices) { - return devices.some(function (device) { - if (clientId) { - return device.clientId === clientId; - } - return (device.clientId !== _self.loggedOnUser.clientId) && - ((device.clientInfo.deviceType === Constants.DeviceType.WEB) || (device.clientInfo.deviceType === Constants.DeviceType.APPLICATION && device.clientInfo.deviceSubtype === 'DESKTOP_APP')); - }); - }); - } - - // Find a client where logged on user is also logged on. - function findClient(clientId) { - return getDevices().then(function (devices) { - return devices.some(function (device) { - return device.clientId === clientId; - }); - }); - } - - function sendClickToCallRequest(emailAddress, mediaType, destClientId, noLaunch) { - return new Promise(function (resolve, reject) { - mediaType = mediaType || 'audio'; - - function launchWeb() { - // Start call by opening a new tab - var domain = _self.domain.startsWith('sdk.') ? _self.domain.substr(4) : _self.domain; - var url = 'https://' + domain + '/#/call?user=' + emailAddress + '&media=' + mediaType; - var win = window.open(url, '_blank'); - if (!win) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Popup blocker prevented opening Circuit in new window')); - return; - } - win.focus(); - resolve({type: 'CALL_VIA_URL', win: win}); - } - - function sendRequest(clientId) { - // Attempt to start call via other logged on web client - var data = { - content: { - type: 'CLICK_TO_CALL_REQUEST', - mediaType: mediaType, - contact: { - email: emailAddress - }, - promptBeforeDial: !clientId, - sdk: true - }, - destUserId: _self.loggedOnUser.userId - }; - - clientId && (data.destClientId = clientId); - - _userToUserHandler.sendContactCardRequest(data, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve({type: 'CALL_VIA_OTHER_CLIENT'}); - }); - } - - if (destClientId) { - findClient(destClientId) - .then(function (found) { - if (found) { - sendRequest(destClientId); - } else { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'No client found for clientId ' + destClientId)); - } - }); - } else { - findWebClientByClientId() - .then(function (found) { - if (found) { - sendRequest(); - } else if (!noLaunch) { - launchWeb(); - } else { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'No clients found and launch parameter not set.')); - } - }); - } - }); - } - - - function sendClickToAnswerRequest(callId, mediaType, destClientId) { - return new Promise(function (resolve, reject) { - mediaType = mediaType || 'audio'; - - function sendRequest(clientId) { - // Attempt to answer call via other logged on web client - var data = { - content: { - type: 'CLICK_TO_ANSWER_REQUEST', - mediaType: mediaType, - callId: callId, - promptBeforeAnswer: !clientId, - sdk: true - }, - destUserId: _self.loggedOnUser.userId - }; - - clientId && (data.destClientId = clientId); - - _userToUserHandler.sendContactCardRequest(data, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve({type: 'ANSWER_VIA_OTHER_CLIENT'}); - }); - } - - if (destClientId) { - findClient(destClientId) - .then(function (found) { - if (found) { - sendRequest(destClientId); - } else { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'No client found for clientId ' + destClientId)); - } - }); - } else { - findWebClientByClientId() - .then(function (found) { - if (found) { - sendRequest(); - } else { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'No clients found and launch parameter not set.')); - } - }); - } - }); - } - - function muteDevice(callId, clientId, option) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - option = option || {}; - if (option.mic === undefined) { - option.mic = true; - } - if (option.speaker === undefined) { - option.speaker = true; - } - findWebClientByClientId(clientId) - .then(function (deviceAvailable) { - if (!deviceAvailable) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Device not found')); - } else { - // Send user to user message to other device with mute request - var data = { - content: { - type: 'MUTE_DEVICE_REQUEST', - callId: callId, - option: option - }, - destUserId: _self.loggedOnUser.userId, - destClientId: clientId, - sdk: true - }; - - _userToUserHandler.sendContactCardRequest(data, function (err, res) { - if (err) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)); - return; - } - if (!res || !res.response) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Invalid response for MUTE_DEVICE_REQUEST')); - return; - } - if (res.response.code && res.response.code !== 'OK') { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, res.response.code)); - return; - } - resolve(); - }); - } - }); - }); - } - - function addParticipantToCall(callId, to) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!to) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'to parameter is required')); - return; - } - if (!to.userId && !to.email && !to.number) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'to.userId, to.email or to.number is required')); - return; - } - - var participant = { - userId: to.userId || undefined, - dialOutPhoneNumber: to.number || undefined, - displayName: to.displayName || undefined - }; - - if (!to.userId && to.email && to.email.match(Utils.EMAIL_ADDRESS_PATTERN)) { - getUserByEmail(to.email) - .then(function (user) { - participant.userId = user.userId; - _services.CallControlSvc.addParticipantToCall(callId, participant, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - }) - .catch(reject); - } else { - _services.CallControlSvc.addParticipantToCall(callId, participant, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - } - }); - } - - function addParticipantToRtcSession(callId, to) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!to) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'to parameter is required')); - return; - } - if (!to.userId && !to.email && !to.number) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'to.userId, to.email or to.number is required')); - return; - } - - var participant = { - userId: to.userId || undefined, - dialOutPhoneNumber: to.number || undefined, - displayName: to.displayName || undefined - }; - - if (!to.userId && to.email && to.email.match(Utils.EMAIL_ADDRESS_PATTERN)) { - getUserByEmail(to.email) - .then(function (user) { - participant.userId = user.userId; - _services.CallControlSvc.addParticipantToRtcSession(callId, participant, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - }) - .catch(reject); - } else { - _services.CallControlSvc.addParticipantToRtcSession(callId, participant, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - } - }); - } - - function findCall(callId) { - var c = _services.CallControlSvc.findCall(callId); - if (c) { - return c; - } - c = _services.CallControlSvc.getActiveCall(callId); - return c || null; - } - - function getCalls() { - return Promise.all(_services.CallControlSvc.getCalls().map(publicizeCall)); - } - - function getActiveCall() { - return Promise.resolve(publicizeCall(_services.CallControlSvc.getActiveCall())); - } - - function getActiveRemoteCalls() { - return Promise.all(_services.CallControlSvc.getActiveRemoteCall().map(publicizeCall)); - } - - function getStartedCalls() { - return Promise.all(_services.CallControlSvc.getCalls().filter(function (call) { - return call.state.name === Circuit.Enums.CallState.Started.name; - }).map(publicizeCall)); - } - - function startConference(convId, mediaType) { - return getConversationById(convId).then(function (conv) { - return new Promise(function (resolve, reject) { - if (!conv) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'Conversation does not exist')); - return; - } - _services.SdkHelperSvc.addConversationToCache(conv); - _services.CallControlSvc.startConference(conv.convId, mediaType, function (err, warn, call) { - if (svcError(err, reject)) { return; } - publicizeCall(call) - .then(resolve) - .catch(reject); - }); - }); - }); - } - - function pruneConversationDetails(convDetails) { - convDetails.pin = convDetails.tenantPIN + convDetails.sessionPIN; - convDetails.link && (convDetails.link = 'https://' + _self.domain + convDetails.link); - delete convDetails.tenantPIN; - delete convDetails.sessionPIN; - delete convDetails.conversationId; - return convDetails; - } - - function getConversationDetails(convId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getJoinDetails(convId, false, function (err, convDetails) { - if (apiError(err, reject)) { return; } - resolve(pruneConversationDetails(convDetails)); - }); - }); - } - - function changeConversationPin(convId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.getJoinDetails(convId, true, function (err, convDetails) { - if (apiError(err, reject)) { return; } - resolve(pruneConversationDetails(convDetails)); - }); - }); - } - - function joinConference(callId, mediaType, clientId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - - if (!clientId || clientId === _self.loggedOnUser.clientId) { - // Join on local client - _services.CallControlSvc.joinGroupCall(callId, mediaType, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - } else { - // Join on another client - findClient(clientId) - .then(function (found) { - if (found) { - var data = { - content: { - type: 'JOIN_CONFERENCE_REQUEST', - callId: callId, - mediaType: mediaType - }, - destUserId: _self.loggedOnUser.userId, - destClientId: clientId - }; - - _userToUserHandler.sendContactCardRequest(data, function (err, res) { - if (err) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)); - return; - } - if (!res || !res.response) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Invalid response for JOIN_CONFERENCE_REQUEST')); - return; - } - if (res.response.code && res.response.code !== 'OK') { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, res.response.code)); - return; - } - resolve(); - }); - } else { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'No client found for clientId ' + clientId)); - } - }); - } - }); - } - - function pullRemoteCall(callId, fallbackToAudio) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.pullRemoteCall(callId, fallbackToAudio, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function endRemoteCall(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.endRemoteCall(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function dropParticipant(callId, userId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.dropParticipant(callId, {userId: userId}, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function muteParticipant(callId, userId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.muteParticipant(callId, {userId: userId}, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function muteRtcSession(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.muteRtcSession(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function setMediaDevices(devices) { - return new Promise(function (resolve, reject) { - if (!devices) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'devices is required')); - return; - } - if (!devices.playback && !devices.recording && !devices.video && !devices.ringing) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'devices must contain playback, recording, video or ringing attributes')); - return; - } - - if (devices.playback) { - devices.playback = Array.isArray(devices.playback) ? devices.playback : [devices.playback]; - _rtcSessionController.playbackDevices = devices.playback.map(function (id) { return {id: id}; }); - } - if (devices.recording) { - devices.recording = Array.isArray(devices.recording) ? devices.recording : [devices.recording]; - _rtcSessionController.recordingDevices = devices.recording.map(function (id) { return {id: id}; }); - } - if (devices.video) { - devices.video = Array.isArray(devices.video) ? devices.video : [devices.video]; - _rtcSessionController.videoDevices = devices.video.map(function (id) { return {id: id}; }); - } - if (devices.ringing) { - devices.ringing = Array.isArray(devices.ringing) ? devices.ringing : [devices.ringing]; - _rtcSessionController.ringingDevices = devices.ringing.map(function (id) { return {id: id}; }); - } - - - // Renegotiate media is a local webrtc call is active - var call = _services.CallControlSvc.getActiveCall(); - if (call) { - _services.CallControlSvc.renegotiateMedia(call.callId, function (err) { - err && logger.warn('[SDK]: Unable to renegotiate media.', err); - resolve(); - }); - } else { - resolve(); - } - }); - } - - function makeCall(emailAddress, mediaType, createIfNotExists) { - return getDirectConversationWithUser(emailAddress, createIfNotExists).then(function (conv) { - return new Promise(function (resolve, reject) { - if (!conv) { - reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Conversation does not exist')); - return; - } - _services.SdkHelperSvc.addConversationToCache(conv); - _services.CallControlSvc.makeCall(conv.convId, mediaType, function (err, warn, call) { - if (svcError(err, reject)) { return; } - publicizeCall(call) - .then(resolve) - .catch(reject); - }); - }); - }); - } - - function dialNumber(number, name) { - return new Promise(function (resolve, reject) { - if (!Utils.PHONE_PATTERN.test(number || null)) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Invalid number')); - return; - } - _services.CallControlSvc.dialNumber(number, name, {audio: true, video: false}, function (err, warn, call) { - if (svcError(err || warn, reject)) { return; } - publicizeCall(call) - .then(resolve) - .catch(reject); - }); - }); - } - - function sendDigits(callId, digits) { - return new Promise(function (resolve, reject) { - if (!(/^[\d#\*,]+$/.exec(digits))) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Invalid DTMF digits (Allowed: 0-9,#,*)')); - return; - } - - _services.CallControlSvc.sendDigits(callId, digits, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function answerCall(callId, mediaType, device) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.answerCallOnDevice(callId, mediaType, device && Targets[device], function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function endCall(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.endCall(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function endConference(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.endConference(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function toggleVideo(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.toggleVideo(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function changeHDVideo(callId, hdQuality) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - hdQuality = !!hdQuality; - _services.CallControlSvc.changeHDVideo(callId, hdQuality, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function toggleRemoteAudio(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var call = findCall(callId); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - return; - } - if (call.remoteAudioDisabled) { - _services.CallControlSvc.enableRemoteAudio(callId); - } else { - _services.CallControlSvc.disableRemoteAudio(callId); - } - resolve(callId); - }); - } - - function toggleRemoteVideo(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.toggleRemoteVideo(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function toggleScreenShare(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var call = findCall(callId); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - return; - } - if (call.hasLocalScreenShare()) { - _services.CallControlSvc.removeScreenShare(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - } else { - _services.CallControlSvc.addScreenShare(callId, null, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - } - }); - } - - function setScreenshareStream(callId, stream) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var call = findCall(callId); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - return; - } - if (call.hasLocalScreenShare()) { - _services.CallControlSvc.removeMediaStream(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - } else { - if (!stream) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Missing MediaStream')); - return; - } - _services.CallControlSvc.addMediaStream(callId, stream, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - } - }); - } - - function getLocalStream(id) { - return new Promise(function (resolve, reject) { - if (id === undefined) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'id is required')); - return; - } - var call = _services.CallControlSvc.getActiveCall(); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - return; - } - var stream = call.sessionCtrl.getLocalStream(id); - if (!stream) { - reject('No stream found with id ' + id); - return; - } - resolve(stream); - }); - } - - function setAudioVideoStream(callId, stream) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!stream) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'stream is required')); - return; - } - var call = findCall(callId); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - return; - } - if (Circuit.WebRTCAdapter.origGetUserMedia) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Already setting another audio/video stream')); - return; - } - - var mediaType = { - audio: !!stream.getAudioTracks().length, - video: !!stream.getVideoTracks().length - }; - - Circuit.WebRTCAdapter.origGetUserMedia = Circuit.WebRTCAdapter.getUserMedia; - Circuit.WebRTCAdapter.getUserMedia = function (constraint, cb) { - cb && cb(stream); - }; - _services.CallControlSvc.setMediaType(callId, mediaType, function (err) { - Circuit.WebRTCAdapter.getUserMedia = Circuit.WebRTCAdapter.origGetUserMedia; - delete Circuit.WebRTCAdapter.origGetUserMedia; - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function startRecording(callId, allowScreenshareRecording) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var call = _services.CallControlSvc.getActiveCall(); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No active call found')); - return; - } - if (callId !== call.callId) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Call ID is not for active call')); - return; - } - _services.CallControlSvc.startRecording(function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }, allowScreenshareRecording); - }); - } - - function stopRecording(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var call = _services.CallControlSvc.getActiveCall(); - if (!call) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No active call found')); - return; - } - if (callId !== call.callId) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'Call ID is not for active call')); - return; - } - _services.CallControlSvc.stopRecording(function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function deleteRecording(itemId) { - return new Promise(function (resolve, reject) { - _clientApiHandler.deleteRecording(itemId, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function mute(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.mute(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function unmute(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - _services.CallControlSvc.unmute(callId, function (err) { - if (svcError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getLastRtpStats(callId) { - if (!callId) { - throw new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required'); - } - var call = findCall(callId); - if (!call || !call.sessionCtrl) { - return null; - } - return call.sessionCtrl.getLastSavedStats() || []; - } - - - function getRemoteStreams(callId) { - if (!callId) { - throw new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required'); - } - var call = findCall(callId); - if (!call || !call.sessionCtrl) { - return null; - } - return call.sessionCtrl.getRemoteStreams() || []; - } - - function enableWhiteboard(callId, viewbox) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!viewbox) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'viewbox is required')); - return; - } - var data = { - rtcSessionId: callId, - viewbox: viewbox - }; - _clientApiHandler.enableWhiteboard(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function disableWhiteboard(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId - }; - _clientApiHandler.disableWhiteboard(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function addWhiteboardElement(callId, xmlElement) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!xmlElement) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'xmlElement is required')); - return; - } - var data = { - rtcSessionId: callId, - xmlElement: xmlElement - }; - _clientApiHandler.addWhiteboardElement(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function removeWhiteboardElement(callId, xmlId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!xmlId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'xmlId is required')); - return; - } - var data = { - rtcSessionId: callId, - xmlId: xmlId - }; - _clientApiHandler.removeWhiteboardElement(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function updateWhiteboardElement(callId, xmlId, xmlElement) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!xmlId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'xmlId is required')); - return; - } - if (!xmlElement) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'xmlElement is required')); - return; - } - var data = { - rtcSessionId: callId, - xmlId: xmlId, - xmlElement: xmlElement - }; - _clientApiHandler.updateWhiteboardElement(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function clearWhiteboard(callId, userId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId, - userId: userId || undefined - }; - _clientApiHandler.clearWhiteboard(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function getWhiteboard(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId - }; - _clientApiHandler.getWhiteboard(data, function (err, whiteboard) { - if (apiError(err, reject)) { return; } - whiteboard.callId = whiteboard.rtcSessionId; - delete whiteboard.rtcSessionId; - resolve(whiteboard); - }); - }); - } - - function setWhiteboardBackground(callId, file) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - if (!file) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'file is required')); - return; - } - - if (typeof file === 'object') { - // Must scope FileUpload with circuit to allow node SDK to inject it's implementation - var fileUpload = new circuit.FileUpload(_config); - - // Upload image, but don't create thumbnail - fileUpload.uploadFiles([file], _self.domain, null, null, true) - .then(function (results) { - if (!results.length) { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'File upload error')); - return; - } - var data = { - rtcSessionId: callId, - url: results[0].url - }; - _clientApiHandler.setWhiteboardBackground(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }) - .catch(function (err) { - logger.error('[SDK]: File upload error. ', err); - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, err)); - }); - } else if (typeof file === 'string') { - var data = { - rtcSessionId: callId, - url: file - }; - _clientApiHandler.setWhiteboardBackground(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - } else { - reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'file must be a File object or a string')); - } - }); - } - - function clearWhiteboardBackground(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId - }; - _clientApiHandler.clearWhiteboardBackground(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function toggleWhiteboardOverlay(callId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId - }; - _clientApiHandler.toggleWhiteboardOverlay(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function undoWhiteboard(callId, steps, userId) { - return new Promise(function (resolve, reject) { - if (!callId) { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'callId is required')); - return; - } - var data = { - rtcSessionId: callId, - steps: steps, - userId: userId || undefined - }; - _clientApiHandler.undoWhiteboard(data, function (err) { - if (apiError(err, reject)) { return; } - resolve(); - }); - }); - } - - function sendAppMessageToUser(type, userId, content) { - return new Promise(function (resolve, reject) { - if (!type || typeof type !== 'string') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'type [string] is required')); - return; - } - if (!userId || typeof userId !== 'string') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'userId [string] is required')); - return; - } - if (!content || typeof content !== 'object') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'content [object] is required')); - return; - } - var data = { - content: { - type: type, - message: content - }, - destUserId: userId - }; - - _userToUserHandler.sendSdkRequest(data, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - }); - } - - function sendAppMessageToClient(type, destClientId, content) { - return new Promise(function (resolve, reject) { - if (!type || typeof type !== 'string') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'type [string] is required')); - return; - } - if (!destClientId || typeof destClientId !== 'string') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'destClientId [string] is required')); - return; - } - if (!content || typeof content !== 'object') { - reject(new Circuit.Error(Constants.ReturnCode.MISSING_REQUIRED_PARAMETER, 'content [object] is required')); - return; - } - var data = { - content: { - type: type, - message: content - }, - destUserId: _self.loggedOnUser.userId, - destClientId: destClientId - }; - - _userToUserHandler.sendSdkRequest(data, function (err) { - err ? reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, err)) : resolve(); - }); - }); - } - - /*********************************************************************************************/ - // Publicize functions - - // Constants and helper function to build the random avatars - var DEFAULT_AVATAR = '/content/images/icon-general-default-avatar'; - var DEFAULT_OPEN_CONV_AVATAR = '/content/images/icon-general-openconvo-avatar'; - var DEFAULT_LARGE_CONV_AVATAR = '/content/images/icon-general-large-conference'; - var DEFAULT_GROUP_CONV_AVATAR = '/content/images/icon-general-emptyconvo-avatar'; - var COLORS = ['-blue', '-green', '-orange', '-yellow']; - - function getColor(convId) { - var sum = 0; - if (convId) { - for (var idx = 0; idx < convId.length; idx++) { - sum += convId.charCodeAt(idx); - } - } - return COLORS[sum % COLORS.length]; - } - - function applyInjector(injector, object) { - if (typeof injector === 'function') { - try { - var res = injector(object); - if (res && (typeof res === 'object')) { - if (typeof res.then === 'function') { - // Looks like the injector returned a promise - return res; - } - // If it is not a promise, it must be the extended object - return Promise.resolve(res); - } - } catch (e) { - logger.error('[SDK]: Error applying injector. ', e); - } - } - return Promise.resolve(object); - } - - var PUBLIC_CALL_FIELDS = [ - 'activeMediaType', - 'callId', - 'convId', - 'convType', - 'direction', - 'isMeetingPointInvited', - 'isRemote', - 'isTelephonyCall', - 'isTestCall', - 'locallyMuted', - 'localMediaType', - 'localVideoUrl', - 'mediaType', - 'participants', - 'remoteAudioDisabled', - 'remoteAudioUrl', - 'remotelyMuted', - 'remoteVideoDisabled', - 'recording', - 'ringAllAllowed', - 'ringAllMaxNumber', - 'whiteboardEnabled' - ]; - - function publicizeCall(call) { - if (!call) { - return Promise.resolve(); - } - - var sdkCall = {}; - PUBLIC_CALL_FIELDS.forEach(function (field) { - sdkCall[field] = Utils.shallowCopy(call[field]); - }); - - // Now copy the relevant state information - sdkCall.state = call.state.name; - sdkCall.isEstablished = call.state.established; - - return applyInjector(Circuit.Injectors.callInjector, sdkCall); - } - - function publicizeUser(user) { - if (!user) { - return Promise.resolve(); - } - // Add avatar and avatarLarge attributes - if (user.smallImageUri && user.largeImageUri) { - user.avatar = fullDomain() + 'fileapi/' + user.userId + '_s.jpg?fileid=' + user.smallImageUri; - user.avatarLarge = fullDomain() + 'fileapi/' + user.userId + '_b.jpg?fileid=' + user.largeImageUri; - user.hasAvatar = true; - } else { - // User does not have a profile picture. Let's assign a random one. - var color = getColor(user.userId); - user.avatar = 'https://' + _self.domain + DEFAULT_AVATAR + color + '.png'; - user.avatarLarge = 'https://' + _self.domain + DEFAULT_AVATAR + color + '-XL.png'; - } - - // Remove user attributes not exposed to SDK - delete user.smallImageUri; - delete user.largeImageUri; - - // Remove deprecated User attributes - delete user.tenantId; - delete user.roles; - delete user.accountTemplateType; - delete user.associatedGTCUserId; - delete user.reroutingPhoneNumber; - delete user.assignedPhoneNumbers; - - // hasPermission is required in CircuitCallControlSvc - // JSDoc in protoDoc.js - user.hasPermission = function (permission) { - if (!this.accounts || !permission) { - return false; - } - var activeAccount = this.accounts[0]; - var permissions = activeAccount && activeAccount.permissions; - - return !!permissions && permissions.some(function (permissionObject) { - return permissionObject.systemPermission === permission; - }); - }; - - return applyInjector(Circuit.Injectors.userInjector, user); - } - - function publicizeItem(item) { - if (!item) { - return Promise.resolve(); - } - switch (item.type) { - case Constants.ConversationItemType.TEXT: - if (item.text.state === Constants.TextItemState.DELETED) { - // Set item.text.content to empty string - item.text.content = ''; - } else { - item.attachments = item.attachments || []; - item.externalAttachments = item.externalAttachments || []; - - // Convert the Circuit emoticon encoding to standard based on config - if (_config.emoticonMode === 'standard') { - item.text.content = Utils.standardizeEmoticon(item.text.content); - } - - // Set the private data field on the base object (if there is any) - PrivateData.retrievePrivateDataFromContent(item); - - // Normalize the mentioned user spans to only show the displayName - if (_config.removeMentionHtml) { - item.text.content = Utils.removeMentionHtml(item.text.content); - } - } - break; - - case Constants.ConversationItemType.SYSTEM: - if (item.system.affectedParticipants) { - // Change participants array to be an array of userID strings to match REST API - item.system.affectedParticipants = item.system.affectedParticipants.map(function (p) { - return p.userId; - }); - } - break; - } - - // Build url for attachment download - item.attachments && item.attachments.forEach(function (attachment) { - attachment.url = fullDomain() + 'fileapi/' + encodeURIComponent(attachment.fileName) + '?fileid=' + attachment.fileId + '&itemid=' + attachment.itemId; - }); - - return applyInjector(Circuit.Injectors.itemInjector, item); - } - - function publicizeTopLevelItem(conv) { - return new Promise(function (resolve) { - if (!conv || !conv.topLevelItem) { - resolve(conv); - return; - } - logger.debug('[SDK]: Publicize the topLevelItem. convId = ', conv.convId); - publicizeItem(conv.topLevelItem).then(function () { - logger.debug('[SDK]: Publicized topLevelItem. convId = ', conv.convId); - resolve(conv); - }).catch(function (err) { - logger.error('[SDK]: Failed to publicize topLevelItem. ', err); - resolve(conv); - }); - }); - } - - function publicizeThread(thread) { - return new Promise(function (resolve) { - if (!thread || !thread.parentItem) { - resolve(thread); - return; - } - logger.debug('[SDK]: Publicize the thread. itemId = ', thread.parentItem.intemId); - - thread.comments = thread.comments || []; - var items = thread.comments.slice(0); - items.unshift(thread.parentItem); - Promise.all(items.map(publicizeItem)) - .then(function () { - resolve(thread); - }) - .catch(function (err) { - logger.error('[SDK]: Failed to publicize thread. ', err); - resolve(thread); - }); - }); - } - - function publicizeConversation(conv) { - if (!conv) { - return Promise.resolve(); - } - logger.debug('[SDK]: Publicize conversation. convId = ', conv.convId); - - // Change participants array to be an array of userID strings - // to match REST API - conv.participants = conv.participants.map(function (p) { - return p.userId; - }); - conv.moderators = conv.moderators && conv.moderators.map(function (p) { - return p.userId; - }); - - conv.isTelephonyConversation = conv.convId === _telephonyConvId; - conv.isSupportConversation = conv.convId === _supportConvId; - - if (conv.type !== Constants.ConversationType.DIRECT) { - // Add avatar and avatarLarge attributes - if (conv.conversationAvatar && conv.conversationAvatar.smallPictureId && conv.conversationAvatar.largePictureId) { - conv.avatar = fullDomain() + 'avatar?convId=' + conv.convId + '&type=small&fileid=' + conv.conversationAvatar.smallPictureId; - conv.avatarLarge = fullDomain() + 'avatar?convId=' + conv.convId + '&type=large&fileid=' + conv.conversationAvatar.largePictureId; - conv.hasConversationAvatar = true; - } else { - if (conv.type === Constants.ConversationType.OPEN) { - var color = conv.isDraft ? '-grey' : getColor(conv.convId); - conv.avatar = 'https://' + _self.domain + DEFAULT_OPEN_CONV_AVATAR + color + '.png'; - conv.avatarLarge = 'https://' + _self.domain + DEFAULT_OPEN_CONV_AVATAR + color + '-XL.png'; - } else if (conv.type === Constants.ConversationType.LARGE) { - conv.avatar = 'https://' + _self.domain + DEFAULT_LARGE_CONV_AVATAR + '.png'; - conv.avatarLarge = 'https://' + _self.domain + DEFAULT_LARGE_CONV_AVATAR + '-XL.png'; - } else { - conv.avatar = 'https://' + _self.domain + DEFAULT_GROUP_CONV_AVATAR + '.png'; - conv.avatarLarge = 'https://' + _self.domain + DEFAULT_GROUP_CONV_AVATAR + '-XL.png'; - } - } - // Transform OPEN to COMMUNITY - if (conv.type === Constants.ConversationType.OPEN) { - conv.type = ConversationType.COMMUNITY; - } - } - - var convPromise = applyInjector(Circuit.Injectors.conversationInjector, conv); - return convPromise.then(publicizeTopLevelItem); - } - - function publicizeConversationParticipant(participant) { - if (!participant) { - return Promise.resolve(); - } - // Add avatar and avatarLarge attributes - if (participant.smallImageUri && participant.largeImageUri) { - participant.avatar = fullDomain() + 'fileapi/' + participant.userId + '_s.jpg?fileid=' + participant.smallImageUri; - participant.avatarLarge = fullDomain() + 'fileapi/' + participant.userId + '_b.jpg?fileid=' + participant.largeImageUri; - participant.hasAvatar = true; - } else { - // User does not have a profile picture. Let's assign a random one. - var color = getColor(participant.userId); - participant.avatar = 'https://' + _self.domain + DEFAULT_AVATAR + color + '.png'; - participant.avatarLarge = 'https://' + _self.domain + DEFAULT_AVATAR + color + '-XL.png'; - } - - // Remove participat attributes not exposed to SDK - delete participant.smallImageUri; - delete participant.largeImageUri; - - return applyInjector(Circuit.Injectors.participantInjector, participant); - } - - - /*********************************************************************************************/ - // Event queue handling - - function addToEventQueue(evt) { - logger.debug('[SDK]: Enqueue ' + evt.type); - _eventQueue.push(evt); - } - - function processEventQueue() { - if (_processingQueue || !_eventQueue.length) { - return; - } - _processingQueue = true; - try { - var evt = _eventQueue.shift(); - logger.debug('[SDK]: Dequeue ' + evt.type + '. Events left in the queue: ' + _eventQueue.length); - - if (!_self.hasEventListener(evt.type)) { - logger.debug('[SDK]: There is no listener for this event'); - return; - } - - // Note: There is no guaranteed order in publicizing the objects in case an event - // contains multiple publicizable objects. - var promises = []; - promises.push(evt.user ? publicizeUser(evt.user) : undefined); - promises.push(evt.item ? publicizeItem(evt.item) : undefined); - promises.push(evt.conversation ? publicizeConversation(evt.conversation) : undefined); - promises.push(evt.call ? publicizeCall(evt.call) : undefined); - Promise.all(promises).then(function (resolvedAry) { - resolvedAry[0] && (evt.user = resolvedAry[0]); - resolvedAry[1] && (evt.item = resolvedAry[1]); - resolvedAry[2] && (evt.conversation = resolvedAry[2]); - resolvedAry[3] && (evt.call = resolvedAry[3]); - // Dispatch the event - logger.debug('[SDK]: Dispatch ', evt.type); - _self.dispatch(evt); - }); - } catch (ex) { - logger.error('[SDK]: Error processing event queue. ', ex); - } finally { - _processingQueue = false; - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Public Properties and Methods - /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Domain connected to. - * @property domain - * @type String - * @default circuitsandbox.net - */ - Object.defineProperty(_self, 'domain', { - get: function () { return _config.domain; }, - set: function (newValue) { _config.domain = newValue; }, - enumerable: true, - configurable: false - }); - - /** - * OAuth2 access token - * @property accessToken - * @type String - */ - Object.defineProperty(_self, 'accessToken', { - get: function () { return _accessToken; }, - set: function (newValue) { _accessToken = newValue; }, - enumerable: true, - configurable: true - }); - - /** - * OAuth2 token expiry - * @property expiresAt - * @type Number - * @readOnly - */ - Object.defineProperty(_self, 'expiresAt', { - get: function () { return _expiresAt; }, - enumerable: true, - configurable: true - }); - - /** - * Connection State - * @property connectionState - * @type ConnectionState - * @readOnly - */ - Object.defineProperty(_self, 'connectionState', { - get: function () { return _connectionState; }, - enumerable: true, - configurable: true - }); - - /** - * The original WebRTC PeerConnection getStats API for the active, local - * Audio/Video PeerConnection, returning RTCStatsReport. Note that Chrome and - * Firefox return a different RTCStatsReport object. Check the browser's - * @method getLastRtpStats. See https://www.chromestatus.com/feature/5665052275245056 - * and https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport. - * Calling this API more than once every 5 seconds will affect the performance. - * For a simpler, normalized API use the preferred client.getLastRtpStats API instead. - * @returns {Promise} A Promise containing RTCStatsReport object when successful - * @scope `CALLS` or `FULL` - * @example - * client.getAudioVideoStats() - * .then(stats => stats.forEach(console.log)) - * .catch(console.error); - */ - Object.defineProperty(_self, 'getAudioVideoStats', { - get: function () { - var call = _services.CallControlSvc.getActiveCall(); - if (!call) { - return function () { - return Promise.reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No call found')); - }; - } - - var pc = call.sessionCtrl.getCurrentPeerConnection(); - if (!pc || !pc.getStats) { - return function () { - return Promise.reject(new Circuit.Error(Constants.ReturnCode.SDK_ERROR, 'No PeerConnection present')); - }; - } - // Use the standards getStats API. For FF this is the rewritten origGetStats - return pc.origGetStats ? pc.origGetStats.bind(pc) : pc.getStats.bind(pc); - }, - enumerable: false, - configurable: false - }); - - /** - * User object of currently logged on user. - * Available after logon is finished which is not immediately after - * event connectionStateChanged/Connected. - * @property loggedOnUser - * @type User - * @readOnly - */ - - /** - * Set the OAuth2 configuration for the Circuit client instance. This data can also be provided as part of the configuration parameters in the constructor. - * @method setOauthConfig - * @param {Object} config Object literal containing the OAuth2 configuration parameters - * @param {String} [config.client_id] The OAuth2 client ID you obtain from the Developer Portal. Identifies the client that is making the request. - * @param {String} [config.client_secret] The OAuth2 client secret you obtain from the Developer Portal. Applicable for `client credentials` grant type used for node.js apps. - * @param {String} [config.scope] Comma-delimited set of permissions that the application requests. Values: ALL,READ_USER_PROFILE,WRITE_USER_PROFILE,READ_CONVERSATIONS,WRITE_CONVERSATIONS,READ_USER,CALLS - */ - _self.setOauthConfig = setOauthConfig; - - /** - * Logon to Circuit using OAuth2. - * <br>Not providing any parameters will popup the OAuth2 window unless recently logged in - * which case the SDK will have remembered the OAuth2 token. - * <br>If the app already has an OAuth2 access token (e.g. via using the REST API), then the - * token can be passed in as `accessToken`. - * <br>OAuth2 supported grant types by the SDK: - * <ul><li>Implicit Grant - usually used in client-side web apps running in browsers</li> - * <li>Client Credentials Grant - usually used for node.js bots</li> - * @method logon - * @param {Object} [options] Optional object with OAuth2 access token, or API token - * @param {String} [options.accessToken] OAuth2 access token - * @param {String} [options.prompt] If set to true, then when passing an accessToken without a valid session, then a logon popup is shown. Not applicable for node.js - * @param {String} [options.logonChecked] If set to true, then no OAuth popup is shown and instead an exception is thrown if authentication session or token is invalid. Only applicable for `implicit` grant. - * @param {String} [options.skipTokenValidation] If set to true, then the app is responsible to obtain and validate access token. Used for native apps like Ionic. - * @param {String} [options.username] Username (email) for Resource Owner Grant Type - * @param {String} [options.password] Password for Resource Owner Grant Type - * @param {String} [options.token] API token (deprecated) - * @return {Promise|User} A promise that returns the logged on user in case of success - * @example - * // OAuth2 logon - Implicit Grant or Client Credentials Grant - * // This implies the client_id (and client_secret for Client Credentials Grant) is provided - * // to the Circuit.client constructor. - * client.logon() - * .then(user => console.log(`Authenticated as ${user.displayName}`)) - * .catch(console.error); - * - * // OAuth2 accessToken logon. The SDK stores the accessToken in localStorage, so this API - * // should not be needed in regular use cases. - * client.logon({accessToken: 'f75ae5b43ab2499a9b9aba460915ef11', prompt: true}) - * .then(user => console.log(`Authenticated as ${user.displayName}`)) - * - * // OAuth2 Resource Owner Grant Type logon. This grant type should only be used if other flows - * // are not viable. Also, it should only be used if the application is trusted by the user - * // (e.g. it is owned by the service, or the user's desktop OS), so not in a browser. - * client.logon({username: 'bob@example.com', password: 'P@ssw0rd'}) - * .then(user => console.log(`Authenticated as ${user.displayName}`)) - * - * // API token logon (deprecated, replaced by OAuth2 Client Credentials Grant) - * client.logon({token: 'Ifa20af19c21d16a182256620a'}) - * .then(user => console.log(`Authenticated as ${user.displayName}`)) - */ - _self.logon = function (options) { - var msg, url; - if (options) { - if (typeof options !== 'object') { - msg = 'OAuth2 is the only supported authentication menthod. Basic Auth is not supported anymore.'; - logger.error(msg); - return Promise.reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Credentials mismatch. Details: ' + msg)); - } - if (_config.client_id && _config.client_secret) { - msg = 'Circuit.Client is created with client_id and client_secret (Client Credentials Grant), so no token should be passed to the logon API.'; - logger.error(msg); - return Promise.reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Credentials mismatch. Details: ' + msg)); - } - if (_config.client_id && options.token) { - msg = 'Circuit.Client is created with client_id, so no API token should be passed to the logon API.'; - logger.error(msg); - return Promise.reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Credentials mismatch. Details: ' + msg)); - } - if (!_config.client_id && !options.token) { - msg = 'Circuit.Client was not created with client_id, so API token should be passed to the logon API.'; - logger.error(msg); - return Promise.reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Credentials mismatch. Details: ' + msg)); - } - } else if (!_config.client_id) { - msg = 'Login with a session cookie has been disabled. OAuth2 or API token are the only supported authentication method.'; - logger.error(msg); - return Promise.reject(new Circuit.Error(Constants.ErrorCode.SDK_ERROR, 'Credentials mismatch. Details: ' + msg)); - } - - if (_config.client_id && _config.client_secret) { - // OAuth2 Client Credentials Grant (mainly used for Node.JS, but also supported for browsers) - return authenticateClientCredentials() - .then(sdkLogin) - .then(wsLogon); - } - - if (_config.client_id && options && options.username && options.password) { - // OAuth2 Resource Owner Password Credentials Grant. Do not use in a browser. - return authenticateResourceOwner(options.username, options.password) - .then(sdkLogin) - .then(wsLogon); - } - - if (_config.client_id && options && options.accessToken) { - // OAuth2 with Access Token - - if (_isNode || _isDotNet || _isWin32 || _isMobile || options.skipTokenValidation) { - // Node.js: Login with accessToken. App is responsible to validate token. - // dotnet: .NET SDK is responsible to validate token and present OAuth popup is needed. - // Win32: UWP JS app is responsible to validate token and present OAuth popup is needed. - // Mobile: iOS & Android is responsible to validate token and present OAuth popup is needed. - // skipTokenValidation: For native web apps (e.g. ionic) the app is responsible to provide a valid token. - return sdkLogin(options) - .then(wsLogon); - } - - // Browser: If 'options.prompt' is set, show auth popup if not authenticated - if (options.prompt) { - url = 'https://' + _config.domain + '/dist/oauthLoading.html'; - _self.oauthwin = open(url, 450, 500); - } - _accessToken = options.accessToken; - - return validateToken() - .then(function () { - if (options.prompt) { - return validateSession() - .then(function () { - _self.oauthwin.close(); - _self.oauthwin = null; - }) - .catch(oauthLogin) - .then(wsLogon); - } else { - return wsLogon(); - } - }) - .catch(function (err) { - _self.oauthwin && _self.oauthwin.close(); - _self.oauthwin = null; - return Promise.reject(err); - }); - } - - if (_config.client_id) { - // OAuth2 Implicit Grant. Browser only. - - // If logon was checked within 5sec then skip token and session validation as well as OAuth pre-popup - if (_self.logonChecked) { - return wsLogon(); - } - - // If options indicate login was checked, then only perform token and session validation - // and throw and exception on failure. Don't show OAuth pre-popup - if (options && options.logonChecked) { - return retrieveToken() // Retrieve token from localStorage - .then(validateToken) // Validate access token - .then(validateSession) // Check for valid session on server using cookie - .then(wsLogon); // Logon to the websocket - } - - // Open the OAuth popup to prevent the browser's popup blocker to block - // the OAuth popup. If token and session is valid, popup will close itself. - url = 'https://' + _config.domain + '/dist/oauthLoading.html'; - _self.oauthwin = open(url, 450, 500); - - return retrieveToken() // Retrieve token from localStorage - .then(validateToken) // Validate access token - .then(validateSession) // Check for valid session on server using cookie - .then(function () { - _self.oauthwin.close(); // Close OAuth popup - _self.oauthwin = null; - }) - .catch(oauthAuthorize) // No token, invalid token or no session -> show oauth popup - .then(wsLogon); // Logon to the websocket - } - - if (options && options.token) { - // Legacy API Token - return sdkLogin(options).then(wsLogon); - } - - return Promise.reject(new Circuit.Error(Constants.ErrorCode.MISSING_REQUIRED_PARAMETER, 'Missing credentials')); - }; - - /** - * Checks whether the logon will succeed, meaning a valid token is in localStorage - * and a valid authentication sesssion is active. This API is useful for auto-logon - * attempts on page load. The logon API needs to be called within 5 seconds of a - * successful logonCheck call. - * If the logonCheck is unsuccessful the app can show a login - * button. Due to browser security a page load cannot trigger a new popup window to - * be opened, hence this API. - * @method logonCheck - * @return {Promise} A promise without data - * @example - * client.logonCheck() - * .then(() => console.log('Ready to call logon even on a page load')) - * .catch(() => console.log('Present a login button')); - */ - _self.logonCheck = function () { - return retrieveToken() - .then(validateToken) - .then(validateSession) - .then(function () { - // Remember that logon check was successful for 5sec - // logon API will then only execute the wsLogon and - // won't pre-show a OAuth2 window. Usually used for - // auto logon on page loads. - _self.logonChecked = true; - _logonCheckTimer && window.clearTimeout(_logonCheckTimer); - _logonCheckTimer = window.setTimeout(function () { - _logonCheckTimer = null; - _self.logonChecked = false; - }, 5000); - }); - }; - - /** - * Change the password of the current user. Will popup password change page. Fires `passwordChanged` event. - * @method changePassword - * @returns {Promise} A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.changePassword() - * .then(() => console.log('Successfully changed password')); - */ - _self.changePassword = function () { - return oauthChangePassword(); - }; - - /** - * Log this client instance out. The 'force' parameter can be used to force a logout - * of the current session (e.g. another SDK app in another browser tab, or even the - * Circuit webclient). Logging out does not revoke the OAuth2 access token. - * @method logout - * @param {Boolean} [force] Setting force to true also terminates the Circuit connection of other browser tabs - * @returns {Promise} A promise without data - * @example - * client.logout(); - */ - _self.logout = logout; - - /** - * Revoke the OAuth2 access token. - * @method revokeToken - * @param {String} [token] If omitted, the internally used token is revoked. - * @returns {Promise} A promise without data - * @example - * client.revokeToken(); - */ - _self.revokeToken = revokeToken; - - /** - * Set the authentication cookie to be used for XMLHttpRequests and WebSockets. - * @method setCookie - * @param {String} cookie Set the cookie to be used. - * @returns {void} - * @example - * client.setCookie(myCookie); - */ - _self.setCookie = function (cookie) { - _config.cookie = cookie; - }; - - /** - * Get the authentication cookie. - * @method getCookie - * @returns {String} cookie - * @example - * let cookie = client.getCookie(); - */ - _self.getCookie = function () { - return _config.cookie; - }; - - /** - * Check if the client is already authenticated and has a valid session. - * @method isAuthenticated - * @returns Promise A promise without data - * @example - * client.isAuthenticated() - * .then(() => console.log('Client is authenticated')); - * .catch(err => console.error('Client is not authenticated', err)); - */ - _self.isAuthenticated = validateSession; - - /** - * Validates the OAuth2 accessToken. - * @method validateToken - * @param {String} [accessToken] If not provided, current accessToken of the client instance is validated. - * @returns Promise A promise without data - * @example - * client.validateToken() - * .then(token => console.log('accessToken is valid')) - * .catch(err => console.error('accessToken is not valid', err)); - */ - _self.validateToken = validateToken; - - /** - * Get the logged on user. - * @scope `READ_USER_PROFILE` - * @method getLoggedOnUser - * @return {Promise|User} A promise that returns the logged on user - * @scope `READ_USER_PROFILE` or `FULL` - * @example - * client.getLoggedOnUser() - * .then(user => console.log('Client is authenticated: ' + user.displayName)); - */ - _self.getLoggedOnUser = getLoggedOnUser; - - /** - * Get the special telephony conversation ID. The telephony conversation is a - * special conversation the user has in case the tenant is enabled for telephony. - * @method getTelephonyConversationId - * @return {String} Telephony Conversation ID - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getTelephonyConversationId() - * .then(convId => console.log('Telephony Conversation ID: ' + convId)); - */ - _self.getTelephonyConversationId = getTelephonyConversationId; - - /** - * Get the special support conversation ID. The support conversation is a - * special conversation the user can report problem with. - * @method getSupportConversationId - * @return {String} Support Conversation ID - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getSupportConversationId() - * .then(convId => console.log('Support Conversation ID: ' + convId)); - */ - _self.getSupportConversationId = getSupportConversationId; - - /** - * Get the users for this tenant/domain. This API requires tenant admin priviledges. - * @method getTenantUsers - * @param {Object} [options] Literal object with filter options - * @param {String} [options.pageSize] Page size. Default is 25. - * @param {String} [options.sorting] Sorting as defined in Constants.GetAccountsSorting. Default is `BY_FIRST_NAME` - * @return {Promise|User[]} A promise that returns an array of users - * @scope `FULL` and only by tenant admins - * @example - * client.getTenantUsers({pageSize: 10, sorting: Constants.GetAccountsSorting.BY_LAST_NAME}) - * .then(users => console.log('First batch of users', users)); - */ - _self.getTenantUsers = getTenantUsers; - - /** - * Subscribe to presence notifications of users. - * @method subscribePresence - * @param {String[]} userIds Array of user IDs - * @returns {Promise} A promise without data - * @scope `READ_USER` or `FULL` - * @example - * client.subscribePresence(['874c528c-1410-4362-b519-6e7d26a2edb2','451e05a7-4649-4887-bfd2-21e70ec47e57']) - * .then(() => console.log('Sucessfully subscribed')); - */ - _self.subscribePresence = subscribePresence; - - /** - * Unsubscribe to presence notifications of users. - * @method unsubscribePresence - * @param {String[]} userIds Array of user IDs - * @returns {Promise} A promise without data - * @scope `READ_USER` or `FULL` - * @example - * client.unsubscribePresence(['874c528c-1410-4362-b519-6e7d26a2edb2','451e05a7-4649-4887-bfd2-21e70ec47e57']) - * .then(() => console.log('Sucessfully unsubscribed')); - */ - _self.unsubscribePresence = unsubscribePresence; - - /** - * Renew the session token of the current user. This is the authentication session, not the OAuth token. - * @method renewSessionToken - * @returns {Promise} A promise without data - * @scope n/a - * @example - * client.renewSessionToken() - * .then(() => console.log('Session token renewed')); - */ - _self.renewSessionToken = renewSessionToken; - - /** - * Renew the OAuth2 access token of the current user. - * @method renewToken - * @returns {Promise|Token} A promise that returns the token - * @scope n/a - * @example - * client.renewToken() - * .then(token => console.log('New access token is: ' + token)); - */ - _self.renewToken = renewToken; - - /** - * Retrieve user by its ID. - * @method getUserById - * @param {String} userId User ID - * @returns {Promise|User} A promise that returns the user - * @scope `READ_USER` or `FULL` - * @example - * client.getUserById('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(user => console.log('User is:', user)); - */ - _self.getUserById = getUserById; - - /** - * Retrieve users by their ID. - * @method getUsersById - * @param {String[]} userIds Array of User IDs - * @param {Boolean} [limited] If true, a limited user object is retrurned with the most important attributes. Default is false. - * @returns {Promise|User[]} A promise that returns the array of users - * @scope `READ_USER` or `FULL` - * @example - * client.getUsersById(['d353db50-b835-483e-9fce-2b157b2253d3']) - * .then(users => console.log('Users are:', users)); - */ - _self.getUsersById = getUsersById; - - /** - * Retrieve a user by its Circuit email. - * @method getUserByEmail - * @param {String} email Circuit email address - * @returns {Promise|User} A promise that returns the user - * @scope `READ_USER` or `FULL` - * @example - * client.getUserByEmail('bob@example.com') - * .then(user => console.log('User: ', user)); - */ - _self.getUserByEmail = getUserByEmail; - - /** - * Retrieve users by their Circuit email address. - * @method getUsersByEmail - * @param {String[]} emails Circuit email addresses - * @returns {Promise|User[]} A promise that returns the array of users - * @scope `READ_USER` or `FULL` - * @example - * client.getUsersByEmail(['bob@unify.com','alice@unify.com']) - * .then(users => console.log('User count: ', users.length)); - */ - _self.getUsersByEmail = getUsersByEmail; - - /** - * Update the logged on user's own user object. - * @method updateUser - * @param {Object} user Object literal containing the user attributes to update - * @param {String} user.userId UserId - * @param {String} [user.firstName] First name - * @param {String} [user.lastName] Last name - * @param {PhoneNumber[]} [user.phoneNumber] Phone numbers - * @param {EmailAddress[]} [user.emailAddresses] Email addresses - * @param {UserLocale} [user.userLocale] User Locale - * @param {String} [user.jobTitle] Job title - * @param {String} [user.company] Company - * @returns {Promise} A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.updateUser({userId: '72ee640f-9ac3-4275-b9ec-46d08e499c5a', firstName: 'Bob'}) - * .then(() => console.log('Successfully updated user')); - */ - _self.updateUser = updateUser; - - /** - * Get the presence for a list of user IDs. - * @method getPresence - * @param {String} userIds List of user IDs - * @param {Boolean} [full] If true, detailed presence is retrurned which also includes long/lat, timezone, etc - * @returns {Promise|Presence} A promise returning an array Presence objects - * @scope `READ_USER` or `FULL` - * @example - * client.getPresence(['72ee640f-9ac3-4275-b9ec-46d08e499c5a']) - * .then(presenceList => console.log('Presence objects for requested users: ', presenceList)); - */ - _self.getPresence = getPresence; - - /** - * Set the presence of the logged on user. - * @method setPresence - * @param {Presence} presence Presence object - * @returns {Promise} A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.setPresence({state: Circuit.Enums.PresenceState.AVAILABLE}) - * .then(() => console.log('Presence updated')); - */ - _self.setPresence = setPresence; - - /** - * Set the status message of the logged on user. Fires `userPresenceChanged` event - * to users on other logged on clients and to all other users that subscribe to this user's presence. - * @method setStatusMessage - * @param {String} statusMessage Status message. Set to empty string to clear status message. - * @returns Promise A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.setStatusMessage('At the beach enjoying life') - * .then(() => console.log('Status message set')); - */ - _self.setStatusMessage = setStatusMessage; - - /** - * Get the status message of the logged on user. - * @method getStatusMessage - * @returns Promise A promise containing the status message - * @scope `READ_USER_PROFILE` or `FULL` - * @example - * client.getStatusMessage() - * .then(statusMessage => console.log('Status message is: ', statusMessage)); - */ - _self.getStatusMessage = getStatusMessage; - - /** - * Save user settings of the logged on user. - * @method setUserSettings - * @param {UserSettings} userSettings User settings. Only attributes present are updated. - * @returns Promise A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.setUserSettings({presenceOptOut: true}) - * .then(() => console.log('presence opt out setting updated')); - */ - _self.setUserSettings = setUserSettings; - - /** - * Get all user settings of the logged on user. - * @method getUserSettings - * @returns Promise A promise returning the UserSettings object on success - * @scope `READ_USER_PROFILE` or `FULL` - * @example - * client.getUserSettings() - * .then(userSettings => console.log('userSettings are: ', userSettings)); - */ - _self.getUserSettings = getUserSettings; - - /** - * Upload a custom voicemail greeting (.wav file). This API only uploads the recording. - * Use API `setUserSettings` (attribute 'enableCustomVoicemailGreeting') to switch from - * detault greeting to uploaded custom greeting. - * The custom voicemail greeting can be downloaded via getUserSettings attribute `voicemailCustomGreetingUri` - * @method uploadCustomVoicemailGreeting - * @param {File} file Custom voicemail greeting as wav file. - * @returns Promise A promise without data - * @scope `WRITE_USER_PROFILE` or `FULL` - * @example - * client.uploadCustomVoicemailGreeting(file) - * .then(() => console.log('greeting uploaded')); - */ - _self.uploadCustomVoicemailGreeting = uploadCustomVoicemailGreeting; - - /** - * Retrieve devices the user is logged in with. - * @method getDevices - * @returns {Promise|Device[]} A promise that returns the array of devceis - * @scope `READ_USER_PROFILE` or `FULL` - * @example - * client.getDevices() - * .then(devices => console.log('Device count: ', devices.length)); - */ - _self.getDevices = getDevices; - - /** - * Retrieve conversations. - * @method getConversations - * @param {Object} [options] Object literal containing options - * @param {SearchDirection} [options.direction] Defines the direction of the search, i.e. get conversation BEFORE or AFTER a timestamp. If no direction is set but a timestamp the direction is set to BEFORE. - * @param {Number} [options.timestamp] Timestamp used in conjunction with the direction parameter. Default is no timestamp, causing most recent conversations to be returned. - * @param {Number} [options.numberOfConversations] Maximum number of conversations to retrieve. Default is 25. - * @param {Number} [options.numberOfParticipants] Maximum number of participants to return in the participants array. Default is 8. - * @returns {Promise|Conversations[]} A promise returning an array of Conversations. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversations({numberOfConversations: 10}) - * .then(conversations => console.log(`Returned ${conversations.length} conversations`)); - */ - _self.getConversations = getConversations; - - /** - * Retrieve all marked (muted and favorited) conversation IDs. - * @method getMarkedConversations - * @returns {Promise|Object} A promise returning an object with a list of favorited conversation IDs and a list of muted conversation IDs. - * @scope `READ_CONVERSATIONS` or `FULL` - * @deprecated - * @example - * client.getMarkedConversations() - * .then(res => console.log(`Muted: ${res.mutedConvIds.length}, Favs: ${res.favoriteConvIds.length}`)); - */ - _self.getMarkedConversations = getMarkedConversations; - - /** - * Retrieve favorited conversation IDs. - * @method getFavoriteConversationIds - * @returns {Promise|Object} A promise returning an object with a list of favorited conversation IDs. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getFavoriteConversationIds() - * .then(favoriteConvIds => console.log(`Favs: ${favoriteConvIds.length}`)); - */ - _self.getFavoriteConversationIds = getFavoriteConversationIds; - - /** - * Add a conversation to the favorite list. Succeeds if conversation is already favorited. - * @method favoriteConversation - * @param {String} convId Conversation ID - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.favoriteConversation('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(() => console.log('Conversation added to favorites')); - */ - _self.favoriteConversation = favoriteConversation; - - /** - * Remove a conversation to the favorite list. Succeeds if conversation is already unfavorited. - * @method unfavoriteConversation - * @param {String} convId Conversation ID - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.unfavoriteConversation('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(() => console.log('Conversation removed from favorites')); - */ - _self.unfavoriteConversation = unfavoriteConversation; - - /** - * Archive a conversation. Succeeds if conversation is already archived. - * @method archiveConversation - * @param {String} convId Conversation ID - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.archiveConversation('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(() => console.log('Conversation archived')); - */ - _self.archiveConversation = archiveConversation; - - /** - * Unarchive a conversation. Succeeds if conversation is already unarchived. - * @method unarchiveConversation - * @param {String} convId Conversation ID - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.unarchiveConversation('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(() => console.log('Conversation unarchived')); - */ - _self.unarchiveConversation = unarchiveConversation; - - /** - * Retrieve all labels. - * @method getAllLabels - * @returns {Promise|Object} A promise returning a list of label objects. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getAllLabels() - * .then(labels => console.log(`Found: ${labels.length} labels`)); - */ - _self.getAllLabels = getAllLabels; - - /** - * Retrieve conversations or conversation IDs by a filter. A filter contains conditions specifying one or more labels or archived/muted indication. - * @method getConversationsByFilter - * @param {Object} [options] Object literal containing filter parameters - * @param {FilterConnector} [options.filterConnector] Object defining the filter criteria. Required. - * @param {Number} [options.number] Maximum number of conversations to retrieve. Default is 25. - * @param {Number} [options.timestamp] Conversations created before this timestamp are returned. Used for paging. - * @param {Number} [options.numberOfParticipants] Maximum number of participants to return in the participants array. Default is 8. - * @param {RetrieveAction} [options.retrieveAction] Influences the data returned. Conversations (with paging support) or all Conversation IDs. Defaults to `CONVERSATIONS`. - * @returns {Promise} A promise returning a list of conversations, or conversation IDs. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationsByFilter({ - * filterConnector: { - * conditions: [{ - * filterTarget: 'LABEL_ID', - * expectedValue: ['ARCHIVED'] - * }] - * }, - * number: 25, - * retrieveAction: Circuit.Enums.RetrieveAction.CONVERSATIONS - * }) - * .then(convs => console.log(`Found: ${convs.length} archived conversations`)); - */ - _self.getConversationsByFilter = getConversationsByFilter; - - /** - * Retrieve conversations with a specified label - * @method getConversationsByLabel - * @param {String} labelId Label ID - * @param {Object} [options] Object literal containing paging and other options - * @param {Number} [options.number] Maximum number of conversations to retrieve. Default is 25. - * @param {Number} [options.timestamp] Conversations created before this timestamp are returned. Used for paging. - * @param {Number} [options.numberOfParticipants] Maximum number of participants to return in the participants array. Default is 8. - * @returns {Promise} A promise returning a list of conversations with that label - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationsByLabel('Project X', {numberOfParticipants: 3}) - * .then(convs => console.log(`Found: ${convs.length} conversations`)); - */ - _self.getConversationsByLabel = getConversationsByLabel; - - /** - * Retrieve archived conversations - * @method getArchivedConversations - * @param {Object} [options] Object literal containing paging and other options - * @param {Number} [options.number] Maximum number of conversations to retrieve. Default is 25. - * @param {Number} [options.timestamp] Conversations created before this timestamp are returned. Used for paging. - * @param {Number} [options.numberOfParticipants] Maximum number of participants to return in the participants array. Default is 8. - * @returns {Promise} A promise returning a list of archived conversations - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationsByLabel({number: 10}) - * .then(convs => console.log(`Found: ${convs.length} conversations`)); - */ - _self.getArchivedConversations = getArchivedConversations; - - /** - * Retrieve a single conversations by its ID. - * @method getConversationById - * @param {String} convId Conversation ID - * @returns {Promise|Conversation} A promise returning a conversation - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationById('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(conversation => console.log('Returned conversation: ', conversation)); - */ - _self.getConversationById = getConversationById; - - /** - * Retrieve conversations by their IDs. - * @method getConversationsByIds - * @param {Array} convIds Array of Conversation IDs - * @returns {Promise|Conversation[]} A promise returning an array of conversation objects - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationsByIds(['d353db50-b835-483e-9fce-2b157b2253d3', '9721646e-850d-426f-a300-bbbed97024aa']) - * .then(conversations => console.log(conversations)); - */ - _self.getConversationsByIds = getConversationsByIds; - - /** - * Retrieve conversation participants using optional search criteria. Resulting objects are participant objects, not user objects. Also returns a searchPointer for next page and a boolean whether there are more participants. - * @method getConversationParticipants - * @param {String} convId Conversation ID - * @param {Object} [options] Object literal containing options - * @param {ParticipantSearchCriteria} [options.searchCriterias] A list of search criterias that are used to filter the participants. Criterias are using and AND operation. - * @param {String} [options.searchPointer] Used for paging of results. Each results page returns a searchPointer that is to be used to fetch the next page. - * @param {Number} [options.pageSize] Number of participants per page. Maximum is 100. - * @param {Boolean} [options.includePresence] If set to true this will add the presence state of the user to the returned participant object. Default is false. - * @returns {Promise|ConversationParticipant[]} A promise returning an array of ConversationParticipant, a searchPointer and a hasMore boolean. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationParticipants('d353db50-b835-483e-9fce-2b157b2253d3', {pageSize: 50, includePresence: true}) - * .then(res => console.log(`Returned ${res.participants.length} participants`)); - */ - _self.getConversationParticipants = getConversationParticipants; - - /** - * Retrieve a single conversation item by its ID. - * @method getItemById - * @param {String} itemId Conversation Item ID - * @returns {Promise|Item} A promise returning an item - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getItemById('cc02d9ca-fb10-4a86-96b3-0198665525b8') - * .then(console.log); - */ - _self.getItemById = getItemById; - - /** - * Retrieve multiple conversation items. By default the most recent items are returned. - * @method getConversationItems - * @param {String} convId Conversation ID - * @param {Object} [options] Object literal containing options - * @param {Number} [options.modificationDate] Mutually exclusive with creationDate. Defaults to current timestamp. - * @param {Number} [options.creationDate] Mutually exclusive with modificationDate. Defaults to current timestamp. - * @param {SearchDirection} [options.direction] Whether to get items `BEFORE` or `AFTER` a certain timestamp. Default is `BEFORE`. - * @param {Number} [options.numberOfItems] Maximum number of conversation items to retrieve. Default 25. - * @returns {Promise|Item[]} A promise returning an array of Items - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationItems('cc02d9ca-fb10-4a86-96b3-0198665525b8', {direction: 'AFTER'}) - * .then(items => console.log(`Returned ${items.length} items`)); - */ - _self.getConversationItems = getConversationItems; - - /** - * Retrieve items of a conversation in a threaded structure before a specific timestamp. Allows specifying how many comments per thread to retrieve, and also how many unread messages of a thread to retrieve. - * @method getConversationFeed - * @param {String} convId Conversation ID - * @param {Object} [options] Object literal containing options - * @param {Number} [options.timestamp] Defines a date to compare with the last creation date of an item (direction is always `BEFORE`). If no timestamp is provided, the current time is used. - * @param {Number} [options.minTotalItems] Minimum number of comments to retrieve. Defaults to 25. - * @param {Number} [options.maxTotalUnread] Maximum number of unread items to retrieve. Defaults to 500. - * @param {Number} [options.commentsPerThread] Minimum number of comments per thread (up to 3) to retrieve. Defaults to 3. - * @param {Number} [options.maxUnreadPerThread] Maximum number of unread comments per thread to retrieve. Defaults to 50. - * @returns {Promise|Item[],hasOlderThreads) A promise returning an object with an array of threads and a boolean indicating whether there are more older threads. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationFeed('d353db50-b835-483e-9fce-2b157b2253d3') - * .then(res => console.log(`Returned ${res.threads.length} threads`)); - */ - _self.getConversationFeed = getConversationFeed; - - /** - * Retrieve conversation items of a thread, not including the parent item. - * @method getItemsByThread - * @param {String} convId Conversation ID - * @param {String} threadId Thread ID - * @param {Object} [options] Object literal containing options - * @param {Number} [options.modificationDate] Defines a date to compare with the last modification date of an item. Defaults to current timestamp. Mutually exclusive with creationDate. Usually used to retrieve the new items in a thread after last cached item. - * @param {Number} [options.creationDate] Defines a date to compare with the creation date of an item. Defaults to current timestamp. Mutually exclusive with modificationDate. Usually used to retrieve older items in a thread, e.g. fetch previous 25 items. - * @param {SearchDirection} [options.direction] Whether to get items `BEFORE` or `AFTER` a certain timestamp. - * @param {Number} [options.number] Maximum number of conversation items to retrieve. Default (-1) is to retrieve all items of a thread. - * @returns {Promise|Item[],hasMore) A promise returning an object with an array of items and a boolean indicating if there are more items. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getItemsByThread('d353db50-b835-483e-9fce-2b157b2253d3', 'e8b72e0e-d2d1-46da-9ed4-737875931440') - * .then(res => console.log(`Returned all ${res.items.length} items of this thread`)); - */ - _self.getItemsByThread = getItemsByThread; - - /** - * Send a new text message with optional attachments and URL previews. This API accepts a single string - * with the text message, or an object literal with the text item attributes.<br> - * RICH text supports Bold, Italic, Highlight, Bullet, Numbering, Emojii, Hyperlink and Mention. - * The formatting is:<br> - * <code> - * Bold: <b\>Bold</b\><br> - * Italic: <i\>Italic</i\><br> - * Highlight: <span class="rich-text-highlight"\>Highlight</span\><br> - * Bullets: <ul\><li\>Bullet</li\></ul\><br> - * Numbering: <ol\><li\>Numbering</li\></ol\><br> - * Emojii: Encoding at emoji-one site \😎<br> - * Hyperlink: <a href="https://github.com/circuit"\>Circuit on github</a\><br> - * Mention: <span class="mention" abbr="0e372ae0-2dff-4439-8f87-1b8a6562f80e"\>@Roger</span\>, can you come over?<br> - * </code> - * @method addTextItem - * @param {String} convId Conversation ID - * @param {Object|String} content Content for text message. Either an object literal, or a string. - * @param {String} [content.parentId] Item ID of parent post if this is a reply. - * @param {TextItemContentType} [content.contentType] Default 'RICH'. Whether the content is rich text (HTML) or plain text. - * @param {String} [content.subject] Subject/Title of message. Only supported for parent post (i.e. parentId omitted) - * @param {String} [content.content] Actual text content. - * @param {File[]} [content.attachments] Array of File objects objects. - * @returns {Promise|Item} A promise returning a the item added. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * var content = { - * subject: 'Message with attachment', - * content: 'Hello <b>World</b>', - * contentType: Circuit.Constants.TextItemContentType.RICH, - * attachments: [new File(['file 1'], 'testfile.txt', {type: 'text/plain', lastModified: Date.now()})] - * } - * client.addTextItem('e8b72e0e-d2d1-46da-9ed4-737875931440', content) - * .then(console.log); - * - * // Alternate API to send a text message. - * client.addTextItem('e8b72e0e-d2d1-46da-9ed4-737875931440', 'Hello World'); - * - * // Example content for mentioning a user. The abbr tad contains the userId of the mentioned user - * content = '<span class="mention" abbr="0e372ae0-2dff-4439-8f87-1b8a6562f80e">@Roger</span>, call me' - * - * // Example hyperlink - * content = 'Check out http://circuit.com or <a href="http://github.com/circuit">Circuit on github</a>' - * - * // Example content for an emojii - * content = 'Encoding at emojione.com 😎'; - * - */ - _self.addTextItem = addTextItem; - - /** - * Update an existing text message. See addTextItem for RICH text formatting support. - * @method updateTextItem - * @param {Object} content Content for text message to be updated. - * @param {String} content.itemId Conversation Item ID of item to update. - * @param {TextItemContentType} [content.contentType] Default 'RICH'. Whether the content is rich text (HTML) or plain text. - * @param {String} [content.subject] Subject/Title of message. Only supported for parent post (i.e. parentId omitted) - * @param {String} [content.content] Actual text content. - * @param {File[]} [content.attachments] Array of HTML File objects objects. - * @returns {Promise|Item} A promise returning the item. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * var content = { - * itemId: 'e8b72e0e-d2d1-46da-9ed4-7378759314488', - * subject: 'Updated subject', - * content: 'Updated text content', - * contentType: Circuit.Constants.TextItemContentType.RICH, - * attachments: [new File(['file 1'], 'testfile.txt', {type: 'text/plain', lastModified: Date.now()})] - * } - * client.updateTextItem(content) - * .then(console.log); - */ - _self.updateTextItem = updateTextItem; - - /** - * Get the direct conversation with a user by its user ID or email address. - * Note that adding a user to a direct conversation creates a new conversation - * with all 3 participants. The reason for that is because in Circuit direct - * conversation are pivate and stay private. - * @method getDirectConversationWithUser - * @param {String} query User ID or email address - * @param {Boolean} [createIfNotExists] Create conversation with user if not already existing. Default is false. - * @returns {Promise|Conversation} A promise returning a the conversation - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getDirectConversationWithUser('bob@unify.com') - * .then(console.log); - */ - _self.getDirectConversationWithUser = getDirectConversationWithUser; - - /** - * Add new participants to a group conversation or community. - * @method addParticipant - * @param {String} convId Conversation ID - * @param {String[]} userIds Single user ID or array of User IDs - * @param {Boolean} [addToCall] If set while a call is ongoing, added users are alerted - * @returns {Promise} A promise with the conversation, or the new conversation in case a new conversation was created - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.addParticipant('8fc29770-84ab-4ace-9e85-3269de707499', ['874c528c-1410-4362-b519-6e7d26a2edb2']) - * .then(() => console.log('Successfully added')); - */ - _self.addParticipant = addParticipant; - - /** - * Remove a participant from a group conversation or community. - * @method removeParticipant - * @param {String} convId Conversation ID - * @param {String[]} userIds Single user ID or array of User IDs - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.removeParticipant('8fc29770-84ab-4ace-9e85-3269de707499', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully removed')); - */ - _self.removeParticipant = removeParticipant; - - /** - * Get all flagged items. - * @method getFlaggedItems - * @returns {Promise|ConversationFlaggedItems[]} A promise with an array of objects containing the flagged info - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getFlaggedItems() - * .then(res => console.log(`${res.length} conversations with flagged items`)); - */ - _self.getFlaggedItems = getFlaggedItems; - - /** - * Flag an item. Flags are user specific. - * @method flagItem - * @param {String} convId Conversation ID - * @param {String} itemId Item ID of item to be flagged - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.flagItem('8fc29770-84ab-4ace-9e85-3269de707499', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully flagged')); - */ - _self.flagItem = setFlagItem; - - /** - * Clear the flag of an item. - * @method unflagItem - * @param {String} convId Conversation ID - * @param {String} itemId Item ID of item to be cleared - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.unflagItem('8fc29770-84ab-4ace-9e85-3269de707499', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully unflagged')); - */ - _self.unflagItem = clearFlagItem; - - /** - * Like an item. Likes will be seen by others. - * @method likeItem - * @param {String} itemId Item ID of item to be liked - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.likeItem('874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully liked item')); - */ - _self.likeItem = likeItem; - - /** - * Unlike an item. - * @method unlikeItem - * @param {String} itemId Item ID of item to be unliked - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.unlikeItem('874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully unliked item')); - */ - _self.unlikeItem = unlikeItem; - - /** - * Mark all items of the specified conversation, before a timestamp as read. - * @method markItemsAsRead - * @param {String} convId Conversation ID - * @param {Number} creationTime Items older than this timestamp are marked as read. Defaults to current time. - * @returns {Promise} A promise without data - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.markItemsAsRead('338d2002-ae68-495c-aa5c-1dff3fbc845a') - * .then(() => console.log('done')); - */ - _self.markItemsAsRead = markItemsAsRead; - - /** - * Moderate a conversation. - * @method moderateConversation - * @param {String} convId Conversation ID. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.moderateConversation('338d2002-ae68-495c-aa5c-1dff3fbc845a') - * .then(() => console.log('Successfully moderated conversation')); - */ - _self.moderateConversation = moderateConversation; - - /** - * Unmoderate a conversation. - * @method unmoderateConversation - * @param {String} convId Conversation ID. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.unmoderateConversation('338d2002-ae68-495c-aa5c-1dff3fbc845a') - * .then(() => console.log('Successfully unmoderated conversation')); - */ - _self.unmoderateConversation = unmoderateConversation; - - /** - * Grant moderator rights to participants. - * @method grantModeratorRights - * @param {String} convId Conversation ID. - * @param {String} userId User that gets moderator rights. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.grantModeratorRights('338d2002-ae68-495c-aa5c-1dff3fbc845a', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully granted moderation rights')); - */ - _self.grantModeratorRights = grantModeratorRights; - - /** - * Drop moderator rights to participants. - * @method dropModeratorRights - * @param {String} convId Conversation ID. - * @param {String} userId User that gets moderator rights removed. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.dropModeratorRights('338d2002-ae68-495c-aa5c-1dff3fbc845a', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully removed moderation rights')); - */ - _self.dropModeratorRights = dropModeratorRights; - - /** - * Enable guest access on a conversation. - * @method enableGuestAccess - * @param {String} convId Conversation ID. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.enableGuestAccess('338d2002-ae68-495c-aa5c-1dff3fbc845a') - * .then(() => console.log('Successfully enabled guest access')); - */ - _self.enableGuestAccess = function (convId) { - return updateGuestAccess(convId, false); - }; - - /** - * Disable guest access on a conversation. - * @method disableGuestAccess - * @param {String} convId Conversation ID. - * @returns {Promise} A promise without data. - * @scope `WRITE_CONVERSATION` or `FULL` - * @example - * client.disableGuestAccess('338d2002-ae68-495c-aa5c-1dff3fbc845a') - * .then(() => console.log('Successfully disabled guest access')); - */ - _self.disableGuestAccess = function (convId) { - return updateGuestAccess(convId, true); - }; - - /** - * Create a new direct conversation. - * <br>Requires one of the following scopes: `ALL`,`WRITE_CONVERSATIONS` - * @method createDirectConversation - * @param {String} participant User ID or email of the user to create a conversation with - * @returns {Promise|Object} A promise returning an object with the `conversation`, and an `alreadyExists` boolean - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.createDirectConversation('bob@example.com') - * .then(res => { - * let str = res.alreadyExists ? 'already exists' : 'is new'; - * console.log(`Conversation ${str}`, res.conversation); - * }); - */ - _self.createDirectConversation = createDirectConversation; - - /** - * Create a new group conversation. - * <br>Requires one of the following scopes: `ALL`,`WRITE_CONVERSATIONS` - * @method createGroupConversation - * @param {String[]} [participants] User ID's of the users to create a conversation with - * @param {String} [topic] Topic of conversation - * @returns {Promise|Conversation} A promise returning the created conversation - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.createGroupConversation(['176f65ce-7bb2-4a35-9030-45cd9b09b512', '4dccc4a2-e6f0-4e58-afb2-5a3f9a00ee05']) - * .then(conv => console.log(`Conversation created`, conv)); - */ - _self.createGroupConversation = createGroupConversation; - - /** - * Create a new conference bridge conversation. - * <br>Requires one of the following scopes: `ALL`,`WRITE_CONVERSATIONS` - * @method createConferenceBridge - * @param {String} topic Topic of conversation - * @returns {Promise|Conversation} A promise returning the created conversation - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.createConferenceBridge('Sales Call') - * .then(conv => console.log(`Conversation created`, conv)); - */ - _self.createConferenceBridge = createConferenceBridge; - - /** - * Create a new community. - * @method createCommunity - * @param {String[]} [participants] User ID's of the users to create a community with - * @param {String} [topic] Topic of community - * @param {String} [description] Description of the community - * @returns {Promise|Conversation} A promise returning the created community - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.createCommunity(null, 'Kiteboarding', 'Discuss best kiteboarding spots in Canada') - * .then(conv => console.log(`Community created`, conv)); - */ - _self.createCommunity = createCommunity; - - /** - * Find a call by its call ID. Call may be local or remote, active or non-active. - * @method findCall - * @param {String} callId callId of the call to find. - * @returns {Promise|Call} A promise returning the call. - * @scope `CALLS` or `FULL` - * @example - * client.findCall('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(call => console.log('Call found: ', call)); - */ - _self.findCall = function (callId) { - return Promise.resolve(publicizeCall(findCall(callId))); - }; - - /** - * Get all local and remote calls in progress. - * @method getCalls - * @returns {Promise|Call[]} A promise returning an array of local and remote calls in progress. - * @scope `CALLS` or `FULL` - * @example - * client.getCalls() - * .then(calls => console.log(`Calls in progress: ${calls.length}`)); - */ - _self.getCalls = getCalls; - - /** - * Get local active call. - * @method getActiveCall - * @returns {Promise|Call} A promise returning the local active call. - * @scope `CALLS` or `FULL` - * @example - * client.getActiveCall() - * .then(call => console.log('Active local call: ', call)); - */ - _self.getActiveCall = getActiveCall; - - /** - * Get remote active calls. - * @method getActiveRemoteCalls - * @returns {Promise|Call[]} A promise returning an array of remote active calls. - * @scope `CALLS` or `FULL` - * @example - * client.getActiveRemoteCalls() - * .then(calls => console.log(`Active remote calls: ${calls.length}`)); - */ - _self.getActiveRemoteCalls = getActiveRemoteCalls; - - /** - * Get calls in `Started` state, meaning the conference has started but the user has not yet joined. - * @method getStartedCalls - * @returns {Promise|Call[]} A promise returning an array of started calls. - * @scope `CALLS` or `FULL` - * @example - * client.getStartedCalls() - * .then(calls => console.log(`Started calls: ${calls.length}`)); - */ - _self.getStartedCalls = getStartedCalls; - - /** - * Start a conference call. - * @method startConference - * @param {String} conversation Conversation ID - * @param {Object} mediaType Object with boolean attributes: audio, video, desktop - * @returns {Promise|Call} A promise returning the created call. - * @scope `CALLS` or `FULL` - * @example - * client.startConference('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {audio: true, video: false}) - * .then(call => console.log('New call: ', call)); - */ - _self.startConference = startConference; - - /** - * Get the conversation details such as the bridge numbers and guest link. - * @method getConversationDetails - * @param {String} conversation Conversation ID - * @returns {Promise|ConversationDetails} A promise returning the conversation details. - * @scope `READ_CONVERSATIONS` or `FULL` - * @example - * client.getConversationDetails('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(convDetails => console.log('Conversation details: ', convDetails)); - */ - _self.getConversationDetails = getConversationDetails; - - /** - * Get the conversation details such as the bridge numbers and guest link. - * @method changeConversationPin - * @param {String} conversation Conversation ID - * @returns {Promise|ConversationDetails} A promise returning the conversation details with the new PIN and Guest link. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.changeConversationPin('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(convDetails => console.log('Pin changed. Conversation details: ', convDetails)); - */ - _self.changeConversationPin = changeConversationPin; - - - /********************************************************************/ - /* Call Control APIs */ - /********************************************************************/ - - /** - * Join a conference call from the current device, or optionally from another logged on device. - * @method joinConference - * @param {String} callId callId of the call to join. - * @param {Object} mediaType Object with boolean attributes: audio, video - * @param {String} [clientId] clientId of device where to join the call from - * @returns {Promise} A promise that is resolved when the call is joined. - * @scope `CALLS` or `FULL` - * @example - * client.joinConference('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {audio: true, video: false}) - * .then(() => console.log('Successfully joined the call')); - */ - _self.joinConference = joinConference; - - /** - * Pull a remote call to the local device. - * @method pullRemoteCall - * @param {String} callId callId of the call to join. - * @param {Boolean} [fallbackToAudio] flag to check whether to pull with audio only. - * @returns {Promise} A promise that is resolved when the call has been pulled. - * @scope `CALLS` or `FULL` - * @example - * client.pullRemoteCall('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(() => console.log('Successfully pulled the call')); - */ - _self.pullRemoteCall = pullRemoteCall; - - /** - * Leave a conference call. - * @method leaveConference - * @param {String} callId callId of the call to leave. - * @returns {Promise} A promise that is resolved when leaving the call is successful. - * @scope `CALLS` or `FULL` - * @example - * client.leaveConference('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(call => console.log('Successfully left the call')); - */ - _self.leaveConference = endCall; - - /** - * End a conference call. Disconnects all other participants as well. - * @method endConference - * @param {String} callId callId of the call to leave. - * @returns {Promise} A promise that is resolved when the conference has ended. - * @scope `CALLS` or `FULL` - * @example - * client.endConference('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(call => console.log('Successfully ended conference')); - */ - _self.endConference = endConference; - - /** - * Set the playback, recording, video and ringing devices to be used for calls. Can be called before or during a call. - * Use navigator.mediaDevices.enumerateDevices to get the available media devices. - * If multiple devices are set for a particular type (e.g. playback), then the first media device still available when starting the call is used. - * @method setMediaDevices - * @param {Object} devices Literal object containing the playback, recording, video and ringing device IDs. - * @param {String|String[]} [devices.playback] Playback (audio output) media device ID, or array of IDs. - * @param {String|String[]} [devices.recording] Recording (audio input) media device ID, or array of IDs. - * @param {String|String[]} [devices.video] Video (camera) media device ID, or array of IDs. - * @param {String|String[]} [devices.ringing] Ringing media device ID, or array of IDs. - * @returns {Promise} A promise returning no content. - * @scope `CALLS` or `FULL` - * @example - * client.setMediaDevices({ - * recording: 'c2c9e42e18ad18d8dd16942ab653c029d81411fbbb2c6b8fedf7e0e36739099d', - * video: ['617fb618597cef464e0bbe72c7978bc195435f9f04ac3d9d8961618d7d9fd07b', 'ed7e20a16115b672215db6479549abea11d41ba65de42b246d2575f678ac09be'] - * }) - * .then(() => console.log('New mic and camera device set')); - */ - _self.setMediaDevices = setMediaDevices; - - /** - * Start a direct call with a user by its email address. - * @method makeCall - * @param {String} user email or userID of the User to call - * @param {Object} mediaType Object with boolean attributes: audio, video, desktop - * @param {Boolean} [createIfNotExists] Create conversation with user if not already existing. Default is false. - * @returns {Promise|Call} A promise returning the created call. - * @scope `CALLS` or `FULL` (also requires `WRITE_CONVERSATIONS` if a conversation needs to be created) - * @example - * client.makeCall('bob@company.com', {audio: true, video: false}, true) - * .then(call => console.log('New call: ', call)); - */ - _self.makeCall = makeCall; - - /** - * End a remote call. - * @method endRemoteCall - * @param {String} callId callId of the call to end. - * @returns {Promise} A promise that is resolved when the call has been ended. - * @scope `CALLS` or `FULL` - * @example - * client.endRemoteCall('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(() => console.log('Successfully ended the remote call')); - */ - _self.endRemoteCall = endRemoteCall; - - /** - * Remove participant of a group call. - * @method dropParticipant - * @param {String} callId Call ID. - * @param {String} userId User ID of the participant to remove from the call. - * @returns {Promise} A promise that is resolved when the participant has been removed. - * @scope `CALLS` or `FULL` - * @example - * client.dropParticipant('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully dropped participant')); - */ - _self.dropParticipant = dropParticipant; - - /** - * Mute a remote participant. - * @method muteParticipant - * @param {String} callId Call ID. - * @param {String} userId User ID of the participant to mute. - * @returns {Promise} A promise that is resolved when the participant has been muted. - * @scope `CALLS` or `FULL` - * @example - * client.muteParticipant('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', '874c528c-1410-4362-b519-6e7d26a2edb2') - * .then(() => console.log('Successfully muted participant')); - */ - _self.muteParticipant = muteParticipant; - - /** - * Mute all participants except the current user. - * @method muteRtcSession - * @param {String} callId Call ID. - * @returns {Promise} A promise that is resolved when the participants have been muted. - * @scope `CALLS` or `FULL` - * @example - * client.muteRtcSession('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(() => console.log('Successfully muted participants')); - */ - _self.muteRtcSession = muteRtcSession; - - /** - * Returns the ATC devices that can initiate a call. - * @returns {string[]} The device list - * @scope `CALLS` or `FULL` - * @example - * var devices = client.getCallDevices(); - */ - _self.getCallDevices = function () { - return _services.CallControlSvc.getCallDevices().map(function (d) { return d.name; }); - }; - - /** - * Returns the ATC devices the call can be pushed to. - * @returns {string[]} The device list - * @scope `CALLS` or `FULL` - * @example - * var devices = client.getPushDevices(); - */ - _self.getPushDevices = function () { - return _services.CallControlSvc.getPushDevices().map(function (d) { return d.name; }); - }; - - /** - * Returns the ATC devices the call can be answered at. - * @param {string} callId Call ID - * @returns {string[]} The device list - * @scope `CALLS` or `FULL` - * @example - * var devices = client.getAnswerDevices('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a'); - */ - _self.getAnswerDevices = function (callId) { - return _services.CallControlSvc.getAnswerDevices(callId).map(function (d) { return d.name; }); - }; - - /** - * REturns a list of ATC calls (i.e. the CSTA calls). - * @returns {Call[]} List of call objects - * @scope `CALLS` or `FULL` - * @example - * var devices = client.getAtcCalls(); - */ - _self.getAtcCalls = _services.CallControlSvc.getAtcCalls; - - /** - * Start a telephony call from this client. - * @method dialNumber - * @param {String} number Dialable number. Must match Circuit.Utils.PHONE_PATTERN. - * @param {String} [name] Display name of number being dialed. - * @returns {Promise|Call} A promise returning the created call. - * @scope `CALLS` or `FULL` - * @example - * client.dialNumber('+15615551111', 'Bob Smith') - * .then(call => console.log('New telephony call: ', call)); - */ - _self.dialNumber = dialNumber; - - /** - * Get the telephony data such as the connection state and default caller ID. - * @method getTelephonyData - * @returns {Promise|Object} A promise returning the telephony data object. - * @scope `READ_USER_PROFILE` or `FULL` - * @example - * client.getTelephonyData() - * .then(console.log); - */ - _self.getTelephonyData = _services.SdkHelperSvc.getTelephonyData; - - /** - * Send DTMF digits in active local call. - * @method sendDigits - * @param {String} callId The call ID of active call - * @param {String} digits The digits to be sent - * @returns {Promise} A promise returning no content. - * @scope `CALLS` or `FULL` - * @example - * client.sendDigits('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', '5') - * .then(() => console.log('Digits sent')); - */ - _self.sendDigits = sendDigits; - - /** - * Start a direct call with a user initiated on one of your other devices. Also called Click-2-Call. - * If destClientId is provided, then the call is initiated on that device, and if that device is not - * logged on an error is returned. Use `getDevices` to find logged on devices. - * If no destClientId is provided the request is sent to all logged on devices with the first one initiating the call. - * Opens a new browser tab in case user is not logged on the another web-based client. - * @method sendClickToCallRequest - * @param {String} emailAddress email of the User to call - * @param {String} [mediaType] `audio` or `video`. Defaults to 'audio'. - * @param {String} [destClientId] ClientID of device to initiate the call. - * @param {String} [noLaunch] If set to false (or omited) default launch web browser on local device unless if no other clients are logged on. Mutually exclusive with `destClientId`. - * @returns {Promise} A promise that is resolved when the call is initiated on your other device. - * @scope `CALLS` or `FULL` - * @example - * client.sendClickToCallRequest('bob@company.com', 'video') - * .then(() => console.log('Success')); - */ - _self.sendClickToCallRequest = sendClickToCallRequest; - - /** - * Answer an incoming call on one of your other devices. - * If destClientId is provided, then the call is initiated on that device, and if that device is not - * logged on an error is returned. Use `getDevices` to find logged on devices. - * If no destClientId is provided the request is sent to all logged on devices with the first one answering the call. - * Does not launch a new browser tab in case user is not logged on with another web-based client. - * @method sendClickToAnswerRequest - * @param {String} callId Call ID of the call to answer - * @param {String} [mediaType] `audio` or `video`. Defaults to 'audio'. - * @param {String} [destClientId] ClientID of device to answer the call on. - * @returns {Promise} A promise that is resolved when the call is answered on your other device. - * @scope `CALLS` or `FULL` - * @example - * client.sendClickToAnswerRequest('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a') - * .then(() => console.log('Success')); - */ - _self.sendClickToAnswerRequest = sendClickToAnswerRequest; - - /** - * Mute/unmute the mic or speaker of your other web device (e.g. Circuit WebClient or another SDK client). - * Use `getDevices()` to obtain ClientIDs of user's other devices. - * @method muteDevice - * @param {String} callId The call ID of the call - * @param {String} [destClientId] ClientID of device to mute the call. Only web clients supported. If none provided, first webclient found is used. - * @param {Object} [option] Literal object with `mic` or `speaker` boolean attributes. Defaults to true for mic and speaker. - * @returns {Promise} A promise that is resolved when the call is muted on the device. - * @scope `CALLS` or `FULL` - * @example - * // Mute the mic and disable incoming audio for the specified call on device specified by its Client ID - * client.muteDevice('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', null, {mic: true, speaker: true}) - * .then(() => console.log('Success')); - */ - _self.muteDevice = muteDevice; - - /** - * Add a participant to a call via dial out. The participant does not have to be a member of the conversation. Dialing PSTN number is also supported. - * @method addParticipantToCall - * @param {String} callId callId of the call. - * @param {Object} to Object literal containing dial out information - * @param {String} [to.userId] userId of participant to add to call. For a PSTN call the userId may be omited. - * @param {String} [to.email] email of participant to add to call. Omited if userId is present. For a PSTN call the email may be omited. - * @param {String} [to.number] Phone number to dial. Not applicable to WebRTC dial-out. - * @param {String} [to.displayName] Display name of user to dial. Not applicable to WebRTC dial-out. - * @returns {Promise} A promise that is resolved when the user id dialed out. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * // Call via PSTN number - * client.addParticipantToCall('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {number: '+15615551234', displayName: 'Bob Jones'}) - * .then(() => console.log('Success')); - * - * // Or using the userID - * client.addParticipantToCall('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {userId: '56497aa7-5421-48e1-9069-2e8d3b12f778'}); - */ - _self.addParticipantToCall = addParticipantToCall; - - /** - * Add a participant to an RTC Session via dial out. Unlike addParticipantToCall this API does not rely on a local call to be present. - * The participant does not have to be a member of the conversation. Dialing PSTN number is also supported. - * @method addParticipantToRtcSession - * @param {String} callId callId of the call. - * @param {Object} to Object literal containing dial out information - * @param {String} [to.userId] userId of participant to add to call. For a PSTN call the userId may be omited. - * @param {String} [to.email] email of participant to add to call. Omited if userId is present. For a PSTN call the email may be omited. - * @param {String} [to.number] Phone number to dial. Not applicable to WebRTC dial-out. - * @param {String} [to.displayName] Display name of user to dial. Not applicable to WebRTC dial-out. - * @returns {Promise} A promise that is resolved when the user id dialed out. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.addParticipantToRtcSession('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {number: '+15615551234', displayName: 'Bob Jones'}) - * .then(() => console.log('Success')); - * - * client.addParticipantToRtcSession('b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', {userId: '56497aa7-5421-48e1-9069-2e8d3b12f778'}) - * .then(() => console.log('Success')); - */ - _self.addParticipantToRtcSession = addParticipantToRtcSession; - - /** - * Answer an incoming call received in a callIncoming event. - * @method answerCall - * @param {String} callId callId of the call to answer. - * @param {Object} mediaType Object with boolean attributes: audio, video, desktop - * @param {string} device Device to answer the call with. Use `getAnswerDevices` to list devices. By default call is answered via WebRTC. - * @returns {Promise} A promise that is resolved when the call is answered. - * @scope `CALLS` or `FULL` - * @example - * client.answerCall('8f365bf3-97ea-4d54-acc7-2c4801337521', {audio: true, video: false}) - * .then(() => console.log('The call has been answered')); - */ - _self.answerCall = answerCall; - - /** - * End a direct call or leave a group conference. - * @method endCall - * @param {String} callId callId of the call to end. - * @returns {Promise} A promise that is resolved when the call is ended. - * @scope `CALLS` or `FULL` - * @example - * client.endCall('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('The call has been ended')); - */ - _self.endCall = endCall; - - /** - * Toggle sending video on an existing call. - * @method toggleVideo - * @param {String} callId callId of the call to toggle video. - * @returns {Promise} A promise that is resolved when the toggle is complete. - * @scope `CALLS` or `FULL` - * @example - * client.toggleVideo('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully toggled')); - */ - _self.toggleVideo = toggleVideo; - - /** - * Enable/disable HD on a sending video stream. - * @method changeHDVideo - * @param {String} callId callId of the call. - * @param {Boolean} hdQuality true to enable streaming HD quality up to 1920x1080. - * @returns {Promise} A promise that is resolved when action is complete. - * @scope `CALLS` or `FULL` - * @example - * client.changeHDVideo('8f365bf3-97ea-4d54-acc7-2c4801337521', true) - * .then(() => console.log('Successful')); - */ - _self.changeHDVideo = changeHDVideo; - - /** - * Toggle the incoming (remote) audio stream on an existing call. Use - * `callStatus` event and `call.remoteAudioDisabled` to determine if - * remote audio is to be paused. - * @method toggleRemoteAudio - * @param {String} callId callId of the call. - * @returns {Promise} A promise that is resolved when the toggle is complete. - * @scope `CALLS` or `FULL` - * @example - * client.toggleRemoteAudio(callId).then(call => console.log(`Remote audio state: ${call.remoteAudioDisabled}`)); - * - * // remoteAudio is the audio element - * client.addEventListener('callStatus', evt => - * if (callId === evt.call.callId) { - * if (remoteAudio.src !== evt.call.remoteAudioUrl) { - * remoteAudio.src = evt.call.remoteAudioUrl; - * } - * if (evt.reason === 'remoteStreamUpdated') { - * evt.call.remoteAudioDisabled ? remoteAudio.pause() : remoteAudio.play(); - * } - * } - * ); - */ - _self.toggleRemoteAudio = toggleRemoteAudio; - - /** - * Toggle receiving video on an existing call. Useful for low bandwidth. - * @method toggleRemoteVideo - * @param {String} callId callId of the call to toggle video. - * @returns {Promise} A promise that is resolved when the toggle is complete. - * @scope `CALLS` or `FULL` - * @example - * client.toggleRemoteVideo('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully toggled')); - */ - _self.toggleRemoteVideo = toggleRemoteVideo; - - /** - * Toggle local screenshare on an existing call. Only Chrome supported at this time. - * Requires the generic Chrome Extension for Circuit screenshare available - * at https://github.com/yourcircuit/screenshare-chrome-extension. - * To check if screenshare is currently active check call.localMediaType and call.mediaType. - * @method toggleScreenShare - * @param {String} callId callId of the call to add/remove screen share. - * @returns {Promise} A promise that is resolved when the screen share has been added/removed. - * @scope `CALLS` or `FULL` - * @example - * client.toggleScreenShare('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully toggled')); - */ - _self.toggleScreenShare = toggleScreenShare; - - /** - * Set the media stream for screenshare to be transmitted in the active call. - * Useful for sending a MediaStream captured via the HTMLCanvasElement.captureStream API. - * API has been renamed from toggleMediaStream. - * See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream - * @method setScreenshareStream - * @param {String} callId callId of the call to add/remove a media stream. - * @param {MediaStream} stream Screenhare MediaStream to send. - * @returns {Promise} A promise that is resolved when the mediaStream has been set. - * @scope `CALLS` or `FULL` - * @example - * client.setScreenshareStream('8f365bf3-97ea-4d54-acc7-2c4801337521', stream) - * .then(() => console.log('Successfully set media stream')); - */ - _self.setScreenshareStream = setScreenshareStream; - _self.toggleMediaStream = setScreenshareStream; - - /** - * Get the local video stream (audio/video) - * @method getLocalAudioVideoStream - * @returns {Promise} A promise containing the MediaStream - * @scope `CALLS` or `FULL` - * @example - * client.getLocalAudioVideoStream() - * .then(stream => console.log(stream)); - */ - _self.getLocalAudioVideoStream = getLocalStream.bind(_self, _rtcSessionController.LOCAL_STREAMS.AUDIO_VIDEO); - - /** - * Get the local video stream for the screenshare. - * @method getLocalScreenshareStream - * @returns {Promise} A promise containing the MediaStream - * @scope `CALLS` or `FULL` - * @example - * client.getLocalScreenshareStream() - * .then(stream => console.log(stream)); - */ - _self.getLocalScreenshareStream = getLocalStream.bind(_self, _rtcSessionController.LOCAL_STREAMS.DESKTOP); - - /** - * Set the media stream for audio/video to be transmitted in the active call. - * @method setAudioVideoStream - * @param {String} callId callId of the call to add/remove a media stream. - * @param {MediaStream} stream Audio/Video MediaStream to send. - * @returns {Promise} A promise that is resolved when the mediaStream has been set. - * @scope `CALLS` or `FULL` - * @example - * client.setAudioVideoStream('8f365bf3-97ea-4d54-acc7-2c4801337521', stream) - * .then(() => console.log('Successfully set media stream')); - */ - _self.setAudioVideoStream = setAudioVideoStream; - - /** - * Start recording the active call. - * @method startRecording - * @param {String} callId callId of the call to start recording. Only local active calls - * can be recorded. - * @param {Boolean} [allowScreenshareRecording] If true screenshare is recorded if used in the call. Default: false - * @returns {Promise} A promise that is resolved when recording has started. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.startRecording('8f365bf3-97ea-4d54-acc7-2c4801337521', true) - * .then(() => console.log('Successfully started recording')); - */ - _self.startRecording = startRecording; - - /** - * Stop recording the active call. - * @method stopRecording - * @param {String} callId callId of the call to stop recording. - * @returns {Promise} A promise that is resolved when recording has started. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.stopRecording('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully stopped recording')); - */ - _self.stopRecording = stopRecording; - - /** - * Delete a recording. - * @method deleteRecording - * @param {String} Item ID of item to remove the recording from. - * @returns {Promise} A promise that is resolved when recording has been deleted. - * @scope `WRITE_CONVERSATIONS` or `FULL` - * @example - * client.deleteRecording('cc02d9ca-fb10-4a86-96b3-0198665525b8') - * .then(() => console.log('Successfully deleted recording')); - */ - _self.deleteRecording = deleteRecording; - - /** - * Mute an existing call. - * @method mute - * @param {String} callId callId of the call to mute. - * @returns {Promise} A promise that is resolved when the mute is complete. - * @scope `CALLS` or `FULL` - * @example - * client.mute('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully muted call')); - */ - _self.mute = mute; - - /** - * Unmute an existing call. - * @method unmute - * @param {String} callId callId of the call to unmute. - * @returns {Promise} A promise that is resolved when the unmute is complete. - * @scope `CALLS` or `FULL` - * @example - * client.unmute('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully unmuted call')); - */ - _self.unmute = unmute; - - /** - * Get the RTP call statistics of the last stats collection interval (every 5 sec by default) - * @method getLastRtpStats - * @param {String} callId call ID - * @returns {RtpStats[]} An array of RTP stats, one for each stream. Returns `null` is call is not present. - * @scope `CALLS` or `FULL` - * @example - * var stats = client.getLastRtpStats('8f365bf3-97ea-4d54-acc7-2c4801337521'); - * var audioVideoStat = stats.find(stat => stats.pcType === 'AUDIO/VIDEO') - */ - _self.getLastRtpStats = getLastRtpStats; - - /** - * Get the remote (receiving) media streams (audio, video and screenshare). - * For group calls there is one audio stream and 4 video streams. Not all streams may have audio/video tracks. - * For direct calls there is a single stream with audio and possibly video tracks. - * @method getRemoteStreams - * @param {String} callId call ID - * @returns {MediaStream[]} An array of MediaStream objects. Returns `null` is call is not present. - * @scope `CALLS` or `FULL` - * @example - * var audioStream = client.getRemoteStreams(callId).find(s => s.getAudioTracks().length > 0); - */ - _self.getRemoteStreams = getRemoteStreams; - - /** - * Enable the whiteboard feature on an active RTC call/conference. - * @method enableWhiteboard - * @param {String} callId Call ID of the call. - * @param {Viewbox} viewbox The viewbox of the SVG root element. This will allow the clients to - * transform their canvas into the used SVG coordinate system. - * @returns {Promise} A promise that is resolved when the feature is enabled. - * @scope `CALLS` or `FULL` - * @example - * client.enableWhiteboard('8f365bf3-97ea-4d54-acc7-2c4801337521', { width: 800, height: 400 }) - * .then(() => console.log('Successfully enabled whiteboarding for this call')); - */ - _self.enableWhiteboard = enableWhiteboard; - - /** - * Disable the whiteboard feature on an active RTC call/conference. - * @method disableWhiteboard - * @param {String} callId Call ID of the call. - * @returns {Promise} A promise that is resolved when the feature is disabled. - * @scope `CALLS` or `FULL` - * @example - * client.disableWhiteboard('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully disabled whiteboarding for this call')); - */ - _self.disableWhiteboard = disableWhiteboard; - - /** - * Add a whiteboard element (e.g. circle, line, freehand) to the whiteboard. - * @method addWhiteboardElement - * @param {String} callId Call ID of the call. - * @param {String} xmlElement SVG XML representation of the element. (Any JavaScript will be removed). - * @returns {Promise} A promise that is resolved when the drawing has been sent to the peers. - * @scope `CALLS` or `FULL` - * @example - * client.addWhiteboardElement('8f365bf3-97ea-4d54-acc7-2c4801337521', - *. '<rect style="fill: none; stroke: #ff0000; stroke-width: 0.02;" x="6.075" y="1.78203125" width="1.1" height="1.2"></rect>') - * .then(() => console.log('Successfully added whiteboarding element')); - */ - _self.addWhiteboardElement = addWhiteboardElement; - - /** - * Remove a whiteboard element. - * @method removeWhiteboardElement - * @param {String} callId Call ID of the call. - * @param {String} xmlId SDK generated ID of the SVG elements. - * @returns {Promise} A promise that is resolved when the drawing has been removed. - * @scope `CALLS` or `FULL` - * @example - * client.addWhiteboardElement('8f365bf3-97ea-4d54-acc7-2c4801337521', - *. '<rect style="fill: none; stroke: #ff0000; stroke-width: 0.02;" x="6.075" y="1.78203125" width="1.1" height="1.2"></rect>') - * .then(() => console.log('Successfully removed whiteboarding element')); - */ - _self.removeWhiteboardElement = removeWhiteboardElement; - - // Not exposing this publically for now - _self.updateWhiteboardElement = updateWhiteboardElement; - - /** - * Clear a whiteboard. - * @method clearWhiteboard - * @param {String} callId Call ID of the call. - * @param {String} [userId] If set only elements of the given user are removed. - * @returns {Promise} A promise that is resolved when the action has completed. - * @scope `CALLS` or `FULL` - * @example - * client.clearWhiteboard('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully cleared whiteboard')); - */ - _self.clearWhiteboard = clearWhiteboard; - - /** - * Get the complete whiteboard drawing. - * @method getWhiteboard - * @param {String} callId Call ID of the call. - * @returns {Promise|Whiteboard} A promise containing the whiteboard drawing. - * @scope `CALLS` or `FULL` - * @example - * client.getWhiteboard('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(board => console.log('Successfully retrieved the whiteboard')); - */ - _self.getWhiteboard = getWhiteboard; - - /** - * Set the background of the whiteboard. - * @method setWhiteboardBackground - * @param {String} callId Call ID of the call. - * @param {File} file File object for background image. - * @returns {Promise} A promise without data. - * @scope `CALLS` or `FULL` - * @example - * client.setWhiteboardBackground('8f365bf3-97ea-4d54-acc7-2c4801337521', file) - * .then(() => console.log('Successfully uploaded and set background')); - */ - _self.setWhiteboardBackground = setWhiteboardBackground; - - /** - * Clear the background of the whiteboard. - * @method clearWhiteboardBackground - * @param {String} callId Call ID of the call. - * @returns {Promise} A promise without data. - * @scope `CALLS` or `FULL` - * @example - * client.clearWhiteboardBackground('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully cleared the background')); - */ - _self.clearWhiteboardBackground = clearWhiteboardBackground; - - /** - * Toggle the overlay of the whiteboard. - * @method toggleWhiteboardOverlay - * @param {String} callId Call ID of the call. - * @returns {Promise} A promise without data. - * @scope `CALLS` or `FULL` - * @example - * client.toggleWhiteboardOverlay('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully toggled the overlay')); - */ - _self.toggleWhiteboardOverlay = toggleWhiteboardOverlay; - - /** - * Toggle the overlay of the whiteboard. - * @method undoWhiteboard - * @param {String} callId Call ID of the call. - * @param {Number} steps Number of steps to undo. - * @param {String} userId If the user ID is set only steps of this user are undone. - * @returns {Promise} A promise without data. - * @scope `CALLS` or `FULL` - * @example - * client.undoWhiteboard('8f365bf3-97ea-4d54-acc7-2c4801337521') - * .then(() => console.log('Successfully undone steps')); - */ - _self.undoWhiteboard = undoWhiteboard; - - /** - * Start an asynchronous user search returning a search ID. Search results are received with searchResult events. - * Searches are done with beginning characters of first and lastname. - * @method startUserSearch - * @param {String|Object} searchTerm string, or object literal containing a `query` attribute as search string - * @param {String} searchTerm.query The query string that is searched for. - * @param {Boolean} [searchTerm.reversePhoneNumberLookup] If true match term starting from the last digit of user's phone numbers. If false match search term anywhere in users phone numbers. - * @returns {Promise|String} A promise returning the search ID. Search ID is used to correlate the - * asynchronous events. - * @scope `READ_USER` or `FULL` - * @example - * client.startUserSearch('Ro') - * .then(searchId => console.log('Search started with ID: ' + searchId)); - */ - _self.startUserSearch = startUserSearch; - - /** - * Start an asynchronous basic search returning a search ID. Search results are received with searchResult events. - * Raises `basicSearchResults` and `searchStatus` events. - * @method startBasicSearch - * @param {String|Array} searchTerm Array of searchTerm objects, or a string for a simple ALL scope search search. - * @param {String} searchTerm.searchTerm The string that is searched for. - * @param {String} searchTerm.scope The scope to search in. - * @param {Number} [searchTerm.startTime] start date used for DATE scope. In ms since midnight on January 1, 1970 in UTC - * @param {Number} [searchTerm.endTime] end date used for DATE scope. In ms since midnight on January 1, 1970 in UTC - * @param {FilterConnector} [searchTerm.rootConnector] filter constraints used for FILTER scopes. See getConversationsByFilter for example. - * @returns {Promise|String} A promise returning the search ID. Search ID is used to correlate the - * asynchronous events. - * @scope `READ_USER` or `FULL` - * @example - * client.startBasicSearch('Kiteboarding') - * .then(searchId => console.log('Search started with ID: ' + searchId)); - * - * client.startBasicSearch([{ - * scope: Constants.SearchScope.CONVERSATIONS, - * searchTerm: 'Vacation' - * }, { - * scope: Constants.SearchScope.MEMBERS, - * searchTerm: 'Paul' - * }]) - * .then(searchId => console.log('Search started with ID: ' + searchId)); - * - * client.startBasicSearch([{ - * scope: Constants.SearchScope.DATE, - * startTime: new Date(new Date().setDate(new Date().getDate()-1)) // yesterday - * }]) - * .then(searchId => console.log('Search started with ID: ' + searchId)); - */ - _self.startBasicSearch = startBasicSearch; - - /** - * Send an application specific message to another user. Use `addUserMessageListener` to - * listen for user messages on the receiving side. To send a message to your own clients, - * use `sendAppMessageToClient`. - * Sending a reply is the responsibility of the application. - * @method sendAppMessageToUser - * @param {String} type The type of the message - * @param {String} userId The user ID of the user to send the message to - * @param {Object} content The content to send. Object must be serializable. - * @scope `FULL` - * @example - * client.sendAppMessageToUser('channel1', 'b3b97aa7-fe6c-48e1-9069-2e8d3b12f18a', { - * reqId: '1234', - * msgName: 'CHAT_REQ', - * text: 'Hello World' - * }) - * .then(() => console.log(`Message sent`)); - */ - _self.sendAppMessageToUser = sendAppMessageToUser; - - /** - * Send an application specific message to another client of the logged on user. - * Use `addClientMessageListener` to listen for user messages on the receiving side. - * To send a message to your other users, use `sendAppMessageToUser`. - * Sending a reply is the responsibility of the application. - * @method sendAppMessageToClient - * @param {String} type The type of the message - * @param {String} [destClientId] The client ID of the device to send the message to. If omited message is sent to all devices. - * @param {Object} content The content to send. Object must be serializable. - * @scope `FULL` - * @example - * client.sendAppMessageToClient('topic1', null, { - * messageType: 'REQUEST_ABC', - * data: {msg: 'Hello', id: '1234'} - * }); - */ - _self.sendAppMessageToClient = sendAppMessageToClient; - - /** - * Add listener for messages sent via `sendAppMessageToUser` or `sendAppMessageToClient`. - * @method addAppMessageListener - * @param {String} type Type - * @param {Function} cb Callback with message and routing information. - * @returns {void} - * @scope `FULL` - * @example - * client.addAppMessageListener('channel1', (msg, routing) => { - * console.log(`Message received`, msg); - * }); - */ - _self.addAppMessageListener = function (type, cb) { - _userToUserHandler.on('SDK.' + type, cb); - }; - - /** - * Remove listener for UserMessage events for a specific type. If the callback is not specified, - * all listeners for the given type are removed. If neither type nor callback is specified, all - * listeners are removed. - * @method removeAppMessageListener - * @param {String} [type] Type - * @param {Function} [cb] Callback to be removed. - * @scope `FULL` - * @example - * client.removeAppMessageListener('channel1', myHandler); - * client.removeAppMessageListener('channel1'); - * client.removeAppMessageListener(); - */ - _self.removeAppMessageListener = _userToUserHandler.off; - - - /** - * Fired when OAuth access token has been renewed. - * @event accessTokenRenewed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - /** - * Fired when renewal of OAuth access token has failed. - * @event renewAccessTokenFailed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.error Error object if token renew failed - */ - - - /** - * Fired when any attribute on the loggedOnUser changed - * @event loggedOnUserUpdated - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Item} event.user Updated loggedOnUser object - */ - _services.PubSubSvc.subscribe('/localUser/update', function () { - addToEventQueue({type: 'loggedOnUserUpdated', user: _self.loggedOnUser}); - }); - - /** - * Fired when a new conversation item is received. Note that the sender of an item will also receive this event. - * @event itemAdded - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Item} event.item Item being added - */ - _clientApiHandler.on('Conversation.ADD_ITEM', function (evt) { - addToEventQueue({type: 'itemAdded', item: evt.item}); - }); - - /** - * Fired when an existing conversation item is updated. - * @event itemUpdated - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Item} event.item Itembeing updated - */ - _clientApiHandler.on('Conversation.UPDATE_ITEM', function (evt) { - addToEventQueue({type: 'itemUpdated', item: evt.item}); - }); - - /** - * Fired when a new conversation is created for this user. This can be a brand new conversation, or being added to a conversation. - * @event conversationCreated - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Conversation} event.conversation Conversation being created - */ - _clientApiHandler.on('Conversation.CREATE', function (evt) { - addToEventQueue({type: 'conversationCreated', conversation: evt.conversation}); - }); - - /** - * Fired when an existing conversation is updated. - * @event conversationUpdated - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Conversation} event.conversation Conversation being updated - */ - _clientApiHandler.on('Conversation.UPDATE', function (evt) { - addToEventQueue({type: 'conversationUpdated', conversation: evt.conversation}); - }); - - /** - * Fired when a conversation has been archived for the logged on user - * @event conversationArchived - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.convId Conversation ID - */ - /** - * Fired when a conversation has been favorited for the logged on user - * @event conversationFavorited - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.convId Conversation ID - */ - _clientApiHandler.on('Conversation.CONVERSATION_MARKED', function (evt) { - switch (evt.filter) { - case Constants.ConversationMarkFilter.MUTE: - addToEventQueue({type: 'conversationArchived', convId: evt.convId}); - break; - case Constants.ConversationMarkFilter.FAVORITE: - addToEventQueue({type: 'conversationFavorited', convId: evt.convId}); - break; - default: - break; - } - }); - - /** - * Fired when a conversation has been unarchived for the logged on user - * @event conversationUnarchived - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.convId Conversation ID - */ - /** - * Fired when a conversation has been unfavorited for the logged on user - * @event conversationUnfavorited - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.convId Conversation ID - */ - _clientApiHandler.on('Conversation.CONVERSATION_UNMARKED', function (evt) { - switch (evt.filter) { - case Constants.ConversationMarkFilter.MUTE: - addToEventQueue({type: 'conversationUnarchived', convId: evt.convId}); - break; - case Constants.ConversationMarkFilter.FAVORITE: - addToEventQueue({type: 'conversationUnfavorited', convId: evt.convId}); - break; - default: - break; - } - }); - - /** - * Fired when conversation user data changes. E.g. label added/changed/removed - * @event conversationUserDataChanged - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data User data - * @param {String} event.data.convId Conversation ID - * @param {String[]} event.data.labels Array of changed label IDs - */ - _clientApiHandler.on('Conversation.USER_DATA_CHANGED', function (evt) { - addToEventQueue({type: 'conversationUserDataChanged', data: evt}); - }); - - /** - * Fired when user read items on anohter device - * @event conversationReadItems - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data User data - * @param {String} event.data.convId Conversation ID - * @param {String[]} event.data.lastReadTimestamp Timestamp of last read item in this conversation - */ - _clientApiHandler.on('Conversation.READ_ITEMS', function (evt) { - addToEventQueue({type: 'conversationReadItems', data: evt}); - }); - - /** - * Fired when the presence of a subscribed user changes. - * @event userPresenceChanged - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Presence} presenceState Updated presence object - */ - _clientApiHandler.on('User.USER_PRESENCE_CHANGE', function (evt) { - addToEventQueue({type: 'userPresenceChanged', presenceState: evt.newState}); - }); - - /** - * Fired when the local user is updated. E.g. I change the jobTitle - * on my mobile device. - * @event userUpdated - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {User} event.user Updated user object - */ - _clientApiHandler.on('User.USER_UPDATED', function (evt) { - addToEventQueue({type: 'userUpdated', user: evt.user}); - }); - - /** - * Fired when the connection state changes. - * @event connectionStateChange - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.state Connection State (`Disconnected`, `Connecting`, `Reconnecting`, `Connected`) - */ - _clientApiHandler.addEventListener('connectionStateChange', function (evt) { - if (_connectionState === ConnectionState.Reconnecting && evt.newState === ConnectionState.Connected) { - initWs() - .catch(function (err) { - logger.error('Error initializing websocket on reconenct', err); - }); - } - _connectionState = evt.newState; - addToEventQueue({type: 'connectionStateChanged', state: evt.newState}); - }); - - /** - * Fired when automatic reconnecting to the server fails. - * @event reconnectFailed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - _clientApiHandler.addEventListener('reconnectFailed', function () { - addToEventQueue({type: 'reconnectFailed'}); - if (_config.client_secret) { - // For client credentials re-authenticate and re-authorize. - authenticateClientCredentials() - .then(sdkLogin) - .then(wsLogon) - .catch(function (err) { - logger.error('Error re-authenticating on auto-reconnect for client credentials grant', err); - }); - } - }); - - /** - * Fired when session token has been renewed. - * @event sessionTokenRenewed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - /** - * Fired when renewal of session token has failed. - * @event renewSessionTokenFailed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.error Error object if token renew failed - */ - _clientApiHandler.on('User.SESSION_EXPIRES', function () { - _self.loggedOnUser && renewSessionToken(); - }); - - /** - * Fired when session is about to expire. - * @event sessionExpiring - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - _clientApiHandler.on('User.SESSION_EXPIRING', function () { - addToEventQueue({type: 'sessionExpiring'}); - }); - - /** - * Fired when session has been forcefully closed by server. - * @event sessionClosed - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {SessionClosedReason} event.reason Reason - */ - _clientApiHandler.on('User.SESSION_CLOSED', function (evt) { - addToEventQueue({type: 'sessionClosed', reason: evt.reason}); - }); - - /** - * Fired when password has been changed. - * @event passwordChanged - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - _clientApiHandler.on('User.PASSWORD_CHANGED', function () { - addToEventQueue({type: 'passwordChanged'}); - }); - - /** - * Fired when one or more user settings for the logged on user change. - * @event userSettingsChanged - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {UserSettings} event.settings UserSettings object - */ - _clientApiHandler.on('User.USER_SETTING_UPDATED', function (evt) { - var settings = parseUserSettings(evt.settings); - if (Object.keys(settings).length > 0) { - addToEventQueue({type: 'userSettingsChanged', userSettings: settings}); - } - }); - - /** - * Asynchronous search results for startUserSearch or startBasicSearch. - * @event basicSearchResults - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data Object literal containing search ID and its results - * @param {String} event.data.searchId Search ID to correlate to the request - * @param {ConversationSearchResult[]} event.data.searchResults Array of conversation results - * @param {String[]} event.data.users Array of user IDs that match the query - */ - _clientApiHandler.on('Search.BASIC_SEARCH_RESULT', function (evt) { - addToEventQueue({type: 'basicSearchResults', data: evt}); - }); - - /** - * Status of a pending search. E.g. Indication a search is FINISHED. - * @event searchStatus - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data Object literal - * @param {SearchStatusCode} event.data.status Search status - * @param {String} event.data.searchId Search ID - */ - _clientApiHandler.on('Search.SEARCH_STATUS', function (evt) { - addToEventQueue({type: 'searchStatus', data: evt}); - }); - - /** - * Fired to inform of an upcoming maintenance event. - * @event systemMaintenance - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data Object literal containing start and end time - * @param {Number} event.data.startTime The start time of the maintenance window in UTC - * @param {Number} event.data.endTime The end time of the maintenance window in UTC - */ - _clientApiHandler.on('System.MAINTENANCE', function (evt) { - addToEventQueue({type: 'systemMaintenance', data: evt}); - }); - - // Not yet officially supported - _clientApiHandler.on('User.USER_DELETED', function (evt) { - addToEventQueue({type: 'userDeleted', data: evt}); - }); - - // Not yet officially supported - _clientApiHandler.on('Conversation.DELETE', function (evt) { - addToEventQueue({type: 'conversationDeleted', convId: evt.convId}); - }); - - /** - * Whiteboard enabled/disabled on a call. - * @event whiteboardEnabled - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Boolean} event.enabled `true` if enabled, `false` if disabled - * @param {Whiteboard} [event.whiteboard] The whiteboard object (only when whiteboard becomes enabled) - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_ENABLED', function (evt) { - addToEventQueue({ - type: 'whiteboardEnabled', - enabled: true, - whiteboard: evt.whiteboard, - data: { // deprecated - enabled: true, - whiteboard: evt.whiteboard - } - }); - }); - _clientApiHandler.on('RTCSession.WHITEBOARD_DISABLED', function () { - addToEventQueue({ - type: 'whiteboardEnabled', - enabled: false, - data: { // deprecated - enabled: false - } - }); - }); - - /** - * Whiteboard cleared. - * @event whiteboardCleared - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} [event.userId] userId if only elements for a specific user have been cleared - * @param {Number} [event.possibleUndoOperations] The number of supported undo operations for the whiteboard. - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_CLEARED', function (evt) { - addToEventQueue({type: 'whiteboardCleared', userId: evt.userId, possibleUndoOperations: evt.possibleUndoOperations}); - }); - - /** - * Whiteboard element added, removed or updated. - * @event whiteboardElement - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.action 'added', 'removed' or 'updated' - * @param {WhiteboardElement} [event.element] element added or updated - * @param {WhiteboardElementId} [event.elementId] element removed - * @param {Number} [event.possibleUndoOperations] The number of supported undo operations for the whiteboard. - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_ELEMENT_ADDED', function (evt) { - addToEventQueue({type: 'whiteboardElement', action: 'added', element: evt.element, possibleUndoOperations: evt.possibleUndoOperations}); - }); - _clientApiHandler.on('RTCSession.WHITEBOARD_ELEMENT_REMOVED', function (evt) { - addToEventQueue({type: 'whiteboardElement', action: 'removed', elementId: evt.elementId, possibleUndoOperations: evt.possibleUndoOperations}); - }); - _clientApiHandler.on('RTCSession.WHITEBOARD_ELEMENT_UPDATED', function (evt) { - addToEventQueue({type: 'whiteboardElement', action: 'updated', element: evt.element, possibleUndoOperations: evt.possibleUndoOperations}); - }); - - /** - * Whiteboard background set or cleared. - * @event whiteboardBackground - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {String} event.action 'set' or 'cleared' - * @param {WhiteboardElement} [event.element] Applicable for 'set'. The element for the background - * @param {Number} [event.possibleUndoOperations] Applicable for 'set'. The number of supported undo operations for the whiteboard. - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_BACKGROUND_SET', function (evt) { - addToEventQueue({type: 'whiteboardBackground', action: 'set', element: evt.element, possibleUndoOperations: evt.possibleUndoOperations}); - }); - _clientApiHandler.on('RTCSession.WHITEBOARD_BACKGROUND_CLEARED', function () { - addToEventQueue({type: 'whiteboardBackground', action: 'cleared'}); - }); - - /** - * Whiteboard overlay toggled. - * @event whiteboardOverlayToggled - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_OVERLAY_TOGGLED', function () { - addToEventQueue({type: 'whiteboardOverlayToggled'}); - }); - - /** - * Whiteboard sync event. - * @event whiteboardSync - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Whiteboard} [event.whiteboard] The whiteboard object - * @param {Number} [event.possibleUndoOperations] The number of supported undo operations for the whiteboard. - */ - _clientApiHandler.on('RTCSession.WHITEBOARD_SYNC', function (evt) { - addToEventQueue({type: 'whiteboardSync', whiteboard: evt.whiteboard, possibleUndoOperations: evt.possibleUndoOperations}); - }); - - /** - * Mention notification for logged on user. - * @event mention - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.mention - * @param {Object} event.mention.itemReference Object literal containing the convId and itemId - * @param {Object} event.mention.itemReference.convId Conversation ID - * @param {Object} event.mention.itemReference.itemId Item ID - * @param {Object} event.mention.userReference Object literal containing the userId - * @param {Object} event.mention.userReference.userId User ID - */ - _clientApiHandler.on('ActivityStream.ACTIVITY_CREATED', function (evt) { - if (evt.navigableItem && evt.navigableItem.mention) { - addToEventQueue({type: 'mention', mention: evt.navigableItem.mention}); - } - }); - - /** - * Fired when an incoming call is received. I.e. client is alerting - * @event callIncoming - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.call Call object - */ - _services.PubSubSvc.subscribe('/call/incoming', function (call) { - addToEventQueue({type: 'callIncoming', call: call}); - }); - - // For node.js use the warmupFailed event to raise an callIncoming event - _isNode && _services.PubSubSvc.subscribe('/call/warmupFailed', function (err, call) { - addToEventQueue({type: 'callIncoming', call: call}); - }); - - - /** - * Fired when the call state, or any other call attribute of a local or remote call changes. - * Use the isRemote property to determine if the call is local or remote. A call is considered - * remote when a) the call is active on anohter device, or b) a group call is not joined yet. - * @event callStatus - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.call Call object - * @param {Object} [event.participant] Participant object for participant related reasons - * @param {Object} [event.participants] Participants object for participant related reasons - * @param {Object} [event.recordingInfo] data for `callRecording` reasons - * @param {String} event.reason Reason event is triggered. Supported reasons are: callStateChanged, remoteStreamUpdated, localStreamEnded, callMuted, localUserMuted, localUserUnmuted, localUserSelfMuted, localUserSelfUnmuted, callLocked, callMoved, participantUpdated, participantAdded, participantJoined, participantRemoved, callRecording, droppedRemotely, sdpConnected - */ - _services.PubSubSvc.subscribe('/call/state', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'callStateChanged'}); - }); - _services.PubSubSvc.subscribe('/call/remoteStreamUpdated', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'remoteStreamUpdated'}); - }); - _services.PubSubSvc.subscribe('/call/localStreamEnded', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'localStreamEnded'}); - }); - _services.PubSubSvc.subscribe('/call/muted', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'callMuted'}); - }); - _services.PubSubSvc.subscribe('/call/localUser/muted', function (callId) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, reason: 'localUserMuted'}); - }); - _services.PubSubSvc.subscribe('/call/localUser/unmuted', function (callId) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, reason: 'localUserUnmuted'}); - }); - _services.PubSubSvc.subscribe('/call/localUser/mutedSelf', function (callId, muted) { - var call = findCall(callId); - if (muted) { - addToEventQueue({type: 'callStatus', call: call, reason: 'localUserSelfMuted'}); - } else { - addToEventQueue({type: 'callStatus', call: call, reason: 'localUserSelfUnmuted'}); - } - }); - _services.PubSubSvc.subscribe('/call/locked', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'callLocked'}); - }); - _services.PubSubSvc.subscribe('/call/moved', function (oldCallId, newCallId) { - var call = findCall(newCallId); - addToEventQueue({type: 'callStatus', call: call, reason: 'callMoved'}); - }); - _services.PubSubSvc.subscribe('/call/participant/updated', function (callId, participant) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, participant: participant, reason: 'participantUpdated'}); - }); - _services.PubSubSvc.subscribe('/call/participants/updated', function (callId, participants) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, participants: participants, reason: 'participantsUpdated'}); - }); - _services.PubSubSvc.subscribe('/call/participant/added', function (callId, participant) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, participant: participant, reason: 'participantAdded'}); - }); - _services.PubSubSvc.subscribe('/call/participants/added', function (callId, participants) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, participants: participants, reason: 'participantsAdded'}); - }); - _services.PubSubSvc.subscribe('/call/participant/joined', function (call, participant) { - addToEventQueue({type: 'callStatus', call: call, participant: participant, reason: 'participantJoined'}); - }); - _services.PubSubSvc.subscribe('/call/participant/removed', function (callId) { - var call = findCall(callId); - addToEventQueue({type: 'callStatus', call: call, reason: 'participantRemoved'}); - }); - _services.PubSubSvc.subscribe('/call/recording/info', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'callRecording'}); - }); - _services.PubSubSvc.subscribe('/call/droppedRemotely', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'droppedRemotely'}); - }); - _services.PubSubSvc.subscribe('/call/sdpConnected', function (call) { - addToEventQueue({type: 'callStatus', call: call, reason: 'sdpConnected'}); - }); - - - /** - * Fired when call network quality changes. - * @event callNetworkQualityChanged - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.data Call quality object - * @param {Object} event.data.userId User ID of local user - * @param {Object} event.data.audio Audio quality object - * @param {Object} event.data.audio.qualityLevel Low=1, Medium=2, High=3 - * @param {Object} event.data.audio.currentPlReceive Packet Loss on receiving stream - * @param {Object} event.data.audio.currentPlSend Packet Loss on sending stream - * @param {Object} event.data.audio.firstTimeLowQuality true if this is the first time with low quality on this call - */ - _services.PubSubSvc.subscribe('/call/network/quality', function (data) { - addToEventQueue({type: 'callNetworkQualityChanged', data: data}); - }); - - /** - * Fired when call quality falls below a predefined threshold. - * @event callRtpThresholdReached - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.call Call object - */ - _services.PubSubSvc.subscribe('/call/rtp/threshholdExceeded/detected', function (callId) { - var call = findCall(callId); - addToEventQueue({type: 'callRtpThresholdReached', call: call}); - }); - - /** - * Fired when call quality recovered after having reached the threshold. - * @event callRtpThresholdCleared - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.call Call object - */ - _services.PubSubSvc.subscribe('/call/rtp/threshholdExceeded/cleared', function (callId) { - var call = findCall(callId); - addToEventQueue({type: 'callRtpThresholdCleared', call: call}); - }); - - /** - * Fired when a call is terminated. - * @event callEnded - * @param {Object} event Object literal containing the event properties - * @param {String} event.type Event name - * @param {Object} event.call Call object - */ - _services.PubSubSvc.subscribe('/call/ended', function (call) { - // Ingore the event if there is a still a call present for that call ID - // E.g. Joining a group call will not raise a callEnded for the remote call. - if (!findCall(call.callId)) { - addToEventQueue({type: 'callEnded', call: call}); - } - }); - - return _self; - }); - - - circuit.Client.prototype.name = 'Client'; - - /** - * Check if browser is compatible. Use `supports` API for supported features on the current browser. - * @method isCompatible - * @return {Boolean} true if browser is compatible - */ - circuit.isCompatible = function () { - // Chrome 44+, Firefox 42+, IE11+ - var versionNumber = parseInt(browserInfo.version, 10); - return (browserInfo.chrome && versionNumber >= 44) || - (browserInfo.firefox && versionNumber >= 42) || - browserInfo.edge; - }; - - - /** - * List supported features for current browser. Only list features are currently `text` and `rtc`. - * @method supportedFeatures - * @return {Object} Object specifying what features are supported. - */ - circuit.supportedFeatures = function () { - if (!circuit.isCompatible()) { - return { - text: false, - rtc: false - }; - } - - return { - text: true, - rtc: browserInfo.chrome || browserInfo.firefox - }; - }; - - /** - * List of supported events - * @property supportedEvents - * @memberof Circuit - * @type Array - * @readOnly - */ - Object.defineProperty(circuit, 'supportedEvents', { - get: function () { - return [ - 'accessTokenRenewed', - 'renewAccessTokenFailed', - 'loggedOnUserUpdated', - 'itemAdded', - 'itemUpdated', - 'conversationCreated', - 'conversationUpdated', - 'conversationArchived', - 'conversationFavorited', - 'conversationUnarchived', - 'conversationUnfavorited', - 'conversationUserDataChanged', - 'conversationReadItems', - 'userPresenceChanged', - 'userUpdated', - 'connectionStateChanged', - 'sessionTokenRenewed', - 'reconnectFailed', - 'userSettingsChanged', - 'basicSearchResults', - 'systemMaintenance', - //'userDeleted', - //'conversationDeleted', - 'whiteboardEnabled', - 'whiteboardElement', - 'whiteboardCleared', - 'mention', - 'callIncoming', - 'callStatus', - 'callNetworkQualityChanged', - 'callRtpThresholdReached', - 'callRtpThresholdCleared', - 'callEnded', - 'whiteboardBackground', - 'whiteboardOverlayToggled', - 'whiteboardSync' - ]; - }, - enumerable: true, - configurable: true - }); - - /** - * SDK version - * @property version - * @type String - * @readOnly - */ - // Injected to top of file - - return circuit; -})(Circuit); - -/** - * Copyright 2018 Unify Software and Solutions GmbH & Co.KG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -/*global Circuit, navigator, process, Promise, require, WebSocket, XMLHttpRequest*/ - -/////////////////////////////////////////////////////////////////////////////////////// -// setLogger and circuit.NodeSDK for Node.js -/////////////////////////////////////////////////////////////////////////////////////// -var Circuit = (function (circuit) { - 'use strict'; - - if (circuit._runtime.name === 'node') { - - circuit.setLogger = function setLogger(appLogger) { - appLogger = appLogger || {}; - var logDebug = appLogger.debug || appLogger.log || function () {}; - var logInfo = appLogger.info || logDebug; - var logWarning = appLogger.warn || appLogger.warning || logInfo; - var logError = appLogger.error || logWarning; - - circuit.logger.debug = function () { - logDebug.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - circuit.logger.info = function () { - logInfo.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - circuit.logger.warning = function () { - logWarning.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - circuit.logger.warn = function () { - logWarning.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - circuit.logger.error = function (error, obj) { - var args = [(error && error.stack) || error]; - obj = (obj && obj.stack) || obj; - if (obj) { - args.push(obj); - } - logError.apply(appLogger, args); - }; - circuit.logger.msgSend = function () { - logInfo.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - circuit.logger.msgRcvd = function () { - logInfo.apply(appLogger, Array.prototype.slice.apply(arguments)); - }; - }; - - /** - * Object defining Node.js SDK specific options. - * @class NodeSDK - */ - circuit.NodeSDK = {}; - - /** - * If true, the server certificate is verified against the list of supplied CAs. - * An error event is emitted if verification fails; err.code contains the OpenSSL - * error code. Default: false. - * Only applicable to Node.js SDK. - * @property rejectUnauthorized - * @type Boolean - * @example - * NodeSDK.rejectUnauthorized = true; - */ - circuit.NodeSDK.rejectUnauthorized = false; - - /** - * HttpsProxyAgent object as defined in https://www.npmjs.com/package/https-proxy-agent. - * Agent will be used on HTTP(S) request and WebSocket connection within Node SDK. - * Only applicable to Node.js SDK. - * @property proxyAgent - * @type Object - * @example - * // export http_proxy=http://172.20.1.100:8080 - * var HttpsProxyAgent = require('https-proxy-agent'); - * NodeSDK.proxyAgent = new HttpsProxyAgent(url.parse(process.env.http_proxy)); - */ - circuit.NodeSDK.proxyAgent = undefined; - } - - - /////////////////////////////////////////////////////////////////////////////////////////////// - // FileUpload for Node.js - /////////////////////////////////////////////////////////////////////////////////////////////// - if (circuit._runtime.name === 'node') { - - var MAX_FILE_COUNT = 10; - var MAX_FILE_SIZE = 50000000; - var nodeUrl = require('url'); - var fs = require('fs'); - var https = require('https'); - var stat = 0; - var logger = circuit.logger; - - function FileUpload(config) { - - var _config = config; - - /////////////////////////////////////////////////////////////////////////////////////////////// - // private - isFileOK - /////////////////////////////////////////////////////////////////////////////////////////////// - function isFileOK(file) { - //TODO optimize for async flow - var error = null; - - if (file.path) { - if (!fs.existsSync(file.path)) { - error = new Error('[FileUpload] file not found'); - } - stat = fs.statSync(file.path); - if (stat.size > MAX_FILE_SIZE) { - error = new Error('[FileUpload] max file size exceeded'); - } - } else if (file.buffer) { - stat = { size: file.buffer.length }; - if (stat.size > MAX_FILE_SIZE) { - error = new Error('[FileUpload] max file size exceeded'); - } - } else { - error = new Error('[FileUpload] no file provided'); - } - if (file.type === 'application/json') { - logger.debug('[FileUpload]: replace file type ', file.type); - file.type = 'text/plain'; //upload json files as text files - } - return error; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // private - urlToOptions - /////////////////////////////////////////////////////////////////////////////////////////////// - function urlToOptions(url, file, stat) { - logger.debug('[FileUpload] urlToOptions', url, file, stat); - var parts = nodeUrl.parse(url); - var options = { - rejectUnauthorized: circuit.NodeSDK.rejectUnauthorized, - agent: circuit.NodeSDK.proxyAgent, - host: parts.hostname, - port: (parts.port) ? parts.port : '443', - path: parts.path, - method: 'POST', - headers: { - 'User-Agent': navigator.userAgent, - 'Cookie': _config.cookie, - 'Content-Disposition': 'attachment; filename="' + file.name + '"', - 'Content-Length': stat.size, - 'Content-Type': file.type - } - }; - logger.debug('[FileUpload] options', options); - return options; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // private - upLoadFile - /////////////////////////////////////////////////////////////////////////////////////////////// - function upLoadFile(file, itemId, domain) { - logger.debug('[FileUpload] upLoadFile', file, itemId, domain); - return new Promise(function (resolve, reject) { - var error = isFileOK(file); - if (error) { - reject(error); - return; - } - var url = 'https://' + domain + '/fileapi?itemid=' + (itemId || 'NULL'); - var options = urlToOptions(url, file, stat); - var req = https.request(options, function (res) { - if (!res) { - logger.error('[FileUpload]: no res in https callback'); - error = new Error('[FileUpload]: no res in https callback'); - reject(error); - } - logger.debug('[FileUpload]: https callback', res.statusCode, res.statusMessage, res.headers); - if (res.statusCode !== 200) { - logger.error('[FileUpload]: status code: ' + res.statusCode, res.statusMessage); - error = new Error('[FileUpload]: status code ' + res.statusCode, res.statusMessage); - reject(error); - } - var body = ''; - res.on('data', function (d) { - logger.debug('[FileUpload]: result on data', d); - body += d; - }); - res.on('end', function () { - logger.debug('[FileUpload]: result on end', body.toString('utf8')); - resolve(body); - }); - res.on('error', function (e) { - logger.debug('[FileUpload]: result on error', e); - reject(e); - }); - }); - - if (file.path) { - var readStream = fs.createReadStream(file.path); - readStream.on('data', function (chunk) { - logger.debug('[FileUpload]: readStream on data', file.path, chunk); - req.write(chunk); - }); - readStream.on('end', function () { - logger.debug('[FileUpload]: readStream on end ', file.path); - req.end(); - }); - - req.on('error', function (e) { - logger.debug('[FileUpload]: request on error', file.path, e); - reject(e); - }); - } else if (file.buffer) { - req.end(file.buffer); - } - }); - } - - - /////////////////////////////////////////////////////////////////////////////////////////////// - // circuit.FileUpload.uploadFiles - upload files for node SDK - /////////////////////////////////////////////////////////////////////////////////////////////// - this.uploadFiles = function uploadFiles(files, domain) { - var logger = circuit.logger; - logger.debug('[FileUpload] node SDK uploadFiles', files, domain); - - if (files.length > circuit.MAX_FILE_COUNT) { - return Promise.reject('[FileUpload]: Exceeded maximum of %d% files per message', MAX_FILE_COUNT); - } - - var result = []; - - // Find large images that need to be resized - var resizePromises = []; - files.forEach(function (file) { - // Even create a thumbnail for small images - if (circuit.Utils.isSupportedImage && circuit.Utils.isSupportedImage(file.type)) { - //resizePromises.push(createThumbnail(file)); - logger.warning('[FileUpload]: node SDK does not yet support creation of thumbnails'); - } - }); - - return Promise.all(resizePromises) - .then(function (blobs) { - // Add the blob's (thumbnails) to the list of files to be upoaded - files = files.concat(blobs); - - // Sequentially (but async) upload the files. Once all files are - // uploaded, resolve the Promise passing the upload results. - var itemId = null; - return files.reduce(function (sequence, file) { - return sequence.then(function () { - return upLoadFile(file, itemId, domain) - .then(function (res) { - var resp = JSON.parse(res)[0]; - itemId = itemId || resp.id; - var attachmentMetaData = { - fileId: resp.fileid.slice(-1)[0], - fileName: file.name, - itemId: itemId, - mimeType: resp.mimeType || file.type, - size: file.size - }; - result.push(attachmentMetaData); - return result; - }); - }); - }, Promise.resolve()); - }); - }; - } - - // Exports - circuit.FileUpload = FileUpload; - } - - return circuit; -})(Circuit || {}); - - -/////////////////////////////////////////////////////////////////////////////////////// -// Global objects exposed for Node.js (WebSocket and XMLHttpRequest) -/////////////////////////////////////////////////////////////////////////////////////// -if (Circuit._runtime.name === 'node') { - - var NodeWebSocket = require('ws'); - var https = require('https'); - var nodeUrl = require('url'); - - WebSocket = function (target, cookie) { - 'use strict'; - - var logger = Circuit.logger; - - var wsOptions = { - headers: { - Cookie: cookie - }, - rejectUnauthorized: Circuit.NodeSDK.rejectUnauthorized, - agent: Circuit.NodeSDK.proxyAgent - }; - logger.debug('[WebSocket]: Initiated websocket with ' + JSON.stringify(wsOptions)); - return new NodeWebSocket(target, wsOptions); - }; - - XMLHttpRequest = function (config) { - 'use strict'; - - var _logger = Circuit.logger; - var _self = this; - - _self.httpReqOptions = null; - _self.withCredentials = null; - _self.onload = function () {}; - _self.onerror = function () {}; - _self.login = false; - _self.config = config; - - this.setRequestHeader = function (header, value) { - _logger.debug('[XMLHttpRequest]: setRequestHeader ' + header + ':' + value); - this.httpReqOptions.headers[header] = value; - }; - - this.setAuthCookie = function (res) { - _self.config.cookie = ''; - var str = res.headers['set-cookie'].toString(); - var re = null, found = false; - re = /.*(connect\.sess=.*?);.*/i; - found = str.match(re); - if (found) { - _self.config.cookie += found[1]; - } else { - _logger.error('[XMLHttpRequest]: setAuthCookie connect.sess not found in set-cookie'); - process.exit(1); - } - _logger.debug('[XMLHttpRequest]: setAuthCookie ' + JSON.stringify(_self.config.cookie, null, 2)); - }; - - this.open = function (method, url, async) { - _logger.debug('[XMLHttpRequest]: open ' + method + ' ' + url + ' ' + async); - var parts = nodeUrl.parse(url); - _logger.debug(JSON.stringify(parts, null, 2)); - - this.httpReqOptions = { - host: parts.hostname, - port: (parts.port) ? parts.port : '443', - path: parts.path, - method: method, - headers: { - 'User-Agent': navigator.userAgent - }, - rejectUnauthorized: Circuit.NodeSDK.rejectUnauthorized, - agent: Circuit.NodeSDK.proxyAgent - }; - - this.login = /^\/.*login/g.test(parts.path); - if (!this.login && _self.config.cookie) { - this.setRequestHeader('Cookie', _self.config.cookie); - } - }; - - this.send = function (data) { - _logger.debug('[XMLHttpRequest]: send'); - var xhr = this; - _logger.debug('[XMLHttpRequest]: ' + JSON.stringify(this.httpReqOptions, null, 2)); - - var req = https.request(this.httpReqOptions, function (res) { - _logger.debug('[XMLHttpRequest]: response code ' + res.statusCode); - _logger.debug('[XMLHttpRequest]: response headers ' + JSON.stringify(res.headers, null, 2)); - - xhr.status = res.statusCode; - - if (res.statusCode !== 200 && res.statusCode !== 302) { - _logger.error('[XMLHttpRequest]: response with status code error ' + res.statusCode); - xhr.onerror(); - return; - } - - if (xhr.login) { - xhr.setAuthCookie(res); - } - - res.setEncoding('utf8'); - - res.on('data', function (data) { - _logger.debug('[XMLHttpRequest]: ready to call onload with ' + JSON.stringify(data, null, 2)); - xhr.responseText = data; - xhr.onload(); - }); - - res.on('error', function (e) { - _logger.error('[XMLHttpRequest]: result error ' + JSON.stringify(e, null, 2)); - xhr.onerror(); - }); - - }); - - if (data) { - _logger.debug('[XMLHttpRequest]: sending data'); - req.write(data); - } - - req.on('error', function (e) { - _logger.error('[XMLHttpRequest]: request error ' + JSON.stringify(e, null, 2)); - xhr.onerror(); - }); - - req.end(); - }; - }; -} - -return Circuit; - -})); diff --git a/commonjs/index.js b/commonjs/index.js index 5d161a0..b6b0173 100644 --- a/commonjs/index.js +++ b/commonjs/index.js @@ -1,4 +1,4 @@ -const Circuit = require('../circuit.js'); +const Circuit = require('circuit-sdk'); let client = new Circuit.Client({ client_id: 'ebcf5281298a4f2fb1095ea58011d686', diff --git a/global/index.html b/global/index.html index 71c35ce..ca7bb17 100644 --- a/global/index.html +++ b/global/index.html @@ -3,7 +3,7 @@ <button onclick="logon()">Logon</button> <button onclick="logout()">Logout</button> <div id="user"></div> - <script src="../circuit.js"></script> + <script src="//unpkg.com/circuit-sdk"></script> <script> let client = new Circuit.Client({ client_id: 'ebcf5281298a4f2fb1095ea58011d686', diff --git a/package-lock.json b/package-lock.json index cbab9fc..3b57f21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,26 @@ "version": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" }, + "circuit-sdk": { + "version": "1.2.2902", + "resolved": "https://registry.npmjs.org/circuit-sdk/-/circuit-sdk-1.2.2902.tgz", + "integrity": "sha512-BMzLonUNk6QjRGncVxpn6yTK9PnBWlE7gE4KyWKQxI5FU3h2YCFbVCzA1wW3Aa+OKdX8av6L3oDdDc+fMNCxcQ==", + "requires": { + "ws": "3.3.3" + }, + "dependencies": { + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "ultron": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz" + } + } + } + }, "safe-buffer": { "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" diff --git a/package.json b/package.json index 5ca887f..cfa7df5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Roger Urscheler <roger.urscheler@unify.com>", "license": "ISC", "dependencies": { + "circuit-sdk": "^1.2.2902", "ws": "^4.0.0" } }