1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 12.5.1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2020 Cisco Systems, Inc. or its affiliated entities. All Rights Reserved. 9 */ 10 /** 11 * This JavaScript library is made available to Cisco partners and customers as 12 * a convenience to help minimize the cost of Cisco Finesse customizations. 13 * This library can be used in Cisco Finesse deployments. Cisco does not 14 * permit the use of this library in customer deployments that do not include 15 * Cisco Finesse. Support for the JavaScript library is provided on a 16 * "best effort" basis via CDN. Like any custom deployment, it is the 17 * responsibility of the partner and/or customer to ensure that the 18 * customization works correctly and this includes ensuring that the Cisco 19 * Finesse JavaScript is properly integrated into 3rd party applications. 20 * Cisco reserves the right to make changes to the JavaScript code and 21 * corresponding API as part of the normal Cisco Finesse release cycle. The 22 * implication of this is that new versions of the JavaScript might be 23 * incompatible with applications built on older Finesse integrations. That 24 * said, it is Cisco's intention to ensure JavaScript compatibility across 25 * versions as much as possible and Cisco will make every effort to clearly 26 * document any differences in the JavaScript across versions in the event 27 * that a backwards compatibility impacting change is made. 28 */ 29 (function (root, factory) { 30 if (typeof define === 'function' && define.amd) { 31 define('finesse', [], factory); 32 } else { 33 root.finesse = factory(); 34 } 35 }(this, function () { 36 /** 37 * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 38 * Available via the MIT or new BSD license. 39 * see: http://github.com/jrburke/almond for details 40 */ 41 //Going sloppy to avoid 'use strict' string cost, but strict practices should 42 //be followed. 43 /*jslint sloppy: true */ 44 /*global setTimeout: false */ 45 46 var requirejs, require, define; 47 (function (undef) { 48 var main, req, makeMap, handlers, 49 defined = {}, 50 waiting = {}, 51 config = {}, 52 defining = {}, 53 hasOwn = Object.prototype.hasOwnProperty, 54 aps = [].slice, 55 jsSuffixRegExp = /\.js$/; 56 57 function hasProp(obj, prop) { 58 return hasOwn.call(obj, prop); 59 } 60 61 /** 62 * Given a relative module name, like ./something, normalize it to 63 * a real name that can be mapped to a path. 64 * @param {String} name the relative name 65 * @param {String} baseName a real name that the name arg is relative 66 * to. 67 * @returns {String} normalized name 68 */ 69 function normalize(name, baseName) { 70 var nameParts, nameSegment, mapValue, foundMap, lastIndex, 71 foundI, foundStarMap, starI, i, j, part, 72 baseParts = baseName && baseName.split("/"), 73 map = config.map, 74 starMap = (map && map['*']) || {}; 75 76 //Adjust any relative paths. 77 if (name && name.charAt(0) === ".") { 78 //If have a base name, try to normalize against it, 79 //otherwise, assume it is a top-level require that will 80 //be relative to baseUrl in the end. 81 if (baseName) { 82 //Convert baseName to array, and lop off the last part, 83 //so that . matches that "directory" and not name of the baseName's 84 //module. For instance, baseName of "one/two/three", maps to 85 //"one/two/three.js", but we want the directory, "one/two" for 86 //this normalization. 87 baseParts = baseParts.slice(0, baseParts.length - 1); 88 name = name.split('/'); 89 lastIndex = name.length - 1; 90 91 // Node .js allowance: 92 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 93 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 94 } 95 96 name = baseParts.concat(name); 97 98 //start trimDots 99 for (i = 0; i < name.length; i += 1) { 100 part = name[i]; 101 if (part === ".") { 102 name.splice(i, 1); 103 i -= 1; 104 } else if (part === "..") { 105 if (i === 1 && (name[2] === '..' || name[0] === '..')) { 106 //End of the line. Keep at least one non-dot 107 //path segment at the front so it can be mapped 108 //correctly to disk. Otherwise, there is likely 109 //no path mapping for a path starting with '..'. 110 //This can still fail, but catches the most reasonable 111 //uses of .. 112 break; 113 } else if (i > 0) { 114 name.splice(i - 1, 2); 115 i -= 2; 116 } 117 } 118 } 119 //end trimDots 120 121 name = name.join("/"); 122 } else if (name.indexOf('./') === 0) { 123 // No baseName, so this is ID is resolved relative 124 // to baseUrl, pull off the leading dot. 125 name = name.substring(2); 126 } 127 } 128 129 //Apply map config if available. 130 if ((baseParts || starMap) && map) { 131 nameParts = name.split('/'); 132 133 for (i = nameParts.length; i > 0; i -= 1) { 134 nameSegment = nameParts.slice(0, i).join("/"); 135 136 if (baseParts) { 137 //Find the longest baseName segment match in the config. 138 //So, do joins on the biggest to smallest lengths of baseParts. 139 for (j = baseParts.length; j > 0; j -= 1) { 140 mapValue = map[baseParts.slice(0, j).join('/')]; 141 142 //baseName segment has config, find if it has one for 143 //this name. 144 if (mapValue) { 145 mapValue = mapValue[nameSegment]; 146 if (mapValue) { 147 //Match, update name to the new value. 148 foundMap = mapValue; 149 foundI = i; 150 break; 151 } 152 } 153 } 154 } 155 156 if (foundMap) { 157 break; 158 } 159 160 //Check for a star map match, but just hold on to it, 161 //if there is a shorter segment match later in a matching 162 //config, then favor over this star map. 163 if (!foundStarMap && starMap && starMap[nameSegment]) { 164 foundStarMap = starMap[nameSegment]; 165 starI = i; 166 } 167 } 168 169 if (!foundMap && foundStarMap) { 170 foundMap = foundStarMap; 171 foundI = starI; 172 } 173 174 if (foundMap) { 175 nameParts.splice(0, foundI, foundMap); 176 name = nameParts.join('/'); 177 } 178 } 179 180 return name; 181 } 182 183 function makeRequire(relName, forceSync) { 184 return function () { 185 //A version of a require function that passes a moduleName 186 //value for items that may need to 187 //look up paths relative to the moduleName 188 return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 189 }; 190 } 191 192 function makeNormalize(relName) { 193 return function (name) { 194 return normalize(name, relName); 195 }; 196 } 197 198 function makeLoad(depName) { 199 return function (value) { 200 defined[depName] = value; 201 }; 202 } 203 204 function callDep(name) { 205 if (hasProp(waiting, name)) { 206 var args = waiting[name]; 207 delete waiting[name]; 208 defining[name] = true; 209 main.apply(undef, args); 210 } 211 212 if (!hasProp(defined, name) && !hasProp(defining, name)) { 213 throw new Error('No ' + name); 214 } 215 return defined[name]; 216 } 217 218 //Turns a plugin!resource to [plugin, resource] 219 //with the plugin being undefined if the name 220 //did not have a plugin prefix. 221 function splitPrefix(name) { 222 var prefix, 223 index = name ? name.indexOf('!') : -1; 224 if (index > -1) { 225 prefix = name.substring(0, index); 226 name = name.substring(index + 1, name.length); 227 } 228 return [prefix, name]; 229 } 230 231 /** 232 * Makes a name map, normalizing the name, and using a plugin 233 * for normalization if necessary. Grabs a ref to plugin 234 * too, as an optimization. 235 */ 236 makeMap = function (name, relName) { 237 var plugin, 238 parts = splitPrefix(name), 239 prefix = parts[0]; 240 241 name = parts[1]; 242 243 if (prefix) { 244 prefix = normalize(prefix, relName); 245 plugin = callDep(prefix); 246 } 247 248 //Normalize according 249 if (prefix) { 250 if (plugin && plugin.normalize) { 251 name = plugin.normalize(name, makeNormalize(relName)); 252 } else { 253 name = normalize(name, relName); 254 } 255 } else { 256 name = normalize(name, relName); 257 parts = splitPrefix(name); 258 prefix = parts[0]; 259 name = parts[1]; 260 if (prefix) { 261 plugin = callDep(prefix); 262 } 263 } 264 265 //Using ridiculous property names for space reasons 266 return { 267 f: prefix ? prefix + '!' + name : name, //fullName 268 n: name, 269 pr: prefix, 270 p: plugin 271 }; 272 }; 273 274 function makeConfig(name) { 275 return function () { 276 return (config && config.config && config.config[name]) || {}; 277 }; 278 } 279 280 handlers = { 281 require: function (name) { 282 return makeRequire(name); 283 }, 284 exports: function (name) { 285 var e = defined[name]; 286 if (typeof e !== 'undefined') { 287 return e; 288 } else { 289 return (defined[name] = {}); 290 } 291 }, 292 module: function (name) { 293 return { 294 id: name, 295 uri: '', 296 exports: defined[name], 297 config: makeConfig(name) 298 }; 299 } 300 }; 301 302 main = function (name, deps, callback, relName) { 303 var cjsModule, depName, ret, map, i, 304 args = [], 305 callbackType = typeof callback, 306 usingExports; 307 308 //Use name if no relName 309 relName = relName || name; 310 311 //Call the callback to define the module, if necessary. 312 if (callbackType === 'undefined' || callbackType === 'function') { 313 //Pull out the defined dependencies and pass the ordered 314 //values to the callback. 315 //Default to [require, exports, module] if no deps 316 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 317 for (i = 0; i < deps.length; i += 1) { 318 map = makeMap(deps[i], relName); 319 depName = map.f; 320 321 //Fast path CommonJS standard dependencies. 322 if (depName === "require") { 323 args[i] = handlers.require(name); 324 } else if (depName === "exports") { 325 //CommonJS module spec 1.1 326 args[i] = handlers.exports(name); 327 usingExports = true; 328 } else if (depName === "module") { 329 //CommonJS module spec 1.1 330 cjsModule = args[i] = handlers.module(name); 331 } else if (hasProp(defined, depName) || 332 hasProp(waiting, depName) || 333 hasProp(defining, depName)) { 334 args[i] = callDep(depName); 335 } else if (map.p) { 336 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 337 args[i] = defined[depName]; 338 } else { 339 throw new Error(name + ' missing ' + depName); 340 } 341 } 342 343 ret = callback ? callback.apply(defined[name], args) : undefined; 344 345 if (name) { 346 //If setting exports via "module" is in play, 347 //favor that over return value and exports. After that, 348 //favor a non-undefined return value over exports use. 349 if (cjsModule && cjsModule.exports !== undef && 350 cjsModule.exports !== defined[name]) { 351 defined[name] = cjsModule.exports; 352 } else if (ret !== undef || !usingExports) { 353 //Use the return value from the function. 354 defined[name] = ret; 355 } 356 } 357 } else if (name) { 358 //May just be an object definition for the module. Only 359 //worry about defining if have a module name. 360 defined[name] = callback; 361 } 362 }; 363 364 requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 365 if (typeof deps === "string") { 366 if (handlers[deps]) { 367 //callback in this case is really relName 368 return handlers[deps](callback); 369 } 370 //Just return the module wanted. In this scenario, the 371 //deps arg is the module name, and second arg (if passed) 372 //is just the relName. 373 //Normalize module name, if it contains . or .. 374 return callDep(makeMap(deps, callback).f); 375 } else if (!deps.splice) { 376 //deps is a config object, not an array. 377 config = deps; 378 if (config.deps) { 379 req(config.deps, config.callback); 380 } 381 if (!callback) { 382 return; 383 } 384 385 if (callback.splice) { 386 //callback is an array, which means it is a dependency list. 387 //Adjust args if there are dependencies 388 deps = callback; 389 callback = relName; 390 relName = null; 391 } else { 392 deps = undef; 393 } 394 } 395 396 //Support require(['a']) 397 callback = callback || function () {}; 398 399 //If relName is a function, it is an errback handler, 400 //so remove it. 401 if (typeof relName === 'function') { 402 relName = forceSync; 403 forceSync = alt; 404 } 405 406 //Simulate async callback; 407 if (forceSync) { 408 main(undef, deps, callback, relName); 409 } else { 410 //Using a non-zero value because of concern for what old browsers 411 //do, and latest browsers "upgrade" to 4 if lower value is used: 412 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 413 //If want a value immediately, use require('id') instead -- something 414 //that works in almond on the global level, but not guaranteed and 415 //unlikely to work in other AMD implementations. 416 setTimeout(function () { 417 main(undef, deps, callback, relName); 418 }, 4); 419 } 420 421 return req; 422 }; 423 424 /** 425 * Just drops the config on the floor, but returns req in case 426 * the config return value is used. 427 */ 428 req.config = function (cfg) { 429 return req(cfg); 430 }; 431 432 /** 433 * Expose module registry for debugging and tooling 434 */ 435 requirejs._defined = defined; 436 437 define = function (name, deps, callback) { 438 439 //This module may not have dependencies 440 if (!deps.splice) { 441 //deps is not an array, so probably means 442 //an object literal or factory function for 443 //the value. Adjust args. 444 callback = deps; 445 deps = []; 446 } 447 448 if (!hasProp(defined, name) && !hasProp(waiting, name)) { 449 waiting[name] = [name, deps, callback]; 450 } 451 }; 452 453 define.amd = { 454 jQuery: true 455 }; 456 }()); 457 define("../thirdparty/almond", function(){}); 458 459 /* Simple JavaScript Inheritance 460 * By John Resig http://ejohn.org/ 461 * MIT Licensed. 462 */ 463 // Inspired by base2 and Prototype 464 define('../thirdparty/Class',[], function () { 465 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 466 // The base Class implementation (does nothing) 467 /** @private */ 468 Class = function(){}; 469 470 // Create a new Class that inherits from this class 471 /** @private */ 472 Class.extend = function(prop) { 473 var _super = this.prototype; 474 475 // Instantiate a base class (but only create the instance, 476 // don't run the init constructor) 477 initializing = true; 478 var prototype = new this(); 479 initializing = false; 480 481 // Copy the properties over onto the new prototype 482 for (var name in prop) { 483 // Check if we're overwriting an existing function 484 prototype[name] = typeof prop[name] == "function" && 485 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 486 (function(name, fn){ 487 return function() { 488 var tmp = this._super; 489 490 // Add a new ._super() method that is the same method 491 // but on the super-class 492 this._super = _super[name]; 493 494 // The method only need to be bound temporarily, so we 495 // remove it when we're done executing 496 var ret = fn.apply(this, arguments); 497 this._super = tmp; 498 499 return ret; 500 }; 501 })(name, prop[name]) : 502 prop[name]; 503 } 504 505 // The dummy class constructor 506 /** @private */ 507 function Class() { 508 // All construction is actually done in the init method 509 if ( !initializing && this.init ) 510 this.init.apply(this, arguments); 511 } 512 513 // Populate our constructed prototype object 514 Class.prototype = prototype; 515 516 // Enforce the constructor to be what we expect 517 Class.prototype.constructor = Class; 518 519 // And make this class extendable 520 Class.extend = arguments.callee; 521 522 return Class; 523 }; 524 return Class; 525 }); 526 527 /** 528 * JavaScript base object that all finesse objects should inherit 529 * from because it encapsulates and provides the common functionality. 530 * 531 * Note: This javascript class requires the "inhert.js" to be included 532 * (which simplifies the class inheritance). 533 * 534 * 535 * @requires finesse.utilities.Logger 536 */ 537 538 /** The following comment is to prevent jslint errors about 539 * using variables before they are defined. 540 */ 541 /*global Class */ 542 define('FinesseBase', ["../thirdparty/Class"], function (Class) { 543 var FinesseBase = Class.extend({ 544 init: function () { 545 } 546 }); 547 548 window.finesse = window.finesse || {}; 549 window.finesse.FinesseBase = FinesseBase; 550 551 return FinesseBase; 552 }); 553 554 /** 555 * A collection of conversion utilities. 556 * Last modified 07-06-2011, Cisco Systems 557 * 558 */ 559 /** @private */ 560 define('utilities/../../thirdparty/util/converter',[], function () { 561 /** 562 * @class 563 * Contains a collection of utility functions. 564 * @private 565 */ 566 var Converter = (function () { 567 return { 568 /* This work is licensed under Creative Commons GNU LGPL License. 569 570 License: http://creativecommons.org/licenses/LGPL/2.1/ 571 Version: 0.9 572 Author: Stefan Goessner/2006 573 Web: http://goessner.net/ 574 575 2013-09-16 Modified to remove use of XmlNode.innerHTML in the innerXml function by Cisco Systems, Inc. 576 */ 577 xml2json: function (xml, tab) { 578 var X = { 579 toObj: function (xml) { 580 var o = {}; 581 if (xml.nodeType === 1) { 582 // element node .. 583 if (xml.attributes.length) 584 // element with attributes .. 585 for (var i = 0; i < xml.attributes.length; i++) 586 o["@" + xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue || "").toString(); 587 if (xml.firstChild) { 588 // element has child nodes .. 589 var textChild = 0, 590 cdataChild = 0, 591 hasElementChild = false; 592 for (var n = xml.firstChild; n; n = n.nextSibling) { 593 if (n.nodeType == 1) hasElementChild = true; 594 else if (n.nodeType == 3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; 595 // non-whitespace text 596 else if (n.nodeType == 4) cdataChild++; 597 // cdata section node 598 } 599 if (hasElementChild) { 600 if (textChild < 2 && cdataChild < 2) { 601 // structured element with evtl. a single text or/and cdata node .. 602 X.removeWhite(xml); 603 for (var n = xml.firstChild; n; n = n.nextSibling) { 604 if (n.nodeType == 3) 605 // text node 606 o["#text"] = X.escape(n.nodeValue); 607 else if (n.nodeType == 4) 608 // cdata node 609 o["#cdata"] = X.escape(n.nodeValue); 610 else if (o[n.nodeName]) { 611 // multiple occurence of element .. 612 if (o[n.nodeName] instanceof Array) 613 o[n.nodeName][o[n.nodeName].length] = X.toObj(n); 614 else 615 o[n.nodeName] = [o[n.nodeName], X.toObj(n)]; 616 } 617 else 618 // first occurence of element.. 619 o[n.nodeName] = X.toObj(n); 620 } 621 } 622 else { 623 // mixed content 624 if (!xml.attributes.length) 625 o = X.escape(X.innerXml(xml)); 626 else 627 o["#text"] = X.escape(X.innerXml(xml)); 628 } 629 } 630 else if (textChild) { 631 // pure text 632 if (!xml.attributes.length) 633 o = X.escape(X.innerXml(xml)); 634 else 635 o["#text"] = X.escape(X.innerXml(xml)); 636 } 637 else if (cdataChild) { 638 // cdata 639 if (cdataChild > 1) 640 o = X.escape(X.innerXml(xml)); 641 else 642 for (var n = xml.firstChild; n; n = n.nextSibling) 643 o["#cdata"] = X.escape(n.nodeValue); 644 } 645 } 646 if (!xml.attributes.length && !xml.firstChild) o = null; 647 } 648 else if (xml.nodeType == 9) { 649 // document.node 650 o = X.toObj(xml.documentElement); 651 } 652 else 653 throw ("unhandled node type: " + xml.nodeType); 654 return o; 655 }, 656 toJson: function(o, name, ind) { 657 var json = name ? ("\"" + name + "\"") : ""; 658 if (o instanceof Array) { 659 for (var i = 0, n = o.length; i < n; i++) 660 o[i] = X.toJson(o[i], "", ind + "\t"); 661 json += (name ? ":[": "[") + (o.length > 1 ? ("\n" + ind + "\t" + o.join(",\n" + ind + "\t") + "\n" + ind) : o.join("")) + "]"; 662 } 663 else if (o == null) 664 json += (name && ":") + "null"; 665 else if (typeof(o) == "object") { 666 var arr = []; 667 for (var m in o) 668 arr[arr.length] = X.toJson(o[m], m, ind + "\t"); 669 json += (name ? ":{": "{") + (arr.length > 1 ? ("\n" + ind + "\t" + arr.join(",\n" + ind + "\t") + "\n" + ind) : arr.join("")) + "}"; 670 } 671 else if (typeof(o) == "string") 672 json += (name && ":") + "\"" + o.toString() + "\""; 673 else 674 json += (name && ":") + o.toString(); 675 return json; 676 }, 677 innerXml: function(node) { 678 var s = ""; 679 var asXml = function(n) { 680 var s = ""; 681 if (n.nodeType == 1) { 682 s += "<" + n.nodeName; 683 for (var i = 0; i < n.attributes.length; i++) 684 s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue || "").toString() + "\""; 685 if (n.firstChild) { 686 s += ">"; 687 for (var c = n.firstChild; c; c = c.nextSibling) 688 s += asXml(c); 689 s += "</" + n.nodeName + ">"; 690 } 691 else 692 s += "/>"; 693 } 694 else if (n.nodeType == 3) 695 s += n.nodeValue; 696 else if (n.nodeType == 4) 697 s += "<![CDATA[" + n.nodeValue + "]]>"; 698 return s; 699 }; 700 for (var c = node.firstChild; c; c = c.nextSibling) 701 s += asXml(c); 702 return s; 703 }, 704 escape: function(txt) { 705 return txt.replace(/[\\]/g, "\\\\") 706 .replace(/[\"]/g, '\\"') 707 .replace(/[\n]/g, '\\n') 708 .replace(/[\r]/g, '\\r'); 709 }, 710 removeWhite: function(e) { 711 e.normalize(); 712 for (var n = e.firstChild; n;) { 713 if (n.nodeType == 3) { 714 // text node 715 if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { 716 // pure whitespace text node 717 var nxt = n.nextSibling; 718 e.removeChild(n); 719 n = nxt; 720 } 721 else 722 n = n.nextSibling; 723 } 724 else if (n.nodeType == 1) { 725 // element node 726 X.removeWhite(n); 727 n = n.nextSibling; 728 } 729 else 730 // any other node 731 n = n.nextSibling; 732 } 733 return e; 734 } 735 }; 736 if (xml.nodeType == 9) 737 // document node 738 xml = xml.documentElement; 739 var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 740 return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 741 }, 742 743 /* This work is licensed under Creative Commons GNU LGPL License. 744 745 License: http://creativecommons.org/licenses/LGPL/2.1/ 746 Version: 0.9 747 Author: Stefan Goessner/2006 748 Web: http://goessner.net/ 749 */ 750 json2xml: function(o, tab) { 751 var toXml = function(v, name, ind) { 752 var xml = ""; 753 if (v instanceof Array) { 754 for (var i = 0, n = v.length; i < n; i++) 755 xml += ind + toXml(v[i], name, ind + "\t") + "\n"; 756 } 757 else if (typeof(v) == "object") { 758 var hasChild = false; 759 xml += ind + "<" + name; 760 for (var m in v) { 761 if (m.charAt(0) == "@") 762 xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\""; 763 else 764 hasChild = true; 765 } 766 xml += hasChild ? ">": "/>"; 767 if (hasChild) { 768 for (var m in v) { 769 if (m == "#text") 770 xml += v[m]; 771 else if (m == "#cdata") 772 xml += "<![CDATA[" + v[m] + "]]>"; 773 else if (m.charAt(0) != "@") 774 xml += toXml(v[m], m, ind + "\t"); 775 } 776 xml += (xml.charAt(xml.length - 1) == "\n" ? ind: "") + "</" + name + ">"; 777 } 778 } 779 else { 780 xml += ind + "<" + name + ">" + v.toString() + "</" + name + ">"; 781 } 782 return xml; 783 }, 784 xml = ""; 785 for (var m in o) 786 xml += toXml(o[m], m, ""); 787 return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 788 } 789 }; 790 })(); 791 792 window.finesse = window.finesse || {}; 793 window.finesse.Converter = Converter; 794 795 return Converter; 796 }); 797 798 /** 799 * SaxParser.js: provides a simple SAX parser 800 * 801 * NONVALIDATING - this will not validate whether you have valid XML or not. It will simply report what it finds. 802 * Only supports elements, attributes, and text. No comments, cdata, processing instructions, etc. 803 */ 804 805 /** 806 * @requires 807 * @ignore 808 */ 809 // Add SaxParser to the finesse.utilities namespace 810 define('utilities/SaxParser',[], function () { 811 var SaxParser = { 812 parse: function(xml, callback) { 813 // Event callbacks 814 /** @private */ 815 var triggerEvent = function (type, data) { 816 callback.call(null, type, data); 817 }, 818 /** @private */ 819 triggerStartElement = function (name) { 820 triggerEvent("StartElement", name); 821 }, 822 /** @private */ 823 triggerEndElement = function (name) { 824 triggerEvent("EndElement", name); 825 }, 826 /** @private */ 827 triggerAttribute = function (name, value) { 828 triggerEvent("Attribute", { "name": name, "value": value }); 829 }, 830 /** @private */ 831 triggerText = function (text) { 832 triggerEvent("Text", text); 833 }, 834 835 // Parsing 836 cursor = 0, 837 xmlLength = xml.length, 838 whitespaceRegex = /^[ \t\r\n]*$/, 839 /** @private */ 840 isWhitespace = function (text) { 841 return whitespaceRegex.test(text); 842 }, 843 /** @private */ 844 moveToNonWhitespace = function () { 845 while (isWhitespace(xml.charAt(cursor))) { 846 cursor += 1; 847 } 848 }, 849 /** @private */ 850 parseAttribute = function () { 851 var nameBuffer = [], 852 valueBuffer = [], 853 valueIsQuoted = false, 854 cursorChar = ""; 855 856 nameBuffer.push(xml.charAt(cursor)); 857 858 // Get the name 859 cursor += 1; 860 while (cursor < xmlLength) { 861 cursorChar = xml.charAt(cursor); 862 if (isWhitespace(cursorChar) || cursorChar === "=") { 863 // Move on to gathering value 864 break; 865 } 866 else { 867 nameBuffer.push(cursorChar); 868 } 869 cursor += 1; 870 } 871 872 // Skip the equals sign and any whitespace 873 moveToNonWhitespace(); 874 if (cursorChar === "=") { 875 cursor += 1; 876 } else { 877 throw new Error("Did not find = following attribute name at " + cursor); 878 } 879 moveToNonWhitespace(); 880 881 // Get the value 882 valueIsQuoted = cursor !== xmlLength - 1 ? xml.charAt(cursor) === "\"": false; 883 if (valueIsQuoted) { 884 cursor += 1; 885 while (cursor < xmlLength) { 886 cursorChar = xml.charAt(cursor); 887 if (cursorChar === "\"") { 888 // Found the closing quote, so end value 889 triggerAttribute(nameBuffer.join(""), valueBuffer.join("")); 890 break; 891 } 892 else { 893 valueBuffer.push(cursorChar); 894 } 895 cursor += 1; 896 } 897 } 898 else { 899 throw new Error("Found unquoted attribute value at " + cursor); 900 } 901 }, 902 /** @private */ 903 parseEndElement = function () { 904 var elementNameBuffer = [], 905 cursorChar = ""; 906 cursor += 2; 907 while (cursor < xmlLength) { 908 cursorChar = xml.charAt(cursor); 909 if (cursorChar === ">") { 910 triggerEndElement(elementNameBuffer.join("")); 911 break; 912 } 913 else { 914 elementNameBuffer.push(cursorChar); 915 } 916 cursor += 1; 917 } 918 }, 919 /** @private */ 920 parseReference = function() { 921 var type, 922 TYPE_DEC_CHAR_REF = 1, 923 TYPE_HEX_CHAR_REF = 2, 924 TYPE_ENTITY_REF = 3, 925 buffer = ""; 926 cursor += 1; 927 // Determine the type of reference. 928 if (xml.charAt(cursor) === "#") { 929 cursor += 1; 930 if (xml.charAt(cursor) === "x") { 931 type = TYPE_HEX_CHAR_REF; 932 cursor += 1; 933 } else { 934 type = TYPE_DEC_CHAR_REF; 935 } 936 } else { 937 type = TYPE_ENTITY_REF; 938 } 939 // Read the reference into a buffer. 940 while (xml.charAt(cursor) !== ";") { 941 buffer += xml.charAt(cursor); 942 cursor += 1; 943 if (cursor >= xmlLength) { 944 throw new Error("Unterminated XML reference: " + buffer); 945 } 946 } 947 // Convert the reference to the appropriate character. 948 switch (type) { 949 case TYPE_DEC_CHAR_REF: 950 return String.fromCharCode(parseInt(buffer, 10)); 951 case TYPE_HEX_CHAR_REF: 952 return String.fromCharCode(parseInt(buffer, 16)); 953 case TYPE_ENTITY_REF: 954 switch (buffer) { 955 case "amp": 956 return "&"; 957 case "lt": 958 return "<"; 959 case "gt": 960 return ">"; 961 case "apos": 962 return "'"; 963 case "quot": 964 return "\""; 965 default: 966 throw new Error("Invalid XML entity reference: " + buffer); 967 } 968 // break; (currently unreachable) 969 } 970 }, 971 /** @private */ 972 parseElement = function () { 973 var elementNameBuffer = [], 974 textBuffer = [], 975 cursorChar = "", 976 whitespace = false; 977 978 // Get element name 979 cursor += 1; 980 while (cursor < xmlLength) { 981 cursorChar = xml.charAt(cursor); 982 whitespace = isWhitespace(cursorChar); 983 if (!whitespace && cursorChar !== "/" && cursorChar !== ">") { 984 elementNameBuffer.push(cursorChar); 985 } 986 else { 987 elementNameBuffer = elementNameBuffer.join(""); 988 triggerStartElement(elementNameBuffer); 989 break; 990 } 991 cursor += 1; 992 } 993 994 // Get attributes 995 if (whitespace) { 996 while (cursor < xmlLength) { 997 moveToNonWhitespace(); 998 cursorChar = xml.charAt(cursor); 999 if (cursorChar !== "/" && cursorChar !== ">") { 1000 // Start of attribute 1001 parseAttribute(); 1002 } 1003 cursorChar = xml.charAt(cursor); 1004 if (cursorChar === "/" || cursorChar === ">") { 1005 break; 1006 } 1007 else { 1008 cursor += 1; 1009 } 1010 } 1011 } 1012 1013 // End tag if "/>" was found, 1014 // otherwise we're at the end of the start tag and have to parse into it 1015 if (cursorChar === "/") { 1016 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === ">") { 1017 cursor += 1; 1018 triggerEndElement(elementNameBuffer); 1019 } 1020 } 1021 else { 1022 // cursor is on ">", so parse into element content. Assume text until we find a "<", 1023 // which could be a child element or the current element's end tag. We do not support 1024 // mixed content of text and elements as siblings unless the text is only whitespace. 1025 // Text cannot contain <, >, ", or &. They should be <, >, ", & respectively. 1026 cursor += 1; 1027 while (cursor < xmlLength) { 1028 cursorChar = xml.charAt(cursor); 1029 if (cursorChar === "<") { 1030 // Determine if end tag or element 1031 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === "/") { 1032 // At end tag 1033 textBuffer = textBuffer.join(""); 1034 if (!isWhitespace(textBuffer)) { 1035 triggerText(textBuffer); 1036 } 1037 parseEndElement(); 1038 break; 1039 } 1040 else { 1041 // At start tag 1042 textBuffer = textBuffer.join(""); 1043 if (!isWhitespace(textBuffer)) { 1044 triggerText(textBuffer); 1045 } 1046 parseElement(); 1047 textBuffer = []; 1048 } 1049 } else if (cursorChar === "&") { 1050 textBuffer.push(parseReference()); 1051 } 1052 else { 1053 textBuffer.push(cursorChar); 1054 } 1055 cursor += 1; 1056 } 1057 } 1058 }, 1059 /** @private */ 1060 skipXmlDeclaration = function() { 1061 if (xml.substr(0, 5) === "<?xml" && isWhitespace(xml.charAt(5))) { 1062 cursor = xml.indexOf(">") + 1; 1063 } 1064 moveToNonWhitespace(); 1065 }; 1066 1067 // Launch. 1068 skipXmlDeclaration(); 1069 parseElement(); 1070 } 1071 }; 1072 1073 window.finesse = window.finesse || {}; 1074 window.finesse.utilities = window.finesse.utilities || {}; 1075 window.finesse.utilities.SaxParser = SaxParser; 1076 1077 return SaxParser; 1078 }); 1079 1080 /** 1081 * Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> 1082 * ?? 2011 Colin Snover <http://zetafleet.com> 1083 * Released under MIT license. 1084 */ 1085 define('utilities/../../thirdparty/util/iso8601',[], function () { 1086 (function (Date, undefined) { 1087 var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 1088 /** @private **/ 1089 Date.parse = function (date) { 1090 var timestamp, struct, minutesOffset = 0; 1091 1092 // ES5 ??15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 1093 // before falling back to any implementation-specific date parsing, so that???s what we do, even if native 1094 // implementations could be faster 1095 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ?? 10 tzHH 11 tzmm 1096 if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { 1097 // avoid NaN timestamps caused by ???undefined??? values being passed to Date.UTC 1098 for (var i = 0, k; (k = numericKeys[i]); ++i) { 1099 struct[k] = +struct[k] || 0; 1100 } 1101 1102 // allow undefined days and months 1103 struct[2] = (+struct[2] || 1) - 1; 1104 struct[3] = +struct[3] || 1; 1105 1106 if (struct[8] !== 'Z' && struct[9] !== undefined) { 1107 minutesOffset = struct[10] * 60 + struct[11]; 1108 1109 if (struct[9] === '+') { 1110 minutesOffset = 0 - minutesOffset; 1111 } 1112 } 1113 1114 timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); 1115 } 1116 else { 1117 timestamp = origParse ? origParse(date) : NaN; 1118 } 1119 1120 return timestamp; 1121 }; 1122 }(Date)); 1123 }); 1124 1125 /*! 1126 Math.uuid.js (v1.4) 1127 http://www.broofa.com 1128 mailto:robert@broofa.com 1129 1130 Copyright (c) 2010 Robert Kieffer 1131 Dual licensed under the MIT and GPL licenses. 1132 */ 1133 1134 /* 1135 * Generate a random uuid. 1136 * 1137 * USAGE: Math.uuid(length, radix) 1138 * length - the desired number of characters 1139 * radix - the number of allowable values for each character. 1140 * 1141 * EXAMPLES: 1142 * // No arguments - returns RFC4122, version 4 ID 1143 * >>> Math.uuid() 1144 * "92329D39-6F5C-4520-ABFC-AAB64544E172" 1145 * 1146 * // One argument - returns ID of the specified length 1147 * >>> Math.uuid(15) // 15 character ID (default base=62) 1148 * "VcydxgltxrVZSTV" 1149 * 1150 * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) 1151 * >>> Math.uuid(8, 2) // 8 character ID (base=2) 1152 * "01001010" 1153 * >>> Math.uuid(8, 10) // 8 character ID (base=10) 1154 * "47473046" 1155 * >>> Math.uuid(8, 16) // 8 character ID (base=16) 1156 * "098F4D35" 1157 */ 1158 define('utilities/../../thirdparty/util/Math.uuid',[], function () { 1159 (function() { 1160 // Private array of chars to use 1161 var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 1162 1163 /** @private **/ 1164 Math.uuid = function (len, radix) { 1165 var chars = CHARS, uuid = [], i; 1166 radix = radix || chars.length; 1167 1168 if (len) { 1169 // Compact form 1170 for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 1171 } else { 1172 // rfc4122, version 4 form 1173 var r; 1174 1175 // rfc4122 requires these characters 1176 uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 1177 uuid[14] = '4'; 1178 1179 // Fill in random data. At i==19 set the high bits of clock sequence as 1180 // per rfc4122, sec. 4.1.5 1181 for (i = 0; i < 36; i++) { 1182 if (!uuid[i]) { 1183 r = 0 | Math.random()*16; 1184 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1185 } 1186 } 1187 } 1188 1189 return uuid.join(''); 1190 }; 1191 1192 // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 1193 // by minimizing calls to random() 1194 /** @private **/ 1195 Math.uuidFast = function() { 1196 var chars = CHARS, uuid = new Array(36), rnd=0, r; 1197 for (var i = 0; i < 36; i++) { 1198 if (i==8 || i==13 || i==18 || i==23) { 1199 uuid[i] = '-'; 1200 } else if (i==14) { 1201 uuid[i] = '4'; 1202 } else { 1203 if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 1204 r = rnd & 0xf; 1205 rnd = rnd >> 4; 1206 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1207 } 1208 } 1209 return uuid.join(''); 1210 }; 1211 1212 // A more compact, but less performant, RFC4122v4 solution: 1213 /** @private **/ 1214 Math.uuidCompact = function() { 1215 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 1216 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 1217 return v.toString(16); 1218 }); 1219 }; 1220 })(); 1221 }); 1222 1223 /** 1224 * The following comment prevents JSLint errors concerning undefined global variables. 1225 * It tells JSLint that these identifiers are defined elsewhere. 1226 */ 1227 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true, plusplus: true, unparam: true, forin: true */ 1228 1229 /** The following comment is to prevent jslint errors about 1230 * using variables before they are defined. 1231 */ 1232 /*global $, _prefs,_uiMsg,ciscowidgets,dojo,finesse,gadgets,hostUrl, Handlebars */ 1233 1234 /** 1235 * A collection of utility functions. 1236 * 1237 * @requires finesse.Converter 1238 */ 1239 define('utilities/Utilities',[ 1240 "../../thirdparty/util/converter", 1241 "./SaxParser", 1242 "../../thirdparty/util/iso8601", 1243 "../../thirdparty/util/Math.uuid" 1244 ], 1245 function (Converter, SaxParser) { 1246 var Utilities = /** @lends finesse.utilities.Utilities */ { 1247 /** 1248 * @private 1249 */ 1250 CONSTANTS: { 1251 CACHED_REST_DATA: 'CACHED_REST_DATA', 1252 RELOAD_OTHER_SIDE: 'RELOAD_OTHER_SIDE', 1253 LOG_COLL_DATA: 'LOG_COLL_DATA', 1254 CHANGED_LAYOUT_STRUCTURE: 'CHANGED_LAYOUT_STRUCTURE', 1255 PREVIOUS_LAYOUT_HASH_ID: 'PREVIOUS_LAYOUT_HASH_ID', 1256 PREVIOUS_CD_LAYOUT: 'PREVIOUS_CD_LAYOUT', 1257 MESSAGES_DATA: 'MESSAGES_DATA', 1258 USER_ID: 'USER_ID' 1259 }, 1260 IDB_ACTIONS: { 1261 SAVE_OR_UPDATE: 'saveOrUpdate', 1262 FETCH: 'fetch', 1263 DELETE: 'delete', 1264 CLEAR_ALL: 'clear' 1265 }, 1266 IDB_TOPICS: { 1267 REQUEST: 'finesse.idb.request', 1268 RESPONSE: 'finesse.idb.response' 1269 }, 1270 /** 1271 * @class 1272 * Utilities is collection of utility methods. 1273 * 1274 * @augments finesse.restservices.RestBase 1275 * @see finesse.restservices.Contacts 1276 * @constructs 1277 */ 1278 _fakeConstuctor: function () { 1279 /* This is here for jsdocs. */ 1280 }, 1281 1282 /** 1283 * @private 1284 * Retrieves the specified item from window.localStorage 1285 * @param {String} key 1286 * The key of the item to retrieve 1287 * @returns {String} 1288 * The string with the value of the retrieved item; returns 1289 * what the browser would return if not found (typically null or undefined) 1290 * Returns false if window.localStorage feature is not even found. 1291 */ 1292 getDOMStoreItem: function (key) { 1293 var store = window.localStorage; 1294 if (store) { 1295 return store.getItem(key); 1296 } 1297 }, 1298 1299 /** 1300 * @private 1301 * Sets an item into window.localStorage 1302 * @param {String} key 1303 * The key for the item to set 1304 * @param {String} value 1305 * The value to set 1306 * @returns {Boolean} 1307 * True if successful, false if window.localStorage is 1308 * not even found. 1309 */ 1310 setDOMStoreItem: function (key, value) { 1311 var store = window.localStorage; 1312 if (store) { 1313 store.setItem(key, value); 1314 return true; 1315 } 1316 return false; 1317 }, 1318 1319 /** 1320 * @private 1321 * Removes a particular item from window.localStorage 1322 * @param {String} key 1323 * The key of the item to remove 1324 * @returns {Boolean} 1325 * True if successful, false if not 1326 * Returns false if window.localStorage feature is not even found. 1327 */ 1328 removeDOMStoreItem: function (key) { 1329 var store = window.localStorage; 1330 if (store) { 1331 store.removeItem(key); 1332 return true; 1333 } 1334 return false; 1335 }, 1336 1337 /** 1338 * @private 1339 * Dumps all the contents of window.localStorage 1340 * @returns {Boolean} 1341 * True if successful, false if not. 1342 * Returns false if window.localStorage feature is not even found. 1343 */ 1344 clearDOMStore: function () { 1345 var store = window.localStorage; 1346 if (store) { 1347 store.clear(); 1348 return true; 1349 } 1350 return false; 1351 }, 1352 1353 /** 1354 * @private 1355 * Creates a message listener for window.postMessage messages. 1356 * @param {Function} callback 1357 * The callback that will be invoked with the message. The callback 1358 * is responsible for any security checks. 1359 * @param {String} [origin] 1360 * The origin to check against for security. Allows all messages 1361 * if no origin is provided. 1362 * @returns {Function} 1363 * The callback function used to register with the message listener. 1364 * This is different than the one provided as a parameter because 1365 * the function is overloaded with origin checks. 1366 * @throws {Error} If the callback provided is not a function. 1367 */ 1368 receiveMessage: function (callback, origin) { 1369 if (typeof callback !== "function") { 1370 throw new Error("Callback is not a function."); 1371 } 1372 1373 //Create a function closure to perform origin check. 1374 /** @private */ 1375 var cb = function (e) { 1376 // If an origin check is requested (provided), we'll only invoke the callback if it passes 1377 if (typeof origin !== "string" || (typeof origin === "string" && typeof e.origin === "string" && e.origin.toLowerCase() === origin.toLowerCase())) { 1378 callback(e); 1379 } 1380 }; 1381 1382 if (window.addEventListener) { //Firefox, Opera, Chrome, Safari 1383 window.addEventListener("message", cb, false); 1384 } else { //Internet Explorer 1385 window.attachEvent("onmessage", cb); 1386 } 1387 1388 //Return callback used to register with listener so that invoker 1389 //could use it to remove. 1390 return cb; 1391 }, 1392 1393 /** 1394 * Funtion to return if the browser is IE or Edge 1395 */ 1396 isIEorEdge: function () { 1397 var isIE = navigator.userAgent.indexOf('Trident') !== -1 || navigator.userAgent.indexOf('MSIE ') > 0; 1398 var isEDGE = navigator.appName === 'Netscape' && navigator.appVersion.indexOf('Edge') > -1; 1399 return (isIE || isEDGE); 1400 }, 1401 1402 /** 1403 * @private 1404 * Sends a message to a target frame using window.postMessage. 1405 * @param {Function} message 1406 * Message to be sent to target frame. 1407 * @param {Object} [target="parent"] 1408 * An object reference to the target frame. Default us the parent. 1409 * @param {String} [targetOrigin="*"] 1410 * The URL of the frame this frame is sending the message to. 1411 */ 1412 sendMessage: function (message, target, targetOrigin) { 1413 //Default to any target URL if none is specified. 1414 targetOrigin = targetOrigin || "*"; 1415 1416 //Default to parent target if none is specified. 1417 target = target || parent; 1418 1419 //Ensure postMessage is supported by browser before invoking. 1420 if (window.postMessage) { 1421 target.postMessage(message, targetOrigin); 1422 } 1423 }, 1424 1425 /** 1426 * Returns the passed in handler, if it is a function. 1427 * @param {Function} handler 1428 * The handler to validate 1429 * @returns {Function} 1430 * The provided handler if it is valid 1431 * @throws Error 1432 * If the handler provided is invalid 1433 */ 1434 validateHandler: function (handler) { 1435 if (handler === undefined || typeof handler === "function") { 1436 return handler; 1437 } else { 1438 throw new Error("handler must be a function"); 1439 } 1440 }, 1441 1442 /** 1443 * @private 1444 * Tries to get extract the AWS error code from a 1445 * finesse.clientservices.ClientServices parsed error response object. 1446 * @param {Object} rsp 1447 * The handler to validate 1448 * @returns {String} 1449 * The error code, HTTP status code, or undefined 1450 */ 1451 getErrCode: function (rsp) { 1452 try { // Best effort to get the error code 1453 return rsp.object.ApiErrors.ApiError.ErrorType; 1454 } catch (e) { // Second best effort to get the HTTP Status code 1455 if (rsp && rsp.status) { 1456 return "HTTP " + rsp.status; 1457 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1458 return "Request could not be completed"; 1459 } 1460 } // Otherwise, don't return anything (undefined) 1461 }, 1462 1463 /** 1464 * @private 1465 * Tries to get extract the AWS error data from a 1466 * finesse.clientservices.ClientServices parsed error response object. 1467 * @param {Object} rsp 1468 * The handler to validate 1469 * @returns {String} 1470 * The error data, HTTP status code, or undefined 1471 */ 1472 getErrData: function (rsp) { 1473 try { // Best effort to get the error data 1474 return rsp.object.ApiErrors.ApiError.ErrorData; 1475 } catch (e) { // Second best effort to get the HTTP Status code 1476 if (rsp && rsp.status) { 1477 return "HTTP " + rsp.status; 1478 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1479 return "Request could not be completed"; 1480 } 1481 } // Otherwise, don't return anything (undefined) 1482 }, 1483 1484 /** 1485 * @private 1486 * Tries to get extract the AWS error message from a 1487 * finesse.clientservices.ClientServices parsed error response object. 1488 * @param {Object} rsp 1489 * The handler to validate 1490 * @returns {String} 1491 * The error message, HTTP status code, or undefined 1492 */ 1493 getErrMessage: function (rsp) { 1494 try { // Best effort to get the error message 1495 return rsp.object.ApiErrors.ApiError.ErrorMessage; 1496 } catch (e) { // Second best effort to get the HTTP Status code 1497 if (rsp && rsp.status) { 1498 return "HTTP " + rsp.status; 1499 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1500 return "Request could not be completed"; 1501 } 1502 } // Otherwise, don't return anything (undefined) 1503 }, 1504 1505 /** 1506 * @private 1507 * Tries to get extract the AWS peripheral error code from a 1508 * finesse.clientservices.ClientServices parsed error response object. 1509 * @param {Object} rsp 1510 * The handler to validate 1511 * @returns {String} 1512 * The error message, HTTP status code, or undefined 1513 */ 1514 getPeripheralErrorCode: function(rsp) { 1515 try { 1516 return rsp.object.ApiErrors.ApiError.PeripheralErrorCode 1517 } catch (e) { // Second best effort to get the HTTP Status code 1518 if (rsp && rsp.status) { 1519 return "HTTP " + rsp.status; 1520 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1521 return "Request could not be completed"; 1522 } 1523 } 1524 }, 1525 1526 /** 1527 * @private 1528 * Tries to get extract the AWS overrideable boolean from a 1529 * finesse.clientservices.ClientServices parsed error response object. 1530 * @param {Object} rsp 1531 * The handler to validate 1532 * @returns {String} 1533 * The overrideable boolean, HTTP status code, or undefined 1534 */ 1535 getErrOverrideable: function (rsp) { 1536 try { // Best effort to get the override boolean 1537 return rsp.object.ApiErrors.ApiError.Overrideable; 1538 } catch (e) { // Second best effort to get the HTTP Status code 1539 if (rsp && rsp.status) { 1540 return "HTTP " + rsp.status; 1541 } 1542 } // Otherwise, don't return anything (undefined) 1543 }, 1544 1545 /** 1546 * Trims leading and trailing whitespace from a string. 1547 * @param {String} str 1548 * The string to trim 1549 * @returns {String} 1550 * The trimmed string 1551 */ 1552 trim: function (str) { 1553 return str.replace(/^\s*/, "").replace(/\s*$/, ""); 1554 }, 1555 1556 /** 1557 * Utility method for getting the current time in milliseconds. 1558 * @returns {String} 1559 * The current time in milliseconds 1560 */ 1561 currentTimeMillis : function () { 1562 return (new Date()).getTime(); 1563 }, 1564 1565 /** 1566 * Gets the current drift (between client and server) 1567 * 1568 * @returns {integer} which is the current drift (last calculated; 0 if we have not calculated yet) 1569 */ 1570 getCurrentDrift : function () { 1571 var drift; 1572 1573 //Get the current client drift from localStorage 1574 drift = window.sessionStorage.getItem("clientTimestampDrift"); 1575 if (drift) { 1576 drift = parseInt(drift, 10); 1577 if (isNaN(drift)) { 1578 drift = 0; 1579 } 1580 } 1581 return drift; 1582 }, 1583 1584 /** 1585 * Converts the specified clientTime to server time by adjusting by the current drift. 1586 * 1587 * @param clientTime is the time in milliseconds 1588 * @returns {int} serverTime in milliseconds 1589 */ 1590 convertToServerTimeMillis : function(clientTime) { 1591 var drift = this.getCurrentDrift(); 1592 return (clientTime + drift); 1593 }, 1594 1595 /** 1596 * Utility method for getting the current time, 1597 * adjusted by the calculated "drift" to closely 1598 * approximate the server time. This is used 1599 * when calculating durations based on a server 1600 * timestamp, which otherwise can produce unexpected 1601 * results if the times on client and server are 1602 * off. 1603 * 1604 * @returns {String} 1605 * The current server time in milliseconds 1606 */ 1607 currentServerTimeMillis : function () { 1608 var drift = this.getCurrentDrift(); 1609 return (new Date()).getTime() + drift; 1610 }, 1611 1612 /** 1613 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds. 1614 * 1615 * @param timeInMs is the time in milliseconds 1616 * @returns {String} which corresponds to minutes and seconds (e.g. 11:23) 1617 */ 1618 buildTimeString : function (timeInMs) { 1619 var min, sec, timeStr = "00:00"; 1620 1621 if (timeInMs && timeInMs !== "-1") { 1622 // calculate minutes, and seconds 1623 min = this.pad(Math.floor(timeInMs / 60000)); 1624 sec = this.pad(Math.floor((timeInMs % 60000) / 1000)); 1625 1626 // construct MM:SS time string 1627 timeStr = min + ":" + sec; 1628 } 1629 return timeStr; 1630 }, 1631 1632 /** 1633 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds (and optionally hours) 1634 * 1635 * @param timeInMs is the time in milliseconds 1636 * @returns {String} which corresponds to hours, minutes and seconds (e.g. 01:11:23 or 11:23) 1637 */ 1638 buildTimeStringWithOptionalHours: function (timeInMs) { 1639 var hour, min, sec, timeStr = "00:00", optionalHour = "", timeInSecs; 1640 1641 if (timeInMs && timeInMs !== "-1") { 1642 timeInSecs = timeInMs / 1000; 1643 1644 // calculate {hours}, minutes, and seconds 1645 hour = this.pad(Math.floor(timeInSecs / 3600)); 1646 min = this.pad(Math.floor((timeInSecs % 3600) / 60)); 1647 sec = this.pad(Math.floor((timeInSecs % 3600) % 60)); 1648 1649 //Optionally add the hour if we have hours 1650 if (hour > 0) { 1651 optionalHour = hour + ":"; 1652 } 1653 1654 // construct MM:SS time string (or optionally HH:MM:SS) 1655 timeStr = optionalHour + min + ":" + sec; 1656 } 1657 return timeStr; 1658 }, 1659 1660 1661 /** 1662 * @private 1663 * Builds a string which specifies the amount of time user has been in this state (e.g. 11:23). 1664 * 1665 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1666 * @param stateStartTimeInMs is integer argument which specifies time call entered current state 1667 * @returns {String} which is the elapsed time (MINUTES:SECONDS) 1668 * 1669 */ 1670 buildElapsedTimeString : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1671 var result, delta; 1672 1673 result = "--:--"; 1674 if (stateStartTimeInMs !== 0) { 1675 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1676 1677 if (delta > 0) { 1678 result = this.buildTimeString(delta); 1679 } 1680 } 1681 return result; 1682 }, 1683 1684 /** 1685 * @private 1686 * Builds a string which specifies the amount of time user has been in this state with optional hours (e.g. 01:11:23 or 11:23). 1687 * 1688 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1689 * @param startTimeInMs is integer argument which specifies the start time 1690 * @returns {String} which is the elapsed time (MINUTES:SECONDS) or (HOURS:MINUTES:SECONDS) 1691 * 1692 */ 1693 buildElapsedTimeStringWithOptionalHours : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1694 var result, delta; 1695 1696 result = "--:--"; 1697 if (stateStartTimeInMs !== 0) { 1698 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1699 1700 if (delta > 0) { 1701 result = this.buildTimeStringWithOptionalHours(delta); 1702 } 1703 } 1704 return result; 1705 }, 1706 1707 1708 /** 1709 * Builds a string which displays the total call time in minutes and seconds. 1710 * 1711 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1712 * @param callStartTimeInMs is integer argument which specifies time the call started 1713 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1714 */ 1715 buildTotalTimeString : function (adjustedServerTimeInMs, callStartTimeInMs) { 1716 return this.buildElapsedTimeString(adjustedServerTimeInMs, callStartTimeInMs); 1717 }, 1718 1719 /** 1720 * @private 1721 * Builds a string which displays the hold time in minutes and seconds. 1722 * 1723 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1724 * @param holdStartTimeInMs is integer argument which specifies time the hold started 1725 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1726 */ 1727 buildHoldTimeString : function (adjustedServerTimeInMs, holdStartTimeInMs) { 1728 return this.buildElapsedTimeString(adjustedServerTimeInMs, holdStartTimeInMs); 1729 }, 1730 1731 /** 1732 * @private 1733 * Builds a string which displays the elapsed time the call has been in wrap up. 1734 * 1735 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1736 * @param wrapupStartTimeInMs is integer argument which specifies time call entered wrapup state 1737 * @returns {String} which is the elapsed wrapup time 1738 * 1739 */ 1740 buildWrapupTimeString : function (adjustedServerTimeInMs, wrapupStartTimeInMs) { 1741 return this.buildElapsedTimeString(adjustedServerTimeInMs, wrapupStartTimeInMs); 1742 }, 1743 1744 /** 1745 * Extracts a time from the timeStr. Note: The timeStr could be empty. In this case, the time returned will be 0. 1746 * @param timeStr is a time string in ISO8601 format (note: could be empty) 1747 * @returns {long} is the time 1748 */ 1749 extractTime : function (timeStr) { 1750 var result = 0, theDate; 1751 if (timeStr === "") { 1752 result = 0; 1753 } else if (timeStr === null) { 1754 result = 0; 1755 } else { 1756 theDate = this.parseDateStringISO8601(timeStr); 1757 result = theDate.getTime(); 1758 } 1759 return result; 1760 }, 1761 1762 /** 1763 * @private 1764 * Generates an RFC1422v4-compliant UUID using pesudorandom numbers. 1765 * @returns {String} 1766 * An RFC1422v4-compliant UUID using pesudorandom numbers. 1767 **/ 1768 generateUUID: function () { 1769 return Math.uuidCompact(); 1770 }, 1771 1772 /** @private */ 1773 xml2json: finesse.Converter.xml2json, 1774 1775 1776 /** 1777 * @private 1778 * Utility method to get the JSON parser either from gadgets.json 1779 * or from window.JSON (which will be initialized by CUIC if 1780 * browser doesn't support 1781 */ 1782 getJSONParser: function() { 1783 var _container = window.gadgets || {}, 1784 parser = _container.json || window.JSON; 1785 return parser; 1786 }, 1787 1788 /** 1789 * @private 1790 * Utility method to convert a javascript object to XML. 1791 * @param {Object} object 1792 * The object to convert to XML. 1793 * @param {Boolean} escapeFlag 1794 * If escapeFlag evaluates to true: 1795 * - XML escaping is done on the element values. 1796 * - Attributes, #cdata, and #text is not supported. 1797 * - The XML is unformatted (no whitespace between elements). 1798 * If escapeFlag evaluates to false: 1799 * - Element values are written 'as is' (no escaping). 1800 * - Attributes, #cdata, and #text is supported. 1801 * - The XML is formatted. 1802 * @returns The XML string. 1803 */ 1804 json2xml: function (object, escapeFlag) { 1805 var xml; 1806 if (escapeFlag) { 1807 xml = this._json2xmlWithEscape(object); 1808 } 1809 else { 1810 xml = finesse.Converter.json2xml(object, '\t'); 1811 } 1812 return xml; 1813 }, 1814 1815 /** 1816 * @private 1817 * Utility method to convert XML string into javascript object. 1818 */ 1819 xml2JsObj : function (event) { 1820 var parser = this.getJSONParser(); 1821 return parser.parse(finesse.Converter.xml2json(jQuery.parseXML(event), "")); 1822 }, 1823 1824 /** 1825 * @private 1826 * Utility method to convert an XML string to a javascript object. 1827 * @desc This function calls to the SAX parser and responds to callbacks 1828 * received from the parser. Entity translation is not handled here. 1829 * @param {String} xml 1830 * The XML to parse. 1831 * @returns The javascript object. 1832 */ 1833 xml2js: function (xml) { 1834 var STATES = { 1835 INVALID: 0, 1836 NEW_NODE: 1, 1837 ATTRIBUTE_NODE: 2, 1838 TEXT_NODE: 3, 1839 END_NODE: 4 1840 }, 1841 state = STATES.INVALID, 1842 rootObj = {}, 1843 newObj, 1844 objStack = [rootObj], 1845 nodeName = "", 1846 1847 /** 1848 * @private 1849 * Adds a property to the current top JSO. 1850 * @desc This is also where we make considerations for arrays. 1851 * @param {String} name 1852 * The name of the property to add. 1853 * @param (String) value 1854 * The value of the property to add. 1855 */ 1856 addProperty = function (name, value) { 1857 var current = objStack[objStack.length - 1]; 1858 if(current.hasOwnProperty(name) && current[name] instanceof Array){ 1859 current[name].push(value); 1860 }else if(current.hasOwnProperty(name)){ 1861 current[name] = [current[name], value]; 1862 }else{ 1863 current[name] = value; 1864 } 1865 }, 1866 1867 /** 1868 * @private 1869 * The callback passed to the SAX parser which processes events from 1870 * the SAX parser in order to construct the resulting JSO. 1871 * @param (String) type 1872 * The type of event received. 1873 * @param (String) data 1874 * The data received from the SAX parser. The contents of this 1875 * parameter vary based on the type of event. 1876 */ 1877 xmlFound = function (type, data) { 1878 switch (type) { 1879 case "StartElement": 1880 // Because different node types have different expectations 1881 // of parenting, we don't push another JSO until we know 1882 // what content we're getting 1883 1884 // If we're already in the new node state, we're running 1885 // into a child node. There won't be any text here, so 1886 // create another JSO 1887 if(state === STATES.NEW_NODE){ 1888 newObj = {}; 1889 addProperty(nodeName, newObj); 1890 objStack.push(newObj); 1891 } 1892 state = STATES.NEW_NODE; 1893 nodeName = data; 1894 break; 1895 case "EndElement": 1896 // If we're in the new node state, we've found no content 1897 // set the tag property to null 1898 if(state === STATES.NEW_NODE){ 1899 addProperty(nodeName, null); 1900 }else if(state === STATES.END_NODE){ 1901 objStack.pop(); 1902 } 1903 state = STATES.END_NODE; 1904 break; 1905 case "Attribute": 1906 // If were in the new node state, no JSO has yet been created 1907 // for this node, create one 1908 if(state === STATES.NEW_NODE){ 1909 newObj = {}; 1910 addProperty(nodeName, newObj); 1911 objStack.push(newObj); 1912 } 1913 // Attributes are differentiated from child elements by a 1914 // preceding "@" in the property name 1915 addProperty("@" + data.name, data.value); 1916 state = STATES.ATTRIBUTE_NODE; 1917 break; 1918 case "Text": 1919 // In order to maintain backwards compatibility, when no 1920 // attributes are assigned to a tag, its text contents are 1921 // assigned directly to the tag property instead of a JSO. 1922 1923 // If we're in the attribute node state, then the JSO for 1924 // this tag was already created when the attribute was 1925 // assigned, differentiate this property from a child 1926 // element by naming it "#text" 1927 if(state === STATES.ATTRIBUTE_NODE){ 1928 addProperty("#text", data); 1929 }else{ 1930 addProperty(nodeName, data); 1931 } 1932 state = STATES.TEXT_NODE; 1933 break; 1934 } 1935 }; 1936 SaxParser.parse(xml, xmlFound); 1937 return rootObj; 1938 }, 1939 1940 /** 1941 * @private 1942 * Traverses a plain-old-javascript-object recursively and outputs its XML representation. 1943 * @param {Object} obj 1944 * The javascript object to be converted into XML. 1945 * @returns {String} The XML representation of the object. 1946 */ 1947 js2xml: function (obj) { 1948 var xml = "", i, elem; 1949 1950 if (obj !== null) { 1951 if (obj.constructor === Object) { 1952 for (elem in obj) { 1953 if (obj[elem] === null || typeof(obj[elem]) === 'undefined') { 1954 xml += '<' + elem + '/>'; 1955 } else if (obj[elem].constructor === Array) { 1956 for (i = 0; i < obj[elem].length; i++) { 1957 xml += '<' + elem + '>' + this.js2xml(obj[elem][i]) + '</' + elem + '>'; 1958 } 1959 } else if (elem[0] !== '@') { 1960 if (this.js2xmlObjIsEmpty(obj[elem])) { 1961 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '/>'; 1962 } else if (elem === "#text") { 1963 xml += obj[elem]; 1964 } else { 1965 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '>' + this.js2xml(obj[elem]) + '</' + elem + '>'; 1966 } 1967 } 1968 } 1969 } else { 1970 xml = obj; 1971 } 1972 } 1973 1974 return xml; 1975 }, 1976 1977 /** 1978 * @private 1979 * Utility method called exclusively by js2xml() to find xml attributes. 1980 * @desc Traverses children one layer deep of a javascript object to "look ahead" 1981 * for properties flagged as such (with '@'). 1982 * @param {Object} obj 1983 * The obj to traverse. 1984 * @returns {String} Any attributes formatted for xml, if any. 1985 */ 1986 js2xmlAtt: function (obj) { 1987 var elem; 1988 1989 if (obj !== null) { 1990 if (obj.constructor === Object) { 1991 for (elem in obj) { 1992 if (obj[elem] !== null && typeof(obj[elem]) !== "undefined" && obj[elem].constructor !== Array) { 1993 if (elem[0] === '@'){ 1994 return ' ' + elem.substring(1) + '="' + obj[elem] + '"'; 1995 } 1996 } 1997 } 1998 } 1999 } 2000 2001 return ''; 2002 }, 2003 2004 /** 2005 * @private 2006 * Utility method called exclusively by js2xml() to determine if 2007 * a node has any children, with special logic for ignoring attributes. 2008 * @desc Attempts to traverse the elements in the object while ignoring attributes. 2009 * @param {Object} obj 2010 * The obj to traverse. 2011 * @returns {Boolean} whether or not the JS object is "empty" 2012 */ 2013 js2xmlObjIsEmpty: function (obj) { 2014 var elem; 2015 2016 if (obj !== null) { 2017 if (obj.constructor === Object) { 2018 for (elem in obj) { 2019 if (obj[elem] !== null) { 2020 if (obj[elem].constructor === Array){ 2021 return false; 2022 } 2023 2024 if (elem[0] !== '@'){ 2025 return false; 2026 } 2027 } else { 2028 return false; 2029 } 2030 } 2031 } else { 2032 return false; 2033 } 2034 } 2035 2036 return true; 2037 }, 2038 2039 /** 2040 * Encodes the given string into base64. 2041 *<br> 2042 * <b>NOTE:</b> {input} is assumed to be UTF-8; only the first 2043 * 8 bits of each input element are significant. 2044 * 2045 * @param {String} input 2046 * The string to convert to base64. 2047 * @returns {String} 2048 * The converted string. 2049 */ 2050 b64Encode: function (input) { 2051 var output = "", idx, data, 2052 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 2053 2054 for (idx = 0; idx < input.length; idx += 3) { 2055 data = input.charCodeAt(idx) << 16 | 2056 input.charCodeAt(idx + 1) << 8 | 2057 input.charCodeAt(idx + 2); 2058 2059 //assume the first 12 bits are valid 2060 output += table.charAt((data >>> 18) & 0x003f) + 2061 table.charAt((data >>> 12) & 0x003f); 2062 output += ((idx + 1) < input.length) ? 2063 table.charAt((data >>> 6) & 0x003f) : 2064 "="; 2065 output += ((idx + 2) < input.length) ? 2066 table.charAt(data & 0x003f) : 2067 "="; 2068 } 2069 2070 return output; 2071 }, 2072 2073 /** 2074 * Decodes the given base64 string. 2075 * <br> 2076 * <b>NOTE:</b> output is assumed to be UTF-8; only the first 2077 * 8 bits of each output element are significant. 2078 * 2079 * @param {String} input 2080 * The base64 encoded string 2081 * @returns {String} 2082 * Decoded string 2083 */ 2084 b64Decode: function (input) { 2085 var output = "", idx, h, data, 2086 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 2087 2088 for (idx = 0; idx < input.length; idx += 4) { 2089 h = [ 2090 table.indexOf(input.charAt(idx)), 2091 table.indexOf(input.charAt(idx + 1)), 2092 table.indexOf(input.charAt(idx + 2)), 2093 table.indexOf(input.charAt(idx + 3)) 2094 ]; 2095 2096 data = (h[0] << 18) | (h[1] << 12) | (h[2] << 6) | h[3]; 2097 if (input.charAt(idx + 2) === '=') { 2098 data = String.fromCharCode( 2099 (data >>> 16) & 0x00ff 2100 ); 2101 } else if (input.charAt(idx + 3) === '=') { 2102 data = String.fromCharCode( 2103 (data >>> 16) & 0x00ff, 2104 (data >>> 8) & 0x00ff 2105 ); 2106 } else { 2107 data = String.fromCharCode( 2108 (data >>> 16) & 0x00ff, 2109 (data >>> 8) & 0x00ff, 2110 data & 0x00ff 2111 ); 2112 } 2113 output += data; 2114 } 2115 2116 return output; 2117 }, 2118 2119 /** 2120 * @private 2121 * Extracts the username and the password from the Base64 encoded string. 2122 * @params {String} 2123 * A base64 encoded string containing credentials that (when decoded) 2124 * are colon delimited. 2125 * @returns {Object} 2126 * An object with the following structure: 2127 * {id:string, password:string} 2128 */ 2129 getCredentials: function (authorization) { 2130 var credObj = {}, 2131 credStr = this.b64Decode(authorization), 2132 colonIndx = credStr.indexOf(":"); 2133 2134 //Check to ensure that string is colon delimited. 2135 if (colonIndx === -1) { 2136 throw new Error("String is not colon delimited."); 2137 } 2138 2139 //Extract ID and password. 2140 credObj.id = credStr.substring(0, colonIndx); 2141 credObj.password = credStr.substring(colonIndx + 1); 2142 return credObj; 2143 }, 2144 2145 /** 2146 * Takes a string and removes any spaces within the string. 2147 * @param {String} string 2148 * The string to remove spaces from 2149 * @returns {String} 2150 * The string without spaces 2151 */ 2152 removeSpaces: function (string) { 2153 return string.split(' ').join(''); 2154 }, 2155 2156 /** 2157 * Escapes spaces as encoded " " characters so they can 2158 * be safely rendered by jQuery.text(string) in all browsers. 2159 * 2160 * (Although IE behaves as expected, Firefox collapses spaces if this function is not used.) 2161 * 2162 * @param text 2163 * The string whose spaces should be escaped 2164 * 2165 * @returns 2166 * The string with spaces escaped 2167 */ 2168 escapeSpaces: function (string) { 2169 return string.replace(/\s/g, '\u00a0'); 2170 }, 2171 2172 /** 2173 * Adds a span styled to line break at word edges around the string passed in. 2174 * @param str String to be wrapped in word-breaking style. 2175 * @private 2176 */ 2177 addWordWrapping : function (str) { 2178 return '<span style="word-wrap: break-word;">' + str + '</span>'; 2179 }, 2180 2181 /** 2182 * Takes an Object and determines whether it is an Array or not. 2183 * @param {Object} obj 2184 * The Object in question 2185 * @returns {Boolean} 2186 * true if the object is an Array, else false. 2187 */ 2188 isArray: function (obj) { 2189 return obj.constructor.toString().indexOf("Array") !== -1; 2190 }, 2191 2192 /** 2193 * @private 2194 * Takes a data object and returns an array extracted 2195 * @param {Object} data 2196 * JSON payload 2197 * 2198 * @returns {array} 2199 * extracted array 2200 */ 2201 getArray: function (data) { 2202 if (this.isArray(data)) { 2203 //Return if already an array. 2204 return data; 2205 } else { 2206 //Create an array, iterate through object, and push to array. This 2207 //should only occur with one object, and therefore one obj in array. 2208 var arr = []; 2209 arr.push(data); 2210 return arr; 2211 } 2212 }, 2213 2214 /** 2215 * @private 2216 * Extracts the ID for an entity given the Finesse REST URI. The ID is 2217 * assumed to be the last element in the URI (after the last "/"). 2218 * @param {String} uri 2219 * The Finesse REST URI to extract the ID from. 2220 * @returns {String} 2221 * The ID extracted from the REST URI. 2222 */ 2223 getId: function (uri) { 2224 if (!uri) { 2225 return ""; 2226 } 2227 var strLoc = uri.lastIndexOf("/"); 2228 return uri.slice(strLoc + 1); 2229 }, 2230 2231 /** 2232 * Compares two objects for equality. 2233 * @param {Object} obj1 2234 * First of two objects to compare. 2235 * @param {Object} obj2 2236 * Second of two objects to compare. 2237 */ 2238 getEquals: function (objA, objB) { 2239 var key; 2240 2241 for (key in objA) { 2242 if (objA.hasOwnProperty(key)) { 2243 if (!objA[key]) { 2244 objA[key] = ""; 2245 } 2246 2247 if (typeof objB[key] === 'undefined') { 2248 return false; 2249 } 2250 if (typeof objB[key] === 'object') { 2251 if (!objB[key].equals(objA[key])) { 2252 return false; 2253 } 2254 } 2255 if (objB[key] !== objA[key]) { 2256 return false; 2257 } 2258 } 2259 } 2260 return true; 2261 }, 2262 2263 /** 2264 * @private 2265 * Regular expressions used in translating HTML and XML entities 2266 */ 2267 ampRegEx : new RegExp('&', 'gi'), 2268 ampEntityRefRegEx : new RegExp('&', 'gi'), 2269 ltRegEx : new RegExp('<', 'gi'), 2270 ltEntityRefRegEx : new RegExp('<', 'gi'), 2271 gtRegEx : new RegExp('>', 'gi'), 2272 gtEntityRefRegEx : new RegExp('>', 'gi'), 2273 xmlSpecialCharRegEx: new RegExp('[&<>"\']', 'g'), 2274 entityRefRegEx: new RegExp('&[^;]+(?:;|$)', 'g'), 2275 2276 /** 2277 * Translates between special characters and HTML entities 2278 * 2279 * @param text 2280 * The text to translate 2281 * 2282 * @param makeEntityRefs 2283 * If true, encode special characters as HTML entities; if 2284 * false, decode HTML entities back to special characters 2285 * 2286 * @private 2287 */ 2288 translateHTMLEntities: function (text, makeEntityRefs) { 2289 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2290 if (makeEntityRefs) { 2291 text = text.replace(this.ampRegEx, '&'); 2292 text = text.replace(this.ltRegEx, '<'); 2293 text = text.replace(this.gtRegEx, '>'); 2294 } else { 2295 text = text.replace(this.gtEntityRefRegEx, '>'); 2296 text = text.replace(this.ltEntityRefRegEx, '<'); 2297 text = text.replace(this.ampEntityRefRegEx, '&'); 2298 } 2299 } 2300 2301 return text; 2302 }, 2303 2304 /** 2305 * Translates between special characters and XML entities 2306 * 2307 * @param text 2308 * The text to translate 2309 * 2310 * @param makeEntityRefs 2311 * If true, encode special characters as XML entities; if 2312 * false, decode XML entities back to special characters 2313 * 2314 * @private 2315 */ 2316 translateXMLEntities: function (text, makeEntityRefs) { 2317 /** @private */ 2318 var escape = function (character) { 2319 switch (character) { 2320 case "&": 2321 return "&"; 2322 case "<": 2323 return "<"; 2324 case ">": 2325 return ">"; 2326 case "'": 2327 return "'"; 2328 case "\"": 2329 return """; 2330 default: 2331 return character; 2332 } 2333 }, 2334 /** @private */ 2335 unescape = function (entity) { 2336 switch (entity) { 2337 case "&": 2338 return "&"; 2339 case "<": 2340 return "<"; 2341 case ">": 2342 return ">"; 2343 case "'": 2344 return "'"; 2345 case """: 2346 return "\""; 2347 default: 2348 if (entity.charAt(1) === "#" && entity.charAt(entity.length - 1) === ";") { 2349 if (entity.charAt(2) === "x") { 2350 return String.fromCharCode(parseInt(entity.slice(3, -1), 16)); 2351 } else { 2352 return String.fromCharCode(parseInt(entity.slice(2, -1), 10)); 2353 } 2354 } else { 2355 throw new Error("Invalid XML entity: " + entity); 2356 } 2357 } 2358 }; 2359 2360 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2361 if (makeEntityRefs) { 2362 text = text.replace(this.xmlSpecialCharRegEx, escape); 2363 } else { 2364 text = text.replace(this.entityRefRegEx, unescape); 2365 } 2366 } 2367 2368 return text; 2369 }, 2370 2371 /** 2372 * @private 2373 * Utility method to pad the number with a leading 0 for single digits 2374 * @param (Number) num 2375 * the number to pad 2376 */ 2377 pad : function (num) { 2378 if (num < 10) { 2379 return "0" + num; 2380 } 2381 2382 return String(num); 2383 }, 2384 2385 /** 2386 * @private 2387 * Pad with zeros based on a padWidth. 2388 * 2389 * @param num 2390 * @param padWidth 2391 * @returns {String} with padded zeros (based on padWidth) 2392 */ 2393 padWithWidth : function (num, padWidth) { 2394 var value, index, result; 2395 2396 result = ""; 2397 for(index=padWidth;index>1;index--) 2398 { 2399 value = Math.pow(10, index-1); 2400 2401 if (num < value) { 2402 result = result + "0"; 2403 } 2404 } 2405 result = result + num; 2406 2407 return String(result); 2408 }, 2409 2410 /** 2411 * Converts a date to an ISO date string. 2412 * 2413 * @param aDate 2414 * @returns {String} in ISO date format 2415 * 2416 * Note: Some browsers don't support this method (e.g. IE8). 2417 */ 2418 convertDateToISODateString : function (aDate) { 2419 var result; 2420 2421 result = aDate.getUTCFullYear() + "-" + this.padWithWidth(aDate.getUTCMonth()+1, 2) + "-" + this.padWithWidth(aDate.getUTCDate(), 2) + "T" + this.padWithWidth(aDate.getUTCHours(), 2) + ":" + this.padWithWidth(aDate.getUTCMinutes(), 2) + ":" + this.padWithWidth(aDate.getUTCSeconds(), 2)+ "." + this.padWithWidth(aDate.getUTCMilliseconds(), 3) + "Z"; 2422 return result; 2423 }, 2424 2425 /** 2426 * Get the date in ISO date format. 2427 * 2428 * @param aDate is the date 2429 * @returns {String} date in ISO format 2430 * 2431 * Note: see convertDateToISODateString() above. 2432 */ 2433 dateToISOString : function (aDate) { 2434 var result; 2435 2436 try { 2437 result = aDate.toISOString(); 2438 } catch (e) { 2439 result = this.convertDateToISODateString(aDate); 2440 } 2441 return result; 2442 }, 2443 2444 /** 2445 * Parse string (which is formated as ISO8601 date) into Javascript Date object. 2446 * 2447 * @param s ISO8601 string 2448 * @return {Date} 2449 * Note: Some browsers don't support Date constructor which take ISO8601 date (e.g. IE 8). 2450 */ 2451 parseDateStringISO8601 : function (s) { 2452 var i, re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:.(\d+))?(Z|[+\-]\d{2})(?::(\d{2}))?/, 2453 d = s.match(re); 2454 if( !d ) { 2455 return null; 2456 } 2457 for( i in d ) { 2458 d[i] = ~~d[i]; 2459 } 2460 return new Date(Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6], d[7]) + (d[8] * 60 + d[9]) * 60000); 2461 }, 2462 2463 /** 2464 * Utility method to render a timestamp value (in seconds) into HH:MM:SS format. 2465 * @param {Number} time 2466 * The timestamp in ms to render 2467 * @returns {String} 2468 * Time string in HH:MM:SS format. 2469 */ 2470 getDisplayTime : function (time) { 2471 var hour, min, sec, timeStr = "00:00:00"; 2472 2473 if (time && time !== "-1") { 2474 // calculate hours, minutes, and seconds 2475 hour = this.pad(Math.floor(time / 3600)); 2476 min = this.pad(Math.floor((time % 3600) / 60)); 2477 sec = this.pad(Math.floor((time % 3600) % 60)); 2478 // construct HH:MM:SS time string 2479 timeStr = hour + ":" + min + ":" + sec; 2480 } 2481 2482 return timeStr; 2483 }, 2484 2485 /** 2486 * Checks if the string is null. If it is, return empty string; else return 2487 * the string itself. 2488 * 2489 * @param {String} str 2490 * The string to check 2491 * @return {String} 2492 * Empty string or string itself 2493 */ 2494 convertNullToEmptyString : function (str) { 2495 return str || ""; 2496 }, 2497 2498 /** 2499 * Utility method to render a timestamp string (of format 2500 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format. 2501 * 2502 * @param {String} timestamp 2503 * The timestamp to render 2504 * @param {Date} [now] 2505 * Optional argument to provide the time from which to 2506 * calculate the duration instead of using the current time 2507 * @returns {String} 2508 * Duration string in HH:MM:SS format. 2509 */ 2510 convertTsToDuration : function (timestamp, now) { 2511 return this.convertTsToDurationWithFormat(timestamp, false, now); 2512 }, 2513 2514 /** 2515 * Utility method to render a timestamp string (of format 2516 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format, 2517 * with optional -1 for null or negative times. 2518 * 2519 * @param {String} timestamp 2520 * The timestamp to render 2521 * @param {Boolean} forFormat 2522 * If True, if duration is null or negative, return -1 so that the duration can be formated 2523 * as needed in the Gadget. 2524 * @param {Date} [now] 2525 * Optional argument to provide the time from which to 2526 * calculate the duration instead of using the current time 2527 * @returns {String} 2528 * Duration string in HH:MM:SS format. 2529 */ 2530 convertTsToDurationWithFormat : function (timestamp, forFormat, now) { 2531 var startTimeInMs, nowInMs, durationInSec = "-1"; 2532 2533 // Calculate duration 2534 if (timestamp && typeof timestamp === "string") { 2535 // first check it '--' for a msg in grid 2536 if (timestamp === '--' || timestamp ==="" || timestamp === "-1") { 2537 return "-1"; 2538 } 2539 // else try convert string into a time 2540 startTimeInMs = Date.parse(timestamp); 2541 if (!isNaN(startTimeInMs)) { 2542 if (!now || !(now instanceof Date)) { 2543 nowInMs = this.currentServerTimeMillis(); 2544 } else { 2545 nowInMs = this.convertToServerTimeMillis(now.getTime()); 2546 } 2547 durationInSec = Math.floor((nowInMs - startTimeInMs) / 1000); 2548 // Since currentServerTime is not exact (lag between sending and receiving 2549 // messages will differ slightly), treat a slightly negative (less than 1 sec) 2550 // value as 0, to avoid "--" showing up when a state first changes. 2551 if (durationInSec === -1) { 2552 durationInSec = 0; 2553 } 2554 2555 if (durationInSec < 0) { 2556 if (forFormat) { 2557 return "-1"; 2558 } else { 2559 return this.getDisplayTime("-1"); 2560 } 2561 } 2562 } 2563 }else { 2564 if(forFormat){ 2565 return "-1"; 2566 } 2567 } 2568 return this.getDisplayTime(durationInSec); 2569 }, 2570 2571 /** 2572 * @private 2573 * Takes the time in seconds and duration in % and return the duration in milliseconds. 2574 * 2575 * @param time in seconds 2576 * @param duration in % 2577 */ 2578 2579 getRefreshTime :function(expiryTime , duration){ 2580 var durationInMs = Math.floor((expiryTime * duration * 1000) / 100); 2581 return durationInMs; 2582 }, 2583 2584 /** 2585 * Takes a string (typically from window.location) and finds the value which corresponds to a name. For 2586 * example: http://www.company.com/?param1=value1¶m2=value2 2587 * 2588 * @param str is the string to search 2589 * @param name is the name to search for 2590 */ 2591 getParameterByName : function(str, name) { 2592 var regex, results; 2593 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 2594 regex = new RegExp("[\\?&]" + name + "=([^]*)"); 2595 results = regex.exec(str); 2596 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 2597 }, 2598 2599 /** 2600 * 2601 * Returns the base64 encoded user authorization String. 2602 * @returns {String} the Authorization String 2603 * 2604 */ 2605 getUserAuthString: function () { 2606 var authString = window.sessionStorage.getItem('userFinesseAuth'); 2607 return authString; 2608 }, 2609 2610 /** 2611 * Return the user access token as JSON Object. 2612 * @returns {Object} the access token JSON object 2613 * 2614 */ 2615 getAuthTokenObj: function(){ 2616 var authTokenString = window.sessionStorage.getItem('ssoTokenObject'); 2617 return this.getJSONParser().parse(authTokenString); 2618 }, 2619 2620 /** 2621 * Returns the user access token as String. 2622 * @returns {String} the access token 2623 * 2624 */ 2625 2626 getToken: function () { 2627 var tokenString = window.sessionStorage.getItem('ssoTokenObject'), tokenObj; 2628 if (tokenString && typeof tokenString === "string") { 2629 tokenObj = this.getJSONParser().parse(tokenString); 2630 if (tokenObj.token) { 2631 return tokenObj.token; 2632 } else { 2633 throw new Error( 2634 "Unable to retrieve token : Invalid token Object in browser session"); 2635 } 2636 } 2637 }, 2638 2639 /** 2640 * The authorization header based on SSO or non SSO deployment. 2641 * Can be "Bearer " or "Basic " 2642 * @returns {String} The authorization header string. 2643 */ 2644 getAuthHeaderString : function(configObj) { 2645 var authHeader; 2646 if (configObj.systemAuthMode === this.getAuthModes().SSO) { 2647 authHeader = "Bearer " + configObj.authToken; 2648 } else if (configObj.systemAuthMode === this.getAuthModes().NONSSO) { 2649 // CSCvs56405- In case of SPOG, supervisor credentials can be used to retrieve the Workflows and ReasonCodes. 2650 // Since supervisor can log in with SSO. But with context to cfadmin, configObj.systemAuthMode will always be nonsso 2651 // Hence 'Basic' will be appended to actual SSO token. 2652 // Below check is to avoid to append 'Basic' if its already a token 2653 if(configObj.authorization && configObj.authorization.indexOf("Bearer") > -1) { 2654 authHeader = configObj.authorization; 2655 } else { 2656 authHeader = "Basic " + configObj.authorization; 2657 } 2658 } else { 2659 throw new Error("Unknown auth mode "+configObj.systemAuthMode); 2660 } 2661 return authHeader; 2662 }, 2663 2664 /** 2665 * Can be used as a constant for auth modes 2666 * Can be "SSO" , "NON_SSO" or "HYBRID" 2667 * @returns {String} The authorization header string. 2668 */ 2669 getAuthModes : function(){ 2670 return { 2671 SSO: "SSO", 2672 NONSSO: "NON_SSO", 2673 HYBRID: "HYBRID" 2674 }; 2675 }, 2676 2677 /** 2678 * Encodes the node name 2679 */ 2680 encodeNodeName : function(node){ 2681 if (node === null){ 2682 return null; 2683 } 2684 var originalChars, encodedChars,encodedNode, i; 2685 originalChars = ["?", "@", "&","'"]; 2686 encodedChars = ["?3F", "?40", "?26","?27"]; 2687 encodedNode = node; 2688 2689 if(encodedNode.indexOf(originalChars[0]) !== -1){ 2690 encodedNode = encodedNode.replace(/\?/g, encodedChars[0]); 2691 } 2692 for (i = 1; i < originalChars.length; i++){ 2693 if(encodedNode.indexOf(originalChars[i]) !== -1){ 2694 encodedNode = encodedNode.replace(new RegExp(originalChars[i], "g"), encodedChars[i]); 2695 } 2696 } 2697 return encodedNode; 2698 }, 2699 2700 /** 2701 * @private Utility method to convert milliseconds into minutes. 2702 * @param {String} Time in milliseconds 2703 * @returns {String} Time in minutes 2704 */ 2705 convertMilliSecondsToMinutes : function(millisec){ 2706 if(!millisec || isNaN(millisec)){ 2707 throw new Error("passed argument is not a number"); 2708 }else{ 2709 var minutes = Math.floor(millisec / (1000 * 60)); 2710 return minutes; 2711 } 2712 }, 2713 2714 2715 /** 2716 * @private Adds a new cookie to the page with a default domain. 2717 * @param {String} 2718 * key the key to assign a value to 2719 * @param {String} 2720 * value the value to assign to the key 2721 * @param {Number} 2722 * days number of days (from current) until the cookie should 2723 * expire 2724 * @param {String} 2725 * domain the domain for the cookie 2726 */ 2727 addCookie : function (key, value, days, domain) { 2728 var date, expires = "", 2729 cookie = key + "=" + escape(value); 2730 if (typeof days === "number") { 2731 date = new Date(); 2732 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 2733 cookie += "; expires=" + date.toGMTString(); 2734 } 2735 2736 if (domain) { 2737 cookie += "; domain=" + domain; 2738 } 2739 2740 document.cookie = cookie + "; path=/"; 2741 }, 2742 2743 /** 2744 * @private 2745 * Get the value of a cookie given a key. 2746 * @param {String} key 2747 * a key to lookup 2748 * @returns {String} 2749 * the value mapped to a key, null if key doesn't exist 2750 */ 2751 getCookie : function (key) { 2752 var i, pairs, pair; 2753 if (document.cookie) { 2754 pairs = document.cookie.split(";"); 2755 for (i = 0; i < pairs.length; i += 1) { 2756 pair = this.trim(pairs[i]).split("="); 2757 if (pair[0] === key) { 2758 return unescape(pair[1]); 2759 } 2760 } 2761 } 2762 return null; 2763 }, 2764 2765 /** 2766 * @private 2767 * serach cookies which starts with given string 2768 */ 2769 getCookieEntriesThatStartsWith : function (key) { 2770 var i, pairs, pair, entries = []; 2771 if (document.cookie) { 2772 pairs = document.cookie.split(";"); 2773 for (i = 0; i < pairs.length; i += 1) { 2774 pair = this.trim(pairs[i]).split("="); 2775 if (pair[0].startsWith(key)) { 2776 entries.push({key: pair[0], value: unescape(pair[1])}); 2777 } 2778 } 2779 } 2780 return entries; 2781 }, 2782 2783 /** 2784 * @private 2785 * Deletes the cookie mapped to specified key. 2786 * @param {String} key 2787 * the key to delete 2788 */ 2789 deleteCookie : function (key, domain) { 2790 this.addCookie(key, "", -1, domain); 2791 }, 2792 2793 /** 2794 * @private 2795 * Case insensitive sort for use with arrays or Dojox stores 2796 * @param {String} a 2797 * first value 2798 * @param {String} b 2799 * second value 2800 */ 2801 caseInsensitiveSort: function (a, b) { 2802 var ret = 0, emptyString = ""; 2803 a = a + emptyString; 2804 b = b + emptyString; 2805 a = a.toLowerCase(); 2806 b = b.toLowerCase(); 2807 if (a > b) { 2808 ret = 1; 2809 } 2810 if (a < b) { 2811 ret = -1; 2812 } 2813 return ret; 2814 }, 2815 2816 /** 2817 * @private 2818 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 2819 * 2820 * The displayWjitFunc function will be called once and only once when the div for our wijit 2821 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 2822 * throw exceptions and do not render properly if they are created in a display:none div. 2823 * If our gadget is visisble the function will be called immediately. 2824 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 2825 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 2826 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 2827 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 2828 * end up waiting for the node to become visisble anyway. 2829 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 2830 */ 2831 onGadgetFirstVisible: function (displayWjitFunc) { 2832 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2833 try { 2834 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2835 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2836 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2837 2838 // Loop through all of the tab panels to find one that has our gadget id 2839 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2840 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2841 if (q.length > 0) { // You found it 2842 panelNode = node; 2843 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2844 active = dojo.hasClass(panelNode, "active"); 2845 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2846 return; 2847 } 2848 }); 2849 // If panel is already active - execute the function - we're done 2850 if (active) { 2851 //?console.log(frameId + " is visible display it"); 2852 setTimeout(displayWjitFunc); 2853 } 2854 // If its not visible - wait for the active class to show up. 2855 else { 2856 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2857 iterval = setInterval(dojo.hitch(this, function () { 2858 if (dojo.hasClass(panelNode, "active")) { 2859 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2860 clearInterval(iterval); 2861 setTimeout(displayWjitFunc); 2862 } 2863 }), 250); 2864 } 2865 } catch (err) { 2866 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2867 } 2868 }, 2869 2870 /** 2871 * @private 2872 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2873 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2874 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2875 */ 2876 downloadFile : function (url) { 2877 var iframe = document.getElementById("download_iframe"); 2878 2879 if (!iframe) 2880 { 2881 iframe = document.createElement("iframe"); 2882 $(document.body).append(iframe); 2883 $(iframe).css("display", "none"); 2884 } 2885 2886 iframe.src = url; 2887 }, 2888 2889 /** 2890 * @private 2891 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2892 */ 2893 bitMask: { 2894 /** @private */ 2895 isSet: function (value, mask) { 2896 return (value & mask) === mask; 2897 }, 2898 /** 2899 * Returns true if all flags in the intArray are set on the specified value 2900 * @private 2901 */ 2902 all: function (value, intArray) { 2903 var i = intArray.length; 2904 if (typeof(i) === "undefined") 2905 { 2906 intArray = [intArray]; 2907 i = 1; 2908 } 2909 while ((i = i - 1) !== -1) 2910 { 2911 if (!this.isSet(value, intArray[i])) 2912 { 2913 return false; 2914 } 2915 } 2916 return true; 2917 }, 2918 /** 2919 * @private 2920 * Returns true if any flags in the intArray are set on the specified value 2921 */ 2922 any: function (value, intArray) { 2923 var i = intArray.length; 2924 if (typeof(i) === "undefined") 2925 { 2926 intArray = [intArray]; 2927 i = 1; 2928 } 2929 while ((i = i - 1) !== -1) 2930 { 2931 if (this.isSet(value, intArray[i])) 2932 { 2933 return true; 2934 } 2935 } 2936 return false; 2937 } 2938 }, 2939 2940 /** @private */ 2941 renderDojoGridOffScreen: function (grid) { 2942 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2943 $(document.body).append(offscreenDiv); 2944 grid.placeAt(offscreenDiv); 2945 grid.startup(); 2946 document.body.removeChild(offscreenDiv); 2947 return grid; 2948 }, 2949 2950 /** @private */ 2951 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2952 var timerId = null, 2953 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2954 theInputControl = theControl.find("input"), 2955 theClearButton = theControl.find("a"), 2956 inputControlWidthWithClear = 204, 2957 inputControlWidthNoClear = 230, 2958 sPreviousInput = theInputControl.val(), 2959 /** @private **/ 2960 toggleClearButton = function(){ 2961 if (theInputControl.val() === "") { 2962 theClearButton.hide(); 2963 theControl.removeClass("input-append"); 2964 theInputControl.width(inputControlWidthNoClear); 2965 } else { 2966 theInputControl.width(inputControlWidthWithClear); 2967 theClearButton.show(); 2968 theControl.addClass("input-append"); 2969 } 2970 }; 2971 2972 // set placeholder text 2973 theInputControl.attr('placeholder', placeholderText); 2974 2975 theInputControl.unbind('keyup').bind('keyup', function() { 2976 if (sPreviousInput !== theInputControl.val()) { 2977 window.clearTimeout(timerId); 2978 sPreviousInput = theInputControl.val(); 2979 timerId = window.setTimeout(function() { 2980 changeCallback.call((callbackScope || window), theInputControl.val()); 2981 theInputControl[0].focus(); 2982 }, callbackDelay); 2983 } 2984 2985 toggleClearButton(); 2986 }); 2987 2988 theClearButton.bind('click', function() { 2989 theInputControl.val(''); 2990 changeCallback.call((callbackScope || window), ''); 2991 2992 toggleClearButton(); 2993 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2994 }); 2995 2996 theInputControl.val(""); 2997 toggleClearButton(); 2998 }, 2999 3000 DataTables: { 3001 /** @private */ 3002 createDataTable: function (options, dataTableOptions) { 3003 var grid, 3004 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 3005 headerRow = table.find("tr"), 3006 defaultOptions = { 3007 "aaData": [], 3008 "bPaginate": false, 3009 "bLengthChange": false, 3010 "bFilter": false, 3011 "bInfo": false, 3012 "sScrollY": "176", 3013 "oLanguage": { 3014 "sEmptyTable": "", 3015 "sZeroRecords": "" 3016 } 3017 }, 3018 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 3019 columnDefs = [], 3020 columnFormatter; 3021 3022 // Create a header cell for each column, and set up the datatable definition for the column 3023 $(options.columns).each(function (index, column) { 3024 headerRow.append($("<th></th>")); 3025 columnDefs[index] = { 3026 "mData": column.propertyName, 3027 "sTitle": column.columnHeader, 3028 "sWidth": column.width, 3029 "aTargets": [index], 3030 "bSortable": column.sortable, 3031 "bVisible": column.visible, 3032 "mRender": column.render 3033 }; 3034 if (typeof(column.renderFunction) === "function") 3035 { 3036 /** @ignore **/ 3037 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 3038 var returnValue; 3039 3040 //Apply column render logic to value before applying extra render function 3041 if (typeof(column.render) === "function") 3042 { 3043 value = column.render.call(value, value, value); 3044 } 3045 3046 if (typeof(type) === "string") 3047 { 3048 switch (type) 3049 { 3050 case "undefined": 3051 case "sort": 3052 returnValue = value; 3053 break; 3054 case "set": 3055 throw new Error("Unsupported set data in Finesse Grid"); 3056 case "filter": 3057 case "display": 3058 case "type": 3059 returnValue = column.renderFunction.call(dataObject, value, dataObject); 3060 break; 3061 default: 3062 break; 3063 } 3064 } 3065 else 3066 { 3067 throw new Error("type param not specified in Finesse DataTable mData"); 3068 } 3069 3070 return returnValue; 3071 }; 3072 } 3073 }); 3074 gridOptions.aoColumnDefs = columnDefs; 3075 3076 // Set the height 3077 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 3078 { 3079 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 3080 } 3081 3082 // Place it into the DOM 3083 if (typeof(options.container) !== "undefined" && options.container !== null) 3084 { 3085 $(options.container).append(table); 3086 } 3087 3088 // Create the DataTable 3089 table.dataTable(gridOptions); 3090 3091 return table; 3092 } 3093 }, 3094 3095 /** 3096 * @private 3097 * Sets a dojo button to the specified disable state, removing it from 3098 * the tab order if disabling, and restoring it to the tab order if enabling. 3099 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 3100 * @param {bool} disabled 3101 */ 3102 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 3103 var labelNode, 3104 tabIndex; 3105 3106 dojoButton.set("disabled", disabled); 3107 3108 // Remove the tabindex attribute on disabled buttons, store it, 3109 // and replace it when it becomes enabled again 3110 labelNode = $("#" + dojoButton.id + "_label"); 3111 if (disabled) 3112 { 3113 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 3114 labelNode.removeAttr("tabindex"); 3115 } 3116 else 3117 { 3118 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 3119 if (typeof(tabIndex) === "string") 3120 { 3121 labelNode.attr("tabindex", Number(tabIndex)); 3122 } 3123 } 3124 }, 3125 3126 /** 3127 * @private 3128 * Use this utility to disable the tab stop for a Dojo Firebug iframe within a gadget. 3129 * 3130 * Dojo sometimes adds a hidden iframe for enabling a firebug lite console in older 3131 * browsers. Unfortunately, this adds an additional tab stop that impacts accessibility. 3132 */ 3133 disableTabStopForDojoFirebugIframe: function () { 3134 var iframe = $("iframe[src*='loadFirebugConsole']"); 3135 3136 if ((iframe.length) && (iframe.attr("tabIndex") !== "-1")) { 3137 iframe.attr('tabIndex', '-1'); 3138 } 3139 }, 3140 3141 /** 3142 * @private 3143 * Measures the given text using the supplied fontFamily and fontSize 3144 * @param {string} text text to measure 3145 * @param {string} fontFamily 3146 * @param {string} fontSize 3147 * @return {number} pixel width 3148 */ 3149 measureText: function (text, fontFamily, fontSize) { 3150 var width, 3151 element = $("<div></div>").text(text).css({ 3152 "fontSize": fontSize, 3153 "fontFamily": fontFamily 3154 }).addClass("offscreen").appendTo(document.body); 3155 3156 width = element.width(); 3157 element.remove(); 3158 3159 return width; 3160 }, 3161 3162 /** 3163 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 3164 * needing to resize down in IE. This gets around that by calculating the height 3165 * manually and passing it in. 3166 * @return {undefined} 3167 */ 3168 "adjustGadgetHeight": function () { 3169 var bScrollHeight = $("body").height() + 20; 3170 gadgets.window.adjustHeight(bScrollHeight); 3171 }, 3172 3173 /** 3174 * Private helper method for converting a javascript object to xml, where the values of the elements are 3175 * appropriately escaped for XML. 3176 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 3177 * there is no whitespace between elements. 3178 * @param object The javascript object to convert to XML. 3179 * @returns The XML string. 3180 * @private 3181 */ 3182 _json2xmlWithEscape: function(object) { 3183 var that = this, 3184 xml = "", 3185 m, 3186 /** @private **/ 3187 toXmlHelper = function(value, name) { 3188 var xml = "", 3189 i, 3190 m; 3191 if (value instanceof Array) { 3192 for (i = 0; i < value.length; ++i) { 3193 xml += toXmlHelper(value[i], name); 3194 } 3195 } 3196 else if (typeof value === "object") { 3197 xml += "<" + name + ">"; 3198 for (m in value) { 3199 if (value.hasOwnProperty(m)) { 3200 xml += toXmlHelper(value[m], m); 3201 } 3202 } 3203 xml += "</" + name + ">"; 3204 } 3205 else { 3206 // is a leaf node 3207 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 3208 "</" + name + ">"; 3209 } 3210 return xml; 3211 }; 3212 for (m in object) { 3213 if (object.hasOwnProperty(m)) { 3214 xml += toXmlHelper(object[m], m); 3215 } 3216 } 3217 return xml; 3218 }, 3219 3220 /** 3221 * Private method for returning a sanitized version of the user agent string. 3222 * @returns the user agent string, but sanitized! 3223 * @private 3224 */ 3225 getSanitizedUserAgentString: function () { 3226 return this.translateXMLEntities(navigator.userAgent, true); 3227 }, 3228 3229 /** 3230 * Use JQuery's implementation of Promises (Deferred) to execute code when 3231 * multiple async processes have finished. An example use: 3232 * 3233 * var asyncProcess1 = $.Deferred(), 3234 * asyncProcess2 = $.Deferred(); 3235 * 3236 * finesse.utilities.Utilities.whenAllDone(asyncProcess1, asyncProcess2) // WHEN both asyncProcess1 and asyncProcess2 are resolved or rejected ... 3237 * .then( 3238 * // First function passed to then() is called when all async processes are complete, regardless of errors 3239 * function () { 3240 * console.log("all processes completed"); 3241 * }, 3242 * // Second function passed to then() is called if any async processed threw an exception 3243 * function (failures) { // Array of failure messages 3244 * console.log("Number of failed async processes: " + failures.length); 3245 * }); 3246 * 3247 * Found at: 3248 * http://stackoverflow.com/a/15094263/1244030 3249 * 3250 * Pass in any number of $.Deferred instances. 3251 * @returns {Object} 3252 */ 3253 whenAllDone: function () { 3254 var deferreds = [], 3255 result = $.Deferred(); 3256 3257 $.each(arguments, function(i, current) { 3258 var currentDeferred = $.Deferred(); 3259 current.then(function() { 3260 currentDeferred.resolve(false, arguments); 3261 }, function() { 3262 currentDeferred.resolve(true, arguments); 3263 }); 3264 deferreds.push(currentDeferred); 3265 }); 3266 3267 $.when.apply($, deferreds).then(function() { 3268 var failures = [], 3269 successes = []; 3270 3271 $.each(arguments, function(i, args) { 3272 // If we resolved with `true` as the first parameter 3273 // we have a failure, a success otherwise 3274 var target = args[0] ? failures : successes, 3275 data = args[1]; 3276 // Push either all arguments or the only one 3277 target.push(data.length === 1 ? data[0] : args); 3278 }); 3279 3280 if(failures.length) { 3281 return result.reject.apply(result, failures); 3282 } 3283 3284 return result.resolve.apply(result, successes); 3285 }); 3286 3287 return result; 3288 }, 3289 3290 /** 3291 * Private method to format a given string by replacing the place holders (like {0}) with the 3292 * corresponding supplied arguments. For example, calling this method as follows: 3293 * formatString("Hello {0}, {1} rocks!", "there", "Finesse"); 3294 * results in the following output string: 3295 * "Hello there, Finesse rocks!" 3296 * 3297 * @param {String} format - a string that holds the place holder to be replaced 3298 * 3299 * @returns {String} - string where the place holders are replaced with respective values 3300 * @private 3301 */ 3302 formatString : function(format) { 3303 if (!format || arguments.length <= 1) { 3304 return format; 3305 } 3306 3307 var i, retStr = format; 3308 for (i = 1; i < arguments.length; i += 1) { 3309 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 3310 } 3311 3312 // in order to fix French text with single quotes in it, we need to replace \' with ' 3313 return retStr.replace(/\\'/g, "'"); 3314 }, 3315 3316 /** 3317 * @private 3318 * This method is used to make the scheme and port secure when the admin gadgets are loaded in some other container. 3319 * @param {object} config - The config which is passed to client services to handle restRequest in secure or non secure mode. 3320 * 3321 * @returns {object} config - The modified config if scheme was https. 3322 */ 3323 setSchemeAndPortForHttps : function(config) { 3324 var scheme = window.location.protocol; 3325 if(scheme === "https:") { 3326 config["restScheme"] = "https"; 3327 config["localhostPort"] = "8445"; 3328 } 3329 3330 return config; 3331 }, 3332 3333 /** 3334 * 3335 * @private 3336 * If the browser tab is inactive, any calls to javascript function setTimeout(0,...) will not be executed 3337 * immediately since browser throttles events from background tabs. The delay sometimes could be about 5 seconds. 3338 * This utilitiy function provides a wrapper around ES5 promise to resolve this issue. 3339 * 3340 * @param {function} funcOther Target function that will be invoked at the end of browser events. 3341 * 3342 * @returns {Object} The promise 3343 */ 3344 executeAsync: function(funcOther) { 3345 var promise = new Promise( 3346 function( resolve, reject ) { 3347 resolve(); 3348 } 3349 ); 3350 promise.then( funcOther ); 3351 3352 return promise; 3353 }, 3354 3355 /** 3356 * Extract hostname from a given url string 3357 */ 3358 extractHostname : function (url) { 3359 var hostname; 3360 //find & remove protocol (http, ftp, etc.) and get hostname 3361 3362 if (url.indexOf("//") > -1) { 3363 hostname = url.split('/')[2]; 3364 } 3365 else { 3366 hostname = url.split('/')[0]; 3367 } 3368 3369 //find & remove port number 3370 hostname = hostname.split(':')[0]; 3371 //find & remove "?" 3372 hostname = hostname.split('?')[0]; 3373 3374 return hostname; 3375 }, 3376 3377 /** 3378 * Get the value of a querystring 3379 * @param {String} field The field to get the value of 3380 * @param {String} url The URL to get the value from (optional) 3381 * @return {String} The field value 3382 */ 3383 getQueryString : function ( field, url ) { 3384 var href = url ? url : window.location.href; 3385 var reg = new RegExp( '[?&]' + field + '=([^]*)', 'i' ); 3386 var string = reg.exec(href); 3387 return string ? decodeURIComponent(string[1]) : ''; 3388 }, 3389 3390 /** 3391 * Remove log schedule data from session storage 3392 */ 3393 removeLogScheduleDataFromSession : function () { 3394 if (sessionStorage.getItem(finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA)) { 3395 sessionStorage.removeItem(finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA); 3396 } 3397 }, 3398 3399 /** 3400 * Remove log schedule array from session storage 3401 */ 3402 removeScheduleLogDataForFailoverFromSession : function () { 3403 var logScheduleData = JSON.parse(sessionStorage.getItem(finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA) || null); 3404 if (logScheduleData && logScheduleData.scheduledLogArrayData) { 3405 finesse.clientservices.ClientServices.log('Utilities.removeScheduleLogDataForFailoverFromSession(): Deleting the scheduled array data from session'); 3406 delete logScheduleData['scheduledLogArrayData']; 3407 sessionStorage.setItem(window.finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA, JSON.stringify(logScheduleData)); 3408 } 3409 }, 3410 3411 /** 3412 * Shows retry modal with spinner 3413 * @private 3414 * @param options 3415 * options.loginFailureRetryInterval : Retry Interval after which perRetryCallback will be called 3416 * options.retryAttempted : Retry attempts that has already been made , should start from 1 3417 * options.loginFailureRetryAttempts : Total numbers of re- attempts to be made 3418 * options.messageKey : Message key inside modal (will be pulled from properties file) 3419 * options.allRetriesFailedCallback: Callback function called when all retry are exhausted , it is followed by perRetryCallback 3420 * options.perRetryCallback : Callback for single retry attempt that needs to be made (Called when interval reduces to 0) 3421 * @returns boolean - return true if showRetry modal initialization was successful, false when retry attempts are greater than retryAttempted 3422 * 3423 */ 3424 showRetryModal: function (options) { 3425 return window.finesse.Dialog.showRetryModal(options); 3426 }, 3427 3428 isAbsoluteUrl: function(url) { 3429 if(url) { 3430 return /^(http|https):\/\//i.test(url); 3431 } 3432 3433 return false; 3434 } 3435 }; 3436 3437 window.finesse = window.finesse || {}; 3438 window.finesse.utilities = window.finesse.utilities || {}; 3439 window.finesse.utilities.Utilities = Utilities; 3440 3441 return Utilities; 3442 }); 3443 3444 /** 3445 * Allows gadgets to call the log function to publish client logging messages over the hub. 3446 * 3447 * @requires OpenAjax 3448 */ 3449 /** @private */ 3450 define('cslogger/ClientLogger',[], function () { 3451 3452 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 3453 var _hub, _logTopic, _originId, _sessId, _host, 3454 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 3455 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 3456 3457 /** 3458 * Gets timestamp drift stored in sessionStorage 3459 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 3460 * @private 3461 */ 3462 getTsDrift = function() { 3463 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 3464 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 3465 } 3466 else { 3467 return undefined; 3468 } 3469 }, 3470 3471 /** 3472 * Sets timestamp drift in sessionStorage 3473 * @param delta is the timestamp drift between server.and client. 3474 * @private 3475 */ 3476 setTsDrift = function(delta) { 3477 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 3478 }, 3479 3480 /** 3481 * Gets Finesse server timezone offset from GMT in seconds 3482 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 3483 * @private 3484 */ 3485 getServerOffset = function() { 3486 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 3487 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 3488 } 3489 else { 3490 return undefined; 3491 } 3492 }, 3493 3494 /** 3495 * Sets server timezone offset 3496 * @param sec is the server timezone GMT offset in seconds. 3497 * @private 3498 */ 3499 setServerOffset = function(sec) { 3500 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 3501 }, 3502 3503 /** 3504 * Checks to see if we have a console. 3505 * @returns Whether the console object exists. 3506 * @private 3507 */ 3508 hasConsole = function () { 3509 try { 3510 if (window.console !== undefined) { 3511 return true; 3512 } 3513 } 3514 catch (err) { 3515 // ignore and return false 3516 } 3517 3518 return false; 3519 }, 3520 3521 /** 3522 * Gets a short form (6 character) session ID from sessionStorage 3523 * @private 3524 */ 3525 getSessId = function() { 3526 if (!_sessId) { 3527 //when _sessId not defined yet, initiate it 3528 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 3529 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 3530 } 3531 else { 3532 _sessId=" "; 3533 } 3534 } 3535 return _sessId; 3536 }, 3537 3538 /** 3539 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 3540 * @param num is the number to pad to 2 digits 3541 * @returns a two digit padded string 3542 * @private 3543 */ 3544 padTwoDigits = function (num) 3545 { 3546 return (num < 10) ? '0' + num : num; 3547 }, 3548 3549 /** 3550 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 3551 * @param num is the number to pad to 3 digits 3552 * @returns a three digit padded string 3553 * @private 3554 */ 3555 padThreeDigits = function (num) 3556 { 3557 if (num < 10) 3558 { 3559 return '00'+num; 3560 } 3561 else if (num < 100) 3562 { 3563 return '0'+num; 3564 } 3565 else 3566 { 3567 return num; 3568 } 3569 }, 3570 3571 /** 3572 * Compute the "hour" 3573 * 3574 * @param s is time in seconds 3575 * @returns {String} which is the hour 3576 * @private 3577 */ 3578 ho = function (s) { 3579 return ((s/60).toString()).split(".")[0]; 3580 }, 3581 3582 /** 3583 * Gets local timezone offset string. 3584 * 3585 * @param t is the time in seconds 3586 * @param s is the separator character between hours and minutes, e.g. ':' 3587 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 3588 * @private 3589 */ 3590 getGmtOffString = function (min,s) { 3591 var t, sign; 3592 if (min<0) { 3593 t = -min; 3594 sign = "-"; 3595 } 3596 else { 3597 t = min; 3598 sign = "+"; 3599 } 3600 3601 if (s===':') { 3602 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 3603 } 3604 else { 3605 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 3606 } 3607 }, 3608 3609 /** 3610 * Gets short form of a month name in English 3611 * 3612 * @param monthNum is zero-based month number 3613 * @returns {String} is short form of month name in English 3614 * @private 3615 */ 3616 getMonthShortStr = function (monthNum) { 3617 var result; 3618 try { 3619 result = MONTH[monthNum]; 3620 } 3621 catch (err) { 3622 if (hasConsole()) { 3623 window.console.log("Month must be between 0 and 11"); 3624 } 3625 } 3626 return result; 3627 }, 3628 3629 /** 3630 * Gets a timestamp. 3631 * @param aDate is a javascript Date object 3632 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 3633 * @private 3634 */ 3635 getDateTimeStamp = function (aDate) 3636 { 3637 var date, off, timeStr; 3638 if (aDate === null) { 3639 date = new Date(); 3640 } 3641 else { 3642 date = aDate; 3643 } 3644 off = -1*date.getTimezoneOffset(); 3645 timeStr = date.getFullYear().toString() + "-" + 3646 padTwoDigits(date.getMonth()+1) + "-" + 3647 padTwoDigits (date.getDate()) + "T"+ 3648 padTwoDigits(date.getHours()) + ":" + 3649 padTwoDigits(date.getMinutes()) + ":" + 3650 padTwoDigits(date.getSeconds())+"." + 3651 padThreeDigits(date.getMilliseconds()) + " "+ 3652 getGmtOffString(off, ':'); 3653 3654 return timeStr; 3655 }, 3656 3657 /** 3658 * Gets drift-adjusted timestamp. 3659 * @param aTimestamp is a timestamp in milliseconds 3660 * @param drift is a timestamp drift in milliseconds 3661 * @param serverOffset is a timezone GMT offset in minutes 3662 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 3663 * @private 3664 */ 3665 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 3666 { 3667 var date, timeStr, localOffset; 3668 if (aTimestamp === null) { 3669 return "--- -- ---- --:--:--.--- -----"; 3670 } 3671 else if (drift === undefined || serverOffset === undefined) { 3672 if (hasConsole()) { 3673 window.console.log("drift or serverOffset must be a number"); 3674 } 3675 return "--- -- ---- --:--:--.--- -----"; 3676 } 3677 else { 3678 //need to get a zone diff in minutes 3679 localOffset = (new Date()).getTimezoneOffset(); 3680 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 3681 timeStr = getMonthShortStr(date.getMonth()) + " "+ 3682 padTwoDigits (date.getDate())+ " "+ 3683 date.getFullYear().toString() + " "+ 3684 padTwoDigits(date.getHours()) + ":" + 3685 padTwoDigits(date.getMinutes()) + ":" + 3686 padTwoDigits(date.getSeconds())+"." + 3687 padThreeDigits(date.getMilliseconds())+" "+ 3688 getGmtOffString(serverOffset, ''); 3689 return timeStr; 3690 } 3691 }, 3692 3693 /** 3694 * Logs a message to a hidden textarea element on the page 3695 * 3696 * @param msg is the string to log. 3697 * @private 3698 */ 3699 writeToLogOutput = function (msg) { 3700 var logOutput = document.getElementById("finesseLogOutput"); 3701 3702 if (logOutput === null) 3703 { 3704 logOutput = document.createElement("textarea"); 3705 logOutput.id = "finesseLogOutput"; 3706 logOutput.style.display = "none"; 3707 document.body.appendChild(logOutput); 3708 } 3709 3710 if (logOutput.value === "") 3711 { 3712 logOutput.value = msg; 3713 } 3714 else 3715 { 3716 logOutput.value = logOutput.value + "\n" + msg; 3717 } 3718 }, 3719 3720 /** 3721 * Logs a message to console 3722 * @param str is the string to log. 3723 * @private 3724 */ 3725 logToConsole = function (str, error) 3726 { 3727 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 3728 now = new Date(); 3729 timeStr = getDateTimeStamp(now); 3730 if (getTsDrift() !== undefined) { 3731 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 3732 } 3733 else { 3734 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 3735 } 3736 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 3737 // Log to console 3738 if (hasConsole()) { 3739 if (error) { 3740 window.console.error(msg, error); 3741 } else { 3742 window.console.log(msg); 3743 } 3744 } 3745 3746 //Uncomment to print logs to hidden textarea. 3747 //writeToLogOutput(msg); 3748 3749 return msg; 3750 }; 3751 return { 3752 3753 /** 3754 * Publishes a Log Message over the hub. 3755 * 3756 * @param {String} message 3757 * The string to log. 3758 * @param {Object} error - optional 3759 * Javascript error object 3760 * @example 3761 * _clientLogger.log("This is some important message for MyGadget"); 3762 * 3763 */ 3764 log : function (message, error) { 3765 if(_hub) { 3766 _hub.publish(_logTopic, logToConsole(_originId + message, error)); 3767 } 3768 }, 3769 3770 /** 3771 * @class 3772 * Allows gadgets to call the log function to publish client logging messages over the hub. 3773 * 3774 * @constructs 3775 */ 3776 _fakeConstuctor: function () { 3777 /* This is here so we can document init() as a method rather than as a constructor. */ 3778 }, 3779 3780 /** 3781 * Initiates the client logger with a hub a gadgetId and gadget's config object. 3782 * @param {Object} hub 3783 * The hub to communicate with. 3784 * @param {String} gadgetId 3785 * A unique string to identify which gadget is doing the logging. 3786 * @param {finesse.gadget.Config} config 3787 * The config object used to get host name for that thirdparty gadget 3788 * @example 3789 * var _clientLogger = finesse.cslogger.ClientLogger; 3790 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 3791 * 3792 */ 3793 init: function (hub, gadgetId, config) { 3794 _hub = hub; 3795 _logTopic = "finesse.clientLogging." + gadgetId; 3796 _originId = gadgetId + " : "; 3797 if ((config === undefined) || (config === "undefined")) 3798 { 3799 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 3800 } 3801 else 3802 { 3803 _host = ((config && config.host)?config.host : "?.?.?.?"); 3804 } 3805 } 3806 }; 3807 }()); 3808 3809 window.finesse = window.finesse || {}; 3810 window.finesse.cslogger = window.finesse.cslogger || {}; 3811 window.finesse.cslogger.ClientLogger = ClientLogger; 3812 3813 finesse = finesse || {}; 3814 /** @namespace Supports writing messages to a central log. */ 3815 finesse.cslogger = finesse.cslogger || {}; 3816 3817 return ClientLogger; 3818 }); 3819 3820 /** The following comment is to prevent jslint errors about 3821 * using variables before they are defined. 3822 */ 3823 /*global finesse*/ 3824 3825 /** 3826 * Initiated by the Master to create a shared BOSH connection. 3827 * 3828 * @requires Utilities 3829 */ 3830 3831 /** 3832 * @class 3833 * Establishes a shared event connection by creating a communication tunnel 3834 * with the notification server and consume events which could be published. 3835 * Public functions are exposed to register to the connection status information 3836 * and events. 3837 * @constructor 3838 * @param {String} host 3839 * The host name/ip of the Finesse server. 3840 * @throws {Error} If required constructor parameter is missing. 3841 */ 3842 /** @private */ 3843 define('clientservices/MasterTunnel',["utilities/Utilities", "cslogger/ClientLogger"], function (Utilities, ClientLogger) { 3844 var MasterTunnel = function (host, scheme) { 3845 if (typeof host !== "string" || host.length === 0) { 3846 throw new Error("Required host parameter missing."); 3847 } 3848 3849 var 3850 3851 /** 3852 * Flag to indicate whether the tunnel frame is loaded. 3853 * @private 3854 */ 3855 _isTunnelLoaded = false, 3856 3857 /** 3858 * Short reference to the Finesse utility. 3859 * @private 3860 */ 3861 _util = Utilities, 3862 3863 /** 3864 * The URL with host and port to the Finesse server. 3865 * @private 3866 */ 3867 _tunnelOrigin, 3868 3869 /** 3870 * Location of the tunnel HTML URL. 3871 * @private 3872 */ 3873 _tunnelURL, 3874 3875 /** 3876 * The port on which to connect to the Finesse server to load the eventing resources. 3877 * @private 3878 */ 3879 _tunnelOriginPort, 3880 3881 /** 3882 * Flag to indicate whether we have processed the tunnel config yet. 3883 * @private 3884 */ 3885 _isTunnelConfigInit = false, 3886 3887 /** 3888 * The tunnel frame window object. 3889 * @private 3890 */ 3891 _tunnelFrame, 3892 3893 /** 3894 * The handler registered with the object to be invoked when an event is 3895 * delivered by the notification server. 3896 * @private 3897 */ 3898 _eventHandler, 3899 3900 /** 3901 * The handler registered with the object to be invoked when presence is 3902 * delivered by the notification server. 3903 * @private 3904 */ 3905 _presenceHandler, 3906 3907 /** 3908 * The handler registered with the object to be invoked when the BOSH 3909 * connection has changed states. The object will contain the "status" 3910 * property and a "resourceID" property only if "status" is "connected". 3911 * @private 3912 */ 3913 _connInfoHandler, 3914 3915 /** 3916 * The last connection status published by the JabberWerx library. 3917 * @private 3918 */ 3919 _statusCache, 3920 3921 /** 3922 * The last event sent by notification server. 3923 * @private 3924 */ 3925 _eventCache, 3926 3927 /** 3928 * The ID of the user logged into notification server. 3929 * @private 3930 */ 3931 _id, 3932 3933 /** 3934 * The domain of the XMPP server, representing the portion of the JID 3935 * following '@': userid@domain.com 3936 * @private 3937 */ 3938 _xmppDomain, 3939 3940 /** 3941 * The password of the user logged into notification server. 3942 * @private 3943 */ 3944 _password, 3945 3946 /** 3947 * The jid of the pubsub service on the XMPP server 3948 * @private 3949 */ 3950 _pubsubDomain, 3951 3952 /** 3953 * The resource to use for the BOSH connection. 3954 * @private 3955 */ 3956 _resource, 3957 3958 /** 3959 * The resource ID identifying the client device (that we receive from the server). 3960 * @private 3961 */ 3962 _resourceID, 3963 3964 /** 3965 * The xmpp connection protocol type. 3966 * @private 3967 */ 3968 _notificationConnectionType, 3969 3970 /** 3971 * The different types of messages that could be sent to the parent frame. 3972 * The types here should be understood by the parent frame and used to 3973 * identify how the message is formatted. 3974 * @private 3975 */ 3976 _TYPES = { 3977 EVENT: 0, 3978 ID: 1, 3979 PASSWORD: 2, 3980 RESOURCEID: 3, 3981 STATUS: 4, 3982 XMPPDOMAIN: 5, 3983 PUBSUBDOMAIN: 6, 3984 SUBSCRIBE: 7, 3985 UNSUBSCRIBE: 8, 3986 PRESENCE: 9, 3987 CONNECT_REQ: 10, 3988 DISCONNECT_REQ: 11, 3989 NOTIFICATION_CONNECTION_TYPE: 12, 3990 LOGGING: 13, 3991 SUBSCRIPTIONS_REQ: 14, 3992 XMPP_DISCONNECT_PROPERTIES:15 3993 }, 3994 3995 _handlers = { 3996 subscribe: {}, 3997 unsubscribe: {} 3998 }, 3999 4000 4001 /** 4002 * Create a connection info object. 4003 * @returns {Object} 4004 * A connection info object containing a "status" and "resourceID". 4005 * @private 4006 */ 4007 _createConnInfoObj = function () { 4008 return { 4009 status: _statusCache, 4010 resourceID: _resourceID 4011 }; 4012 }, 4013 4014 /** 4015 * Utility function which sends a message to the dynamic tunnel frame 4016 * event frame formatted as follows: "type|message". 4017 * @param {Number} type 4018 * The category type of the message. 4019 * @param {String} message 4020 * The message to be sent to the tunnel frame. 4021 * @private 4022 */ 4023 _sendMessage = function (type, message) { 4024 message = type + "|" + message; 4025 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 4026 }, 4027 4028 /** 4029 * Utility to process the response of a subscribe request from 4030 * the tunnel frame, then invoking the stored callback handler 4031 * with the respective data (error, when applicable) 4032 * @param {String} data 4033 * The response in the format of "node[|error]" 4034 * @private 4035 */ 4036 _processSubscribeResponse = function (data) { 4037 var dataArray = data.split("|"), 4038 node = dataArray[0], 4039 err; 4040 4041 //Error is optionally the second item in the array 4042 if (dataArray.length) { 4043 err = dataArray[1]; 4044 } 4045 4046 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 4047 if (_handlers.subscribe[node]) { 4048 _handlers.subscribe[node](err); 4049 delete _handlers.subscribe[node]; 4050 } 4051 }, 4052 4053 /** 4054 * Utility to process the response of an unsubscribe request from 4055 * the tunnel frame, then invoking the stored callback handler 4056 * with the respective data (error, when applicable) 4057 * @param {String} data 4058 * The response in the format of "node[|error]" 4059 * @private 4060 */ 4061 _processUnsubscribeResponse = function (data) { 4062 var dataArray = data.split("|"), 4063 node = dataArray[0], 4064 err; 4065 4066 //Error is optionally the second item in the array 4067 if (dataArray.length) { 4068 err = dataArray[1]; 4069 } 4070 4071 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 4072 if (_handlers.unsubscribe[node]) { 4073 _handlers.unsubscribe[node](err); 4074 delete _handlers.unsubscribe[node]; 4075 } 4076 }, 4077 4078 4079 /** 4080 * Utility to process the reponse of an getSubscriptions request from 4081 * the tunnel frame, then invoking the stored callback handler 4082 * with the respective data (error, when applicable) 4083 * @param {String} data 4084 * The response containing subscriptions 4085 * @private 4086 */ 4087 _processAllSubscriptionsResponse = function (data) { 4088 var dataArray = data.split("|"), 4089 content = dataArray[0], 4090 err; 4091 4092 //Error is optionally the second item in the array 4093 if (dataArray.length) { 4094 err = dataArray[1]; 4095 } 4096 4097 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 4098 if (_handlers.subscriptions) { 4099 _handlers.subscriptions(content, err); 4100 delete _handlers.subscriptions; 4101 } 4102 }, 4103 4104 /** 4105 * Handler for messages delivered by window.postMessage. Listens for events 4106 * published by the notification server, connection status published by 4107 * the JabberWerx library, and the resource ID created when the BOSH 4108 * connection has been established. 4109 * @param {Object} e 4110 * The message object as provided by the window.postMessage feature. 4111 * @private 4112 */ 4113 _messageHandler = function (e) { 4114 if (typeof e.data !== 'string') { 4115 return; 4116 } 4117 var 4118 4119 //Extract the message type and message data. The expected format is 4120 //"type|data" where type is a number represented by the TYPES object. 4121 delimPos = e.data.indexOf("|"), 4122 type = Number(e.data.substr(0, delimPos)), 4123 data = e.data.substr(delimPos + 1); 4124 4125 //Accepts messages and invoke the correct registered handlers. 4126 switch (type) { 4127 case _TYPES.EVENT: 4128 _eventCache = data; 4129 if (typeof _eventHandler === "function") { 4130 _eventHandler(data); 4131 } 4132 break; 4133 case _TYPES.STATUS: 4134 _statusCache = data; 4135 4136 //A "loaded" status means that the frame is ready to accept 4137 //credentials for establishing a BOSH connection. 4138 if (data === "loaded") { 4139 _isTunnelLoaded = true; 4140 if(_resource) { 4141 _sendMessage(_TYPES.RESOURCEID, _resource); 4142 } 4143 var xmppProperties = JSON.stringify({xmppRequestTimeout: finesse.container.Config.xmppRequestTimeout, 4144 xmppFailoverDetectionPingInterval: finesse.container.Config.xmppFailoverDetectionPingInterval, 4145 xmppFailoverRequestMaxRetry: finesse.container.Config.xmppFailoverRequestMaxRetry 4146 }); 4147 _sendMessage(_TYPES.NOTIFICATION_CONNECTION_TYPE, _notificationConnectionType); 4148 _sendMessage(_TYPES.ID, _id); 4149 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 4150 _sendMessage(_TYPES.PASSWORD, _password); 4151 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 4152 _sendMessage(_TYPES.XMPP_DISCONNECT_PROPERTIES,xmppProperties); 4153 4154 4155 } else if (typeof _connInfoHandler === "function") { 4156 _connInfoHandler(_createConnInfoObj()); 4157 } 4158 break; 4159 case _TYPES.RESOURCEID: 4160 _resourceID = data; 4161 break; 4162 case _TYPES.SUBSCRIBE: 4163 _processSubscribeResponse(data); 4164 break; 4165 case _TYPES.UNSUBSCRIBE: 4166 _processUnsubscribeResponse(data); 4167 break; 4168 case _TYPES.SUBSCRIPTIONS_REQ: 4169 _processAllSubscriptionsResponse(data); 4170 break; 4171 case _TYPES.PRESENCE: 4172 if (typeof _presenceHandler === "function") { 4173 _presenceHandler(data); 4174 } 4175 break; 4176 case _TYPES.LOGGING: 4177 ClientLogger.log(data); 4178 break; 4179 4180 default: 4181 break; 4182 } 4183 }, 4184 4185 /** 4186 * Initialize the tunnel config so that the url can be http or https with the appropriate port 4187 * @private 4188 */ 4189 _initTunnelConfig = function () { 4190 if (_isTunnelConfigInit === true) { 4191 return; 4192 } 4193 4194 // For UCCX, Event tunnel is established via finesse desktop port 4195 if (window.finesse.container.Config.deploymentType === 'UCCX') { 4196 _tunnelOriginPort = window.location.port; 4197 } else { 4198 _tunnelOriginPort = "7443"; 4199 } 4200 4201 if (scheme) { 4202 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 4203 } else { 4204 _tunnelOrigin = "https://" + host + ":" + _tunnelOriginPort; 4205 } 4206 _tunnelURL = _tunnelOrigin + "/tunnel/"; 4207 4208 _isTunnelConfigInit = true; 4209 }, 4210 4211 /** 4212 * Create the tunnel iframe which establishes the shared BOSH connection. 4213 * Messages are sent across frames using window.postMessage. 4214 * @private 4215 */ 4216 _createTunnel = function () { 4217 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 4218 iframe = document.createElement("iframe"); 4219 iframe.style.display = "none"; 4220 iframe.setAttribute("id", tunnelID); 4221 iframe.setAttribute("name", tunnelID); 4222 iframe.setAttribute("src", _tunnelURL); 4223 document.body.appendChild(iframe); 4224 _tunnelFrame = window.frames[tunnelID]; 4225 }; 4226 4227 /** 4228 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 4229 * @private 4230 */ 4231 this.makeConnectReq = function () { 4232 _sendMessage(_TYPES.PASSWORD, _password); 4233 }; 4234 4235 /** 4236 * @private 4237 * Returns the host of the Finesse server. 4238 * @returns {String} 4239 * The host specified during the creation of the object. 4240 */ 4241 this.getHost = function () { 4242 return host; 4243 }; 4244 4245 /** 4246 * @private 4247 * The resource ID of the user who is logged into the notification server. 4248 * @returns {String} 4249 * The resource ID generated by the notification server. 4250 */ 4251 this.getResourceID = function () { 4252 return _resourceID; 4253 }; 4254 4255 /** 4256 * @private 4257 * Indicates whether the tunnel frame is loaded. 4258 * @returns {Boolean} 4259 * True if the tunnel frame is loaded, false otherwise. 4260 */ 4261 this.isTunnelLoaded = function () { 4262 return _isTunnelLoaded; 4263 }; 4264 4265 /** 4266 * @private 4267 * The location of the tunnel HTML URL. 4268 * @returns {String} 4269 * The location of the tunnel HTML URL. 4270 */ 4271 this.getTunnelURL = function () { 4272 return _tunnelURL; 4273 }; 4274 4275 /** 4276 * @private 4277 * Tunnels a subscribe request to the eventing iframe. 4278 * @param {String} node 4279 * The node to subscribe to 4280 * @param {Function} handler 4281 * Handler to invoke upon success or failure 4282 */ 4283 this.subscribe = function (node, handler) { 4284 if (handler && typeof handler !== "function") { 4285 throw new Error("Parameter is not a function."); 4286 } 4287 _handlers.subscribe[node] = handler; 4288 _sendMessage(_TYPES.SUBSCRIBE, node); 4289 }; 4290 4291 4292 /** 4293 * @private 4294 * Tunnels a get subscription request to the eventing iframe. 4295 * @param {Function} handler 4296 * Handler to invoke upon success or failure 4297 */ 4298 this.getSubscriptions = function (handler) { 4299 if (handler && typeof handler !== "function") { 4300 throw new Error("Parameter is not a function."); 4301 } 4302 _handlers.subscriptions = handler; 4303 _sendMessage(_TYPES.SUBSCRIPTIONS_REQ); 4304 }; 4305 4306 /** 4307 * @private 4308 * Tunnels an unsubscribe request to the eventing iframe. 4309 * @param {String} node 4310 * The node to unsubscribe from 4311 * @param {Function} handler 4312 * Handler to invoke upon success or failure 4313 */ 4314 this.unsubscribe = function (node, handler) { 4315 if (handler && typeof handler !== "function") { 4316 throw new Error("Parameter is not a function."); 4317 } 4318 _handlers.unsubscribe[node] = handler; 4319 _sendMessage(_TYPES.UNSUBSCRIBE, node); 4320 }; 4321 4322 /** 4323 * @private 4324 * Registers a handler to be invoked when an event is delivered. Only one 4325 * is registered at a time. If there has already been an event that was 4326 * delivered, the handler will be invoked immediately. 4327 * @param {Function} handler 4328 * Invoked when an event is delivered through the event connection. 4329 */ 4330 this.registerEventHandler = function (handler) { 4331 if (typeof handler !== "function") { 4332 throw new Error("Parameter is not a function."); 4333 } 4334 _eventHandler = handler; 4335 if (_eventCache) { 4336 handler(_eventCache); 4337 } 4338 }; 4339 4340 /** 4341 * @private 4342 * Unregisters the event handler completely. 4343 */ 4344 this.unregisterEventHandler = function () { 4345 _eventHandler = undefined; 4346 }; 4347 4348 /** 4349 * @private 4350 * Registers a handler to be invoked when a presence event is delivered. Only one 4351 * is registered at a time. 4352 * @param {Function} handler 4353 * Invoked when a presence event is delivered through the event connection. 4354 */ 4355 this.registerPresenceHandler = function (handler) { 4356 if (typeof handler !== "function") { 4357 throw new Error("Parameter is not a function."); 4358 } 4359 _presenceHandler = handler; 4360 }; 4361 4362 /** 4363 * @private 4364 * Unregisters the presence event handler completely. 4365 */ 4366 this.unregisterPresenceHandler = function () { 4367 _presenceHandler = undefined; 4368 }; 4369 4370 /** 4371 * @private 4372 * Registers a handler to be invoked when a connection status changes. The 4373 * object passed will contain a "status" property, and a "resourceID" 4374 * property, which will contain the most current resource ID assigned to 4375 * the client. If there has already been an event that was delivered, the 4376 * handler will be invoked immediately. 4377 * @param {Function} handler 4378 * Invoked when a connection status changes. 4379 */ 4380 this.registerConnectionInfoHandler = function (handler) { 4381 if (typeof handler !== "function") { 4382 throw new Error("Parameter is not a function."); 4383 } 4384 _connInfoHandler = handler; 4385 if (_statusCache) { 4386 handler(_createConnInfoObj()); 4387 } 4388 }; 4389 4390 /** 4391 * @private 4392 * Unregisters the connection information handler. 4393 */ 4394 this.unregisterConnectionInfoHandler = function () { 4395 _connInfoHandler = undefined; 4396 }; 4397 4398 /** 4399 * @private 4400 * Start listening for events and create a event tunnel for the shared BOSH 4401 * connection. 4402 * @param {String} id 4403 * The ID of the user for the notification server. 4404 * @param {String} password 4405 * The password of the user for the notification server. 4406 * @param {String} xmppDomain 4407 * The XMPP domain of the notification server 4408 * @param {String} pubsubDomain 4409 * The location (JID) of the XEP-0060 PubSub service 4410 * @param {String} resource 4411 * The resource to connect to the notification servier with. 4412 * @param {String} notificationConnectionType 4413 * The xmpp connection protocol type : websocket or BOSH. 4414 */ 4415 this.init = function (id, password, xmppDomain, pubsubDomain, resource, notificationConnectionType) { 4416 4417 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string" || typeof notificationConnectionType !== "string") { 4418 throw new Error("Invalid or missing required parameters."); 4419 } 4420 4421 _initTunnelConfig(); 4422 4423 _id = id; 4424 _password = password; 4425 _xmppDomain = xmppDomain; 4426 _pubsubDomain = pubsubDomain; 4427 _resource = resource; 4428 _notificationConnectionType = notificationConnectionType; 4429 4430 //Attach a listener for messages sent from tunnel frame. 4431 _util.receiveMessage(_messageHandler, _tunnelOrigin); 4432 4433 //Create the tunnel iframe which will establish the shared connection. 4434 _createTunnel(); 4435 }; 4436 4437 //BEGIN TEST CODE// 4438 // /** 4439 // * Test code added to expose private functions that are used by unit test 4440 // * framework. This section of code is removed during the build process 4441 // * before packaging production code. The [begin|end]TestSection are used 4442 // * by the build to identify the section to strip. 4443 // * @ignore 4444 // */ 4445 // this.beginTestSection = 0; 4446 // 4447 // /** 4448 // * @ignore 4449 // */ 4450 // this.getTestObject = function () { 4451 // //Load mock dependencies. 4452 // var _mock = new MockControl(); 4453 // _util = _mock.createMock(finesse.utilities.Utilities); 4454 // 4455 // return { 4456 // //Expose mock dependencies 4457 // mock: _mock, 4458 // util: _util, 4459 // 4460 // //Expose internal private functions 4461 // types: _TYPES, 4462 // createConnInfoObj: _createConnInfoObj, 4463 // sendMessage: _sendMessage, 4464 // messageHandler: _messageHandler, 4465 // createTunnel: _createTunnel, 4466 // handlers: _handlers, 4467 // initTunnelConfig : _initTunnelConfig 4468 // }; 4469 // }; 4470 // 4471 // /** 4472 // * @ignore 4473 // */ 4474 // this.endTestSection = 0; 4475 // //END TEST CODE// 4476 }; 4477 4478 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 4479 finesse.clientservices = finesse.clientservices || {}; 4480 4481 window.finesse = window.finesse || {}; 4482 window.finesse.clientservices = window.finesse.clientservices || {}; 4483 window.finesse.clientservices.MasterTunnel = MasterTunnel; 4484 4485 return MasterTunnel; 4486 4487 }); 4488 4489 /** 4490 * Contains a list of topics used for client side pubsub. 4491 * 4492 */ 4493 4494 /** @private */ 4495 define('clientservices/Topics',[], function () { 4496 4497 var Topics = (function () { 4498 4499 /** 4500 * @private 4501 * The namespace prepended to all Finesse topics. 4502 */ 4503 this.namespace = "finesse.info"; 4504 4505 /** 4506 * @private 4507 * Gets the full topic name with the Finesse namespace prepended. 4508 * @param {String} topic 4509 * The topic category. 4510 * @returns {String} 4511 * The full topic name with prepended namespace. 4512 */ 4513 var _getNSTopic = function (topic) { 4514 return this.namespace + "." + topic; 4515 }; 4516 4517 /** @scope finesse.clientservices.Topics */ 4518 return { 4519 /** 4520 * @private 4521 * Client side request channel. 4522 */ 4523 REQUESTS: _getNSTopic("requests"), 4524 4525 /** 4526 * @private 4527 * Client side response channel. 4528 */ 4529 RESPONSES: _getNSTopic("responses"), 4530 4531 /** 4532 * @private 4533 * Connection status. 4534 */ 4535 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 4536 4537 /** 4538 * @private 4539 * Presence channel 4540 */ 4541 PRESENCE: _getNSTopic("presence"), 4542 4543 /** 4544 * Topic for listening to token refresh events. 4545 * The provided callback will be invoked when the access token is refreshed. 4546 * This event is only meant for updating the access token in gadget Config object 4547 */ 4548 ACCESS_TOKEN_REFRESHED_EVENT: _getNSTopic("accessTokenRefresh"), 4549 4550 /** 4551 * @private 4552 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 4553 */ 4554 getTopic: function (restUri) { 4555 //The topic should not start with '/' else it will get replaced with 4556 //'.' which is invalid. 4557 //Thus, remove '/' if it is at the beginning of the string 4558 if (restUri.indexOf('/') === 0) { 4559 restUri = restUri.substr(1); 4560 } 4561 4562 //Replace every instance of "/" with ".". This is done to follow the 4563 //OpenAjaxHub topic name convention. 4564 return restUri.replace(/\//g, "."); 4565 } 4566 }; 4567 }()); 4568 window.finesse = window.finesse || {}; 4569 window.finesse.clientservices = window.finesse.clientservices || {}; 4570 /** @private */ 4571 window.finesse.clientservices.Topics = Topics; 4572 4573 return Topics; 4574 }); 4575 /** The following comment is to prevent jslint errors about 4576 * using variables before they are defined. 4577 */ 4578 /*global finesse*/ 4579 4580 /** 4581 * Registers with the MasterTunnel to receive events, which it 4582 * could publish to the OpenAjax gadget pubsub infrastructure. 4583 * 4584 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 4585 */ 4586 4587 /** @private */ 4588 define('clientservices/MasterPublisher',[ 4589 "clientservices/MasterTunnel", 4590 "clientservices/Topics", 4591 "utilities/Utilities" 4592 ], 4593 function (MasterTunnel, Topics, Utilities) { 4594 4595 var MasterPublisher = function (tunnel, hub) { 4596 if (!(tunnel instanceof MasterTunnel)) { 4597 throw new Error("Required tunnel object missing or invalid."); 4598 } 4599 4600 var 4601 4602 ClientServices = finesse.clientservices.ClientServices, 4603 4604 /** 4605 * Reference to the gadget pubsub Hub instance. 4606 * @private 4607 */ 4608 _hub = hub, 4609 4610 /** 4611 * Reference to the Topics class. 4612 * @private 4613 */ 4614 _topics = Topics, 4615 4616 /** 4617 * Reference to conversion utilities class. 4618 * @private 4619 */ 4620 _utils = Utilities, 4621 4622 /** 4623 * References to ClientServices logger methods 4624 * @private 4625 */ 4626 _logger = { 4627 log: ClientServices.log 4628 }, 4629 4630 /** 4631 * Store the passed in tunnel. 4632 * @private 4633 */ 4634 _tunnel = tunnel, 4635 4636 /** 4637 * Caches the connection info event so that it could be published if there 4638 * is a request for it. 4639 * @private 4640 */ 4641 _connInfoCache = {}, 4642 4643 /** 4644 * The types of possible request types supported when listening to the 4645 * requests channel. Each request type could result in different operations. 4646 * @private 4647 */ 4648 _REQTYPES = { 4649 CONNECTIONINFO: "ConnectionInfoReq", 4650 SUBSCRIBE: "SubscribeNodeReq", 4651 UNSUBSCRIBE: "UnsubscribeNodeReq", 4652 SUBSCRIPTIONSINFO: "SubscriptionsInfoReq", 4653 CONNECT: "ConnectionReq" 4654 }, 4655 4656 /** 4657 * Will store list of nodes that have OF subscriptions created 4658 * _nodesList[node][subscribing].reqIds[subid] 4659 * _nodesList[node][active].reqIds[subid] 4660 * _nodesList[node][unsubscribing].reqIds[subid] 4661 * _nodesList[node][holding].reqIds[subid] 4662 * @private 4663 */ 4664 _nodesList = {}, 4665 4666 /** 4667 * The states that a subscription can be in 4668 * @private 4669 */ 4670 _CHANNELSTATES = { 4671 UNINITIALIZED: "Uninitialized", 4672 PENDING: "Pending", 4673 OPERATIONAL: "Operational" 4674 }, 4675 4676 /** 4677 * Checks if the payload is JSON 4678 * @returns {Boolean} 4679 * @private 4680 */ 4681 _isJsonPayload = function(event) { 4682 var delimStart, delimEnd, retval = false; 4683 4684 try { 4685 delimStart = event.indexOf('{'); 4686 delimEnd = event.lastIndexOf('}'); 4687 4688 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 4689 retval = true; //event contains JSON payload 4690 } 4691 } catch (err) { 4692 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 4693 } 4694 return retval; 4695 }, 4696 4697 /** 4698 * Parses a JSON event and then publishes. 4699 * 4700 * @param {String} event 4701 * The full event payload. 4702 * @throws {Error} If the payload object is malformed. 4703 * @private 4704 */ 4705 _parseAndPublishJSONEvent = function(event) { 4706 var topic, eventObj, publishEvent, 4707 delimPos = event.indexOf("{"), 4708 node, parser, 4709 eventJson = event, 4710 returnObj = {node: null, data: null}; 4711 4712 try { 4713 //Extract and strip the node path from the message 4714 if (delimPos > 0) 4715 { 4716 //We need to decode the URI encoded node path 4717 //TODO: make sure this is kosher with OpenAjax topic naming 4718 node = decodeURI(event.substr(0, delimPos)); 4719 eventJson = event.substr(delimPos); 4720 4721 //Converting the node path to openAjaxhub topic 4722 topic = _topics.getTopic(node); 4723 4724 returnObj.node = node; 4725 returnObj.payload = eventJson; 4726 } 4727 else 4728 { 4729 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 4730 throw new Error("node is not given in postMessage: " + eventJson); 4731 } 4732 4733 parser = _utils.getJSONParser(); 4734 4735 eventObj = parser.parse(eventJson); 4736 returnObj.data = eventObj; 4737 4738 } catch (err) { 4739 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 4740 throw new Error("Malformed event payload : " + err); 4741 } 4742 4743 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 4744 4745 publishEvent = {content : event, object : eventObj }; 4746 4747 //Publish event to proper event topic. 4748 if (topic && eventObj) { 4749 _hub.publish(topic, publishEvent); 4750 } 4751 }, 4752 4753 /** 4754 * Parses an XML event and then publishes. 4755 * 4756 * @param {String} event 4757 * The full event payload. 4758 * @throws {Error} If the payload object is malformed. 4759 * @private 4760 */ 4761 _parseAndPublishXMLEvent = function(event) { 4762 var topic, eventObj, publishEvent, restTopic, 4763 delimPos = event.indexOf("<"), 4764 node, 4765 eventXml = event; 4766 4767 try { 4768 //Extract and strip the node path from the message 4769 if (delimPos > 0) { 4770 //We need to decode the URI encoded node path 4771 //TODO: make sure this is kosher with OpenAjax topic naming 4772 node = decodeURI(event.substr(0, delimPos)); 4773 eventXml = event.substr(delimPos); 4774 //Converting the node path to openAjaxhub topic 4775 topic = _topics.getTopic(node); 4776 } else { 4777 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 4778 throw new Error("node is not given in postMessage: " + eventXml); 4779 } 4780 4781 eventObj = _utils.xml2JsObj(eventXml); 4782 4783 } catch (err) { 4784 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 4785 throw new Error("Malformed event payload : " + err); 4786 } 4787 4788 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 4789 4790 publishEvent = {content : event, object : eventObj }; 4791 4792 //Publish event to proper event topic. 4793 if (topic && eventObj) { 4794 // Making sure only User object sent by User api response are updated in cache 4795 if(topic.indexOf('User.') > -1 && eventObj.Update && eventObj.Update.data && eventObj.Update.data.user) { 4796 var removeDataElement = eventXml.substring(eventXml.indexOf('<user>'), eventXml.indexOf('</user>')+ 7); 4797 /** 4798 * event is for user object then replace <user> with <User>. 4799 * In case of xmpp event <user> element starts with small u letter. 4800 * Where as in rest response <User> element starts with capital U letter. 4801 */ 4802 var userObjXml = removeDataElement.replace("<user>", "<User>").replace("</user>", "</User>"); 4803 sessionStorage.setItem('User',userObjXml); 4804 4805 } 4806 _hub.publish(topic, publishEvent); 4807 } 4808 }, 4809 4810 /** 4811 * Publishes events to the appropriate topic. The topic name is determined 4812 * by fetching the source value from the event. 4813 * @param {String} event 4814 * The full event payload. 4815 * @throws {Error} If the payload object is malformed. 4816 * @private 4817 */ 4818 _eventHandler = function (event) { 4819 4820 //Handle JSON or XML events 4821 if (!_isJsonPayload(event)) 4822 { 4823 //XML 4824 _parseAndPublishXMLEvent(event); 4825 } 4826 else 4827 { 4828 //JSON 4829 _parseAndPublishJSONEvent(event); 4830 } 4831 }, 4832 4833 4834 /** 4835 * Handler for when presence events are sent through the MasterTunnel. 4836 * @returns {Object} 4837 * A presence xml event. 4838 * @private 4839 */ 4840 _presenceHandler = function (event) { 4841 var eventObj = _utils.xml2JsObj(event), publishEvent; 4842 4843 publishEvent = {content : event, object : eventObj}; 4844 4845 if (eventObj) { 4846 _hub.publish(_topics.PRESENCE, publishEvent); 4847 } 4848 }, 4849 4850 /** 4851 * Clone the connection info object from cache. 4852 * @returns {Object} 4853 * A connection info object containing a "status" and "resourceID". 4854 * @private 4855 */ 4856 _cloneConnInfoObj = function () { 4857 if (_connInfoCache) { 4858 return { 4859 status: _connInfoCache.status, 4860 resourceID: _connInfoCache.resourceID 4861 }; 4862 } else { 4863 return null; 4864 } 4865 }, 4866 4867 /** 4868 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4869 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4870 * @private 4871 */ 4872 _cleanupPendingRequests = function () { 4873 var node, curSubid, errObj = { 4874 error: { 4875 errorType: "Disconnected", 4876 errorMessage: "Outstanding request will never complete." 4877 } 4878 }; 4879 4880 // Iterate through all outstanding subscribe requests to notify them that it will never return 4881 for (node in _nodesList) { 4882 if (_nodesList.hasOwnProperty(node)) { 4883 for (curSubid in _nodesList[node].subscribing.reqIds) { 4884 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4885 // Notify this outstanding subscribe request to give up and error out 4886 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4887 } 4888 } 4889 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4890 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4891 // Notify this outstanding unsubscribe request to give up and error out 4892 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4893 } 4894 } 4895 } 4896 } 4897 }, 4898 4899 /** 4900 * Publishes the connection info to the connection info topic. 4901 * @param {Object} connInfo 4902 * The connection info object containing the status and resource ID. 4903 * @private 4904 */ 4905 _connInfoHandler = function (connInfo) { 4906 var before = _connInfoCache.status; 4907 _connInfoCache = connInfo; 4908 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4909 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4910 if (before === "connected" && connInfo.status !== "connected") { 4911 // Fail all pending requests when we transition to disconnected 4912 _cleanupPendingRequests(); 4913 } 4914 }, 4915 4916 /** 4917 * Get's all the subscriptions in the Openfire through EventTunnel and publishes the info to the topic 4918 * @param {String} invokeId 4919 * The request id 4920 * @private 4921 */ 4922 _getAllSubscriptions = function(invokeId) { 4923 // _logger.log("MasterPublisher._getAllSubscriptions() - Getting all subscriptions "); 4924 4925 _tunnel.getSubscriptions (function (content) { 4926 _hub.publish(_topics.RESPONSES + "." + invokeId, content); 4927 }); 4928 4929 }, 4930 4931 4932 /** 4933 * Utility method to bookkeep node subscription requests and determine 4934 * whehter it is necessary to tunnel the request to JabberWerx. 4935 * @param {String} node 4936 * The node of interest 4937 * @param {String} reqId 4938 * A unique string identifying the request/subscription 4939 * @private 4940 */ 4941 _subscribeNode = function (node, subid) { 4942 if (_connInfoCache.status !== "connected") { 4943 _hub.publish(_topics.RESPONSES + "." + subid, { 4944 error: { 4945 errorType: "Not connected", 4946 errorMessage: "Cannot subscribe without connection." 4947 } 4948 }); 4949 return; 4950 } 4951 // NODE DOES NOT YET EXIST 4952 if (!_nodesList[node]) { 4953 _nodesList[node] = { 4954 "subscribing": { 4955 "reqIds": {}, 4956 "length": 0 4957 }, 4958 "active": { 4959 "reqIds": {}, 4960 "length": 0 4961 }, 4962 "unsubscribing": { 4963 "reqIds": {}, 4964 "length": 0 4965 }, 4966 "holding": { 4967 "reqIds": {}, 4968 "length": 0 4969 } 4970 }; 4971 } 4972 if (_nodesList[node].active.length === 0) { 4973 if (_nodesList[node].unsubscribing.length === 0) { 4974 if (_nodesList[node].subscribing.length === 0) { 4975 _nodesList[node].subscribing.reqIds[subid] = true; 4976 _nodesList[node].subscribing.length += 1; 4977 4978 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4979 _tunnel.subscribe(node, function (err) { 4980 var errObj, curSubid; 4981 if (err) { 4982 errObj = { 4983 subscribe: { 4984 content: err 4985 } 4986 }; 4987 4988 try { 4989 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4990 } catch (e) { 4991 errObj.error = { 4992 errorType: "parseError", 4993 errorMessage: "Could not serialize XML: " + e 4994 }; 4995 } 4996 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4997 } else { 4998 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4999 } 5000 5001 for (curSubid in _nodesList[node].subscribing.reqIds) { 5002 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 5003 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 5004 if (!err) { 5005 _nodesList[node].active.reqIds[curSubid] = true; 5006 _nodesList[node].active.length += 1; 5007 } 5008 delete _nodesList[node].subscribing.reqIds[curSubid]; 5009 _nodesList[node].subscribing.length -= 1; 5010 } 5011 } 5012 }); 5013 5014 } else { //other ids are subscribing 5015 _nodesList[node].subscribing.reqIds[subid] = true; 5016 _nodesList[node].subscribing.length += 1; 5017 } 5018 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 5019 _nodesList[node].holding.reqIds[subid] = true; 5020 _nodesList[node].holding.length += 1; 5021 } 5022 } else { // The node has active subscriptions; add this subid and return successful response 5023 _nodesList[node].active.reqIds[subid] = true; 5024 _nodesList[node].active.length += 1; 5025 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 5026 } 5027 }, 5028 5029 /** 5030 * Utility method to bookkeep node unsubscribe requests and determine 5031 * whehter it is necessary to tunnel the request to JabberWerx. 5032 * @param {String} node 5033 * The node to unsubscribe from 5034 * @param {String} reqId 5035 * A unique string identifying the subscription to remove 5036 * @param {Boolean} isForceOp 5037 * Boolean flag if true then the subscription will be forefully removed even when active references to the node are nil. 5038 * @private 5039 */ 5040 _unsubscribeNode = function (node, subid, isForceOp) { 5041 if (!_nodesList[node] && !isForceOp ) { //node DNE, publish success response 5042 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 5043 } else { 5044 if (_connInfoCache.status !== "connected") { 5045 _hub.publish(_topics.RESPONSES + "." + subid, { 5046 error: { 5047 errorType: "Not connected", 5048 errorMessage: "Cannot unsubscribe without connection." 5049 } 5050 }); 5051 return; 5052 } 5053 if (_nodesList[node] && _nodesList[node].active.length > 1) { 5054 delete _nodesList[node].active.reqIds[subid]; 5055 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 5056 _nodesList[node].active.length -= 1; 5057 } else if (_nodesList[node] && _nodesList[node].active.length === 1 || isForceOp) { // transition subid from active category to unsubscribing category 5058 5059 if (_nodesList[node]) { 5060 _nodesList[node].unsubscribing.reqIds[subid] = true; 5061 _nodesList[node].unsubscribing.length += 1; 5062 delete _nodesList[node].active.reqIds[subid]; 5063 _nodesList[node].active.length -= 1; 5064 } 5065 5066 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 5067 var requestId = subid; 5068 _tunnel.unsubscribe(node, function (err) { 5069 var errObj, curSubid; 5070 if (err) { 5071 errObj = { 5072 subscribe: { 5073 content: err 5074 } 5075 }; 5076 5077 try { 5078 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 5079 } catch (e) { 5080 errObj.error = { 5081 errorType: "parseError", 5082 errorMessage: "Could not serialize XML: " + e 5083 }; 5084 } 5085 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 5086 } else { 5087 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 5088 } 5089 5090 if (_nodesList[node]) { 5091 5092 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 5093 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 5094 // publish to all subids whether unsubscribe failed or succeeded 5095 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 5096 if (!err) { 5097 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 5098 _nodesList[node].unsubscribing.length -= 1; 5099 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 5100 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 5101 _nodesList[node].unsubscribing.length -= 1; 5102 } 5103 } 5104 } 5105 5106 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 5107 for (curSubid in _nodesList[node].holding.reqIds) { 5108 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 5109 delete _nodesList[node].holding.reqIds[curSubid]; 5110 _nodesList[node].holding.length -= 1; 5111 _subscribeNode(node, curSubid); 5112 } 5113 } 5114 } 5115 } else { 5116 _hub.publish(_topics.RESPONSES + "." + requestId, undefined); 5117 } 5118 }); 5119 } else { // length <= 0? 5120 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 5121 } 5122 } 5123 }, 5124 5125 5126 5127 /** 5128 * Handles client requests to establish a BOSH connection. 5129 * @param {String} id 5130 * id of the xmpp user 5131 * @param {String} password 5132 * password of the xmpp user 5133 * @param {String} xmppDomain 5134 * xmppDomain of the xmpp user account 5135 * @private 5136 */ 5137 _connect = function (id, password, xmppDomain) { 5138 _tunnel.makeConnectReq(id, password, xmppDomain); 5139 }, 5140 5141 /** 5142 * Handles client requests made to the request topic. The type of the 5143 * request is described in the "type" property within the data payload. Each 5144 * type can result in a different operation. 5145 * @param {String} topic 5146 * The topic which data was published to. 5147 * @param {Object} data 5148 * The data containing requests information published by clients. 5149 * @param {String} data.type 5150 * The type of the request. Supported: "ConnectionInfoReq" 5151 * @param {Object} data.data 5152 * May contain data relevant for the particular requests. 5153 * @param {String} [data.invokeID] 5154 * The ID used to identify the request with the response. The invoke ID 5155 * will be included in the data in the publish to the topic. It is the 5156 * responsibility of the client to correlate the published data to the 5157 * request made by using the invoke ID. 5158 * @private 5159 */ 5160 _clientRequestHandler = function (topic, data) { 5161 var dataCopy; 5162 5163 //Ensure a valid data object with "type" and "data" properties. 5164 if (typeof data === "object" && 5165 typeof data.type === "string" && 5166 typeof data.data === "object") { 5167 switch (data.type) { 5168 case _REQTYPES.CONNECTIONINFO: 5169 //It is possible that Slave clients come up before the Master 5170 //client. If that is the case, the Slaves will need to make a 5171 //request for the Master to send the latest connection info to the 5172 //connectionInfo topic. 5173 dataCopy = _cloneConnInfoObj(); 5174 if (dataCopy) { 5175 if (data.invokeID !== undefined) { 5176 dataCopy.invokeID = data.invokeID; 5177 } 5178 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 5179 } 5180 break; 5181 case _REQTYPES.SUBSCRIBE: 5182 if (typeof data.data.node === "string") { 5183 _subscribeNode(data.data.node, data.invokeID); 5184 } 5185 break; 5186 case _REQTYPES.UNSUBSCRIBE: 5187 if (typeof data.data.node === "string") { 5188 _unsubscribeNode(data.data.node, data.invokeID, data.isForceOp ); 5189 } 5190 break; 5191 case _REQTYPES.SUBSCRIPTIONSINFO: 5192 _getAllSubscriptions(data.invokeID); 5193 break; 5194 case _REQTYPES.CONNECT: 5195 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 5196 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5197 break; 5198 default: 5199 break; 5200 } 5201 } 5202 }; 5203 5204 (function () { 5205 //Register to receive events and connection status from tunnel. 5206 _tunnel.registerEventHandler(_eventHandler); 5207 _tunnel.registerPresenceHandler(_presenceHandler); 5208 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 5209 5210 //Listen to a request channel to respond to any requests made by other 5211 //clients because the Master may have access to useful information. 5212 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 5213 }()); 5214 5215 /** 5216 * @private 5217 * Handles client requests to establish a BOSH connection. 5218 * @param {String} id 5219 * id of the xmpp user 5220 * @param {String} password 5221 * password of the xmpp user 5222 * @param {String} xmppDomain 5223 * xmppDomain of the xmpp user account 5224 */ 5225 this.connect = function (id, password, xmppDomain) { 5226 _connect(id, password, xmppDomain); 5227 }; 5228 5229 /** 5230 * @private 5231 * Resets the list of explicit subscriptions 5232 */ 5233 this.wipeout = function () { 5234 _cleanupPendingRequests(); 5235 _nodesList = {}; 5236 }; 5237 5238 //BEGIN TEST CODE// 5239 /** 5240 * Test code added to expose private functions that are used by unit test 5241 * framework. This section of code is removed during the build process 5242 * before packaging production code. The [begin|end]TestSection are used 5243 * by the build to identify the section to strip. 5244 * @ignore 5245 */ 5246 this.beginTestSection = 0; 5247 5248 /** 5249 * @ignore 5250 */ 5251 this.getTestObject = function () { 5252 //Load mock dependencies. 5253 var _mock = new MockControl(); 5254 _hub = _mock.createMock(gadgets.Hub); 5255 _tunnel = _mock.createMock(); 5256 5257 return { 5258 //Expose mock dependencies 5259 mock: _mock, 5260 hub: _hub, 5261 tunnel: _tunnel, 5262 setTunnel: function (tunnel) { 5263 _tunnel = tunnel; 5264 }, 5265 getTunnel: function () { 5266 return _tunnel; 5267 }, 5268 5269 //Expose internal private functions 5270 reqtypes: _REQTYPES, 5271 eventHandler: _eventHandler, 5272 presenceHandler: _presenceHandler, 5273 5274 subscribeNode: _subscribeNode, 5275 unsubscribeNode: _unsubscribeNode, 5276 5277 getNodeList: function () { 5278 return _nodesList; 5279 }, 5280 setNodeList: function (nodelist) { 5281 _nodesList = nodelist; 5282 }, 5283 5284 cloneConnInfoObj: _cloneConnInfoObj, 5285 connInfoHandler: _connInfoHandler, 5286 clientRequestHandler: _clientRequestHandler 5287 5288 }; 5289 }; 5290 5291 5292 /** 5293 * @ignore 5294 */ 5295 this.endTestSection = 0; 5296 //END TEST CODE// 5297 5298 }; 5299 5300 window.finesse = window.finesse || {}; 5301 window.finesse.clientservices = window.finesse.clientservices || {}; 5302 window.finesse.clientservices.MasterPublisher = MasterPublisher; 5303 5304 return MasterPublisher; 5305 }); 5306 5307 /** The following comment is to prevent jslint errors about 5308 * using variables before they are defined. 5309 */ 5310 /*global publisher:true */ 5311 5312 /** 5313 * Exposes a set of API wrappers that will hide the dirty work of 5314 * constructing Finesse API requests and consuming Finesse events. 5315 * 5316 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 5317 */ 5318 5319 5320 /** 5321 * Allow clients to make Finesse API requests and consume Finesse events by 5322 * calling a set of exposed functions. The Services layer will do the dirty 5323 * work of establishing a shared BOSH connection (for designated Master 5324 * modules), consuming events for client subscriptions, and constructing API 5325 * requests. 5326 */ 5327 /** @private */ 5328 define('clientservices/ClientServices',[ 5329 "clientservices/MasterTunnel", 5330 "clientservices/MasterPublisher", 5331 "clientservices/Topics", 5332 "utilities/Utilities" 5333 ], 5334 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 5335 5336 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 5337 var 5338 5339 /** 5340 * Shortcut reference to the master tunnel 5341 * @private 5342 */ 5343 _tunnel, 5344 5345 _publisher, 5346 5347 /** 5348 * Shortcut reference to the finesse.utilities.Utilities singleton 5349 * This will be set by init() 5350 * @private 5351 */ 5352 _util, 5353 5354 /** 5355 * Shortcut reference to the gadgets.io object. 5356 * This will be set by init() 5357 * @private 5358 */ 5359 _io, 5360 5361 /** 5362 * Shortcut reference to the gadget pubsub Hub instance. 5363 * This will be set by init() 5364 * @private 5365 */ 5366 _hub, 5367 5368 /** 5369 * Logger object set externally by setLogger, defaults to nothing. 5370 * @private 5371 */ 5372 _logger = {}, 5373 5374 /** 5375 * Shortcut reference to the Topics class. 5376 * This will be set by init() 5377 * @private 5378 */ 5379 _topics, 5380 5381 /** 5382 * Config object needed to initialize this library 5383 * This must be set by init() 5384 * @private 5385 */ 5386 _config, 5387 5388 /** 5389 * @private 5390 * Whether or not this ClientService instance is a Master. 5391 */ 5392 _isMaster = false, 5393 5394 /** 5395 * @private 5396 * Whether the Client Services have been initiated yet. 5397 */ 5398 _inited = false, 5399 5400 /** 5401 * Stores the list of subscription IDs for all subscriptions so that it 5402 * could be retrieve for unsubscriptions. 5403 * @private 5404 */ 5405 _subscriptionID = {}, 5406 5407 _restResponseCache = {}, 5408 5409 /** 5410 * The possible states of the JabberWerx BOSH connection. 5411 * @private 5412 */ 5413 _STATUS = { 5414 CONNECTING: "connecting", 5415 CONNECTED: "connected", 5416 DISCONNECTED: "disconnected", 5417 DISCONNECTED_CONFLICT: "conflict", 5418 DISCONNECTED_UNAUTHORIZED: "unauthorized", 5419 DISCONNECTING: "disconnecting", 5420 RECONNECTING: "reconnecting", 5421 UNLOADING: "unloading", 5422 FAILING: "failing", 5423 RECOVERED: "recovered" 5424 }, 5425 5426 /** 5427 * Local reference for authMode enum object. 5428 * @private 5429 */ 5430 _authModes, 5431 5432 _failoverMode = false, 5433 5434 /** 5435 * Handler function to be invoked when BOSH connection is connecting. 5436 * @private 5437 */ 5438 _onConnectingHandler, 5439 5440 /** 5441 * Handler function to be invoked when BOSH connection is connected 5442 * @private 5443 */ 5444 _onConnectHandler, 5445 5446 /** 5447 * Handler function to be invoked when BOSH connection is disconnecting. 5448 * @private 5449 */ 5450 _onDisconnectingHandler, 5451 5452 /** 5453 * Handler function to be invoked when the BOSH is disconnected. 5454 * @private 5455 */ 5456 _onDisconnectHandler, 5457 5458 /** 5459 * Handler function to be invoked when the BOSH is reconnecting. 5460 * @private 5461 */ 5462 _onReconnectingHandler, 5463 5464 /** 5465 * Handler function to be invoked when the BOSH is unloading. 5466 * @private 5467 */ 5468 _onUnloadingHandler, 5469 5470 /** 5471 * Contains a cache of the latest connection info containing the current 5472 * state of the BOSH connection and the resource ID. 5473 * @private 5474 */ 5475 _connInfo, 5476 5477 /** 5478 * Keeps track of all the objects that need to be refreshed when we recover 5479 * due to our resilient connection. Only objects that we subscribe to will 5480 * be added to this list. 5481 * @private 5482 */ 5483 _refreshList = [], 5484 5485 /** 5486 * Needs to be passed as authorization header inside makeRequest wrapper function 5487 */ 5488 _authHeaderString, 5489 5490 /** 5491 * @private 5492 * Centralized logger.log method for external logger 5493 * @param {String} msg 5494 * @param {Object} [Optional] Javascript error object 5495 */ 5496 _log = function (msg, e) { 5497 // If the external logger throws up, it stops here. 5498 try { 5499 if (_logger.log) { 5500 _logger.log("[ClientServices] " + msg, e); 5501 } 5502 } catch (e) { } 5503 }, 5504 5505 /** 5506 * Go through each object in the _refreshList and call its refresh() function 5507 * @private 5508 */ 5509 _refreshObjects = function () { 5510 var i; 5511 5512 // wipe out the explicit subscription list before we refresh objects 5513 if (_publisher) { 5514 _publisher.wipeout(); 5515 } 5516 5517 // refresh each item in the refresh list 5518 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5519 try{ 5520 _log("Refreshing " + _refreshList[i].getRestUrl()); 5521 _refreshList[i].refresh(10); 5522 } 5523 catch(e){ 5524 _log("ClientServices._refreshObjects() unexpected error while refreshing object" + e); 5525 } 5526 } 5527 }, 5528 5529 /** 5530 * Handler to process connection info publishes. 5531 * @param {Object} data 5532 * The connection info data object. 5533 * @param {String} data.status 5534 * The BOSH connection status. 5535 * @param {String} data.resourceID 5536 * The resource ID for the connection. 5537 * @private 5538 */ 5539 _connInfoHandler = function (data) { 5540 5541 //Invoke registered handler depending on status received. Due to the 5542 //request topic where clients can make request for the Master to publish 5543 //the connection info, there is a chance that duplicate connection info 5544 //events may be sent, so ensure that there has been a state change 5545 //before invoking the handlers. 5546 if (_connInfo === undefined || _connInfo.status !== data.status) { 5547 _connInfo = data; 5548 switch (data.status) { 5549 case _STATUS.CONNECTING: 5550 if (_isMaster && _onConnectingHandler) { 5551 _onConnectingHandler(); 5552 } 5553 break; 5554 case _STATUS.CONNECTED: 5555 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 5556 _onConnectHandler(); 5557 } 5558 break; 5559 case _STATUS.DISCONNECTED: 5560 if (_isMaster && _onDisconnectHandler) { 5561 _onDisconnectHandler(); 5562 } 5563 break; 5564 case _STATUS.DISCONNECTED_CONFLICT: 5565 if (_isMaster && _onDisconnectHandler) { 5566 _onDisconnectHandler("conflict"); 5567 } 5568 break; 5569 case _STATUS.DISCONNECTED_UNAUTHORIZED: 5570 if (_isMaster && _onDisconnectHandler) { 5571 _onDisconnectHandler("unauthorized"); 5572 } 5573 break; 5574 case _STATUS.DISCONNECTING: 5575 if (_isMaster && _onDisconnectingHandler) { 5576 _onDisconnectingHandler(); 5577 } 5578 break; 5579 case _STATUS.RECONNECTING: 5580 if (_isMaster && _onReconnectingHandler) { 5581 _onReconnectingHandler(); 5582 } 5583 break; 5584 case _STATUS.UNLOADING: 5585 if (_isMaster && _onUnloadingHandler) { 5586 _onUnloadingHandler(); 5587 } 5588 break; 5589 case _STATUS.FAILING: 5590 if (!_isMaster) { 5591 // Stop 5592 _failoverMode = true; 5593 if (_onDisconnectHandler) { 5594 _onDisconnectHandler(); 5595 } 5596 } 5597 break; 5598 case _STATUS.RECOVERED: 5599 if (!_isMaster) { 5600 _failoverMode = false; 5601 if (_onConnectHandler) { 5602 _onConnectHandler(); 5603 } 5604 } 5605 // Whenever we are recovered, we need to refresh any objects 5606 // that are stored. 5607 _refreshObjects(); 5608 break; 5609 } 5610 } 5611 }, 5612 5613 /** 5614 * Ensure that ClientServices have been inited. 5615 * @private 5616 */ 5617 _isInited = function () { 5618 if (!_inited) { 5619 throw new Error("ClientServices needs to be inited."); 5620 } 5621 }, 5622 5623 /** 5624 * Have the client become the Master by initiating a tunnel to a shared 5625 * event BOSH connection. The Master is responsible for publishing all 5626 * events to the pubsub infrastructure. 5627 * 5628 * TODO: Currently we only check the global auth mode. This code has to 5629 * handle mixed mode - in this case the user specfic SSO mode has to be 5630 * exposed via an API. 5631 * 5632 * @private 5633 */ 5634 _becomeMaster = function () { 5635 var creds , id; 5636 _tunnel = new MasterTunnel(_config.host, _config.scheme); 5637 _publisher = new MasterPublisher(_tunnel, _hub); 5638 if(_authModes.SSO === _config.systemAuthMode ) { 5639 creds = _config.authToken; 5640 } else { 5641 creds = _config.password; 5642 } 5643 _util = Utilities; 5644 id = _util.encodeNodeName(_config.id); 5645 _tunnel.init(id, creds, _config.xmppDomain, _config.pubsubDomain, _config.resource, _config.notificationConnectionType); 5646 _isMaster = true; 5647 }, 5648 5649 /** 5650 * Make a request to the request channel to have the Master publish the 5651 * connection info object. 5652 * @private 5653 */ 5654 _makeConnectionInfoReq = function () { 5655 var data = { 5656 type: "ConnectionInfoReq", 5657 data: {}, 5658 invokeID: (new Date()).getTime() 5659 }; 5660 _hub.publish(_topics.REQUESTS, data); 5661 }, 5662 5663 /** 5664 * Utility method to register a handler which is associated with a 5665 * particular connection status. 5666 * @param {String} status 5667 * The connection status string. 5668 * @param {Function} handler 5669 * The handler to associate with a particular connection status. 5670 * @throws {Error} 5671 * If the handler provided is not a function. 5672 * @private 5673 */ 5674 _registerHandler = function (status, handler) { 5675 if (typeof handler === "function") { 5676 if (_connInfo && _connInfo.status === status) { 5677 handler(); 5678 } 5679 switch (status) { 5680 case _STATUS.CONNECTING: 5681 _onConnectingHandler = handler; 5682 break; 5683 case _STATUS.CONNECTED: 5684 _onConnectHandler = handler; 5685 break; 5686 case _STATUS.DISCONNECTED: 5687 _onDisconnectHandler = handler; 5688 break; 5689 case _STATUS.DISCONNECTING: 5690 _onDisconnectingHandler = handler; 5691 break; 5692 case _STATUS.RECONNECTING: 5693 _onReconnectingHandler = handler; 5694 break; 5695 case _STATUS.UNLOADING: 5696 _onUnloadingHandler = handler; 5697 break; 5698 } 5699 5700 } else { 5701 throw new Error("Callback is not a function"); 5702 } 5703 }, 5704 5705 /** 5706 * Callback function that is called when a refresh access token event message is posted to the Hub. 5707 * 5708 * Get access token from the data and update the finesse.gadget.Config object! 5709 * 5710 * @param {String} topic 5711 * which topic the event came on (unused) 5712 * @param {Object} data 5713 * the data published with the event 5714 * @private 5715 */ 5716 _accessTokenRefreshHandler = function(topic , data){ 5717 _log("Access token refreshed - topic :" + topic + ", authToken :" + data.authToken); 5718 5719 if(data.authToken){ 5720 _config.authToken = data.authToken; 5721 if(finesse.gadget && finesse.gadget.Config){ 5722 finesse.gadget.Config.authToken = data.authToken; 5723 } 5724 } 5725 }, 5726 5727 /** 5728 * @private 5729 * Retrieves systemAuthMode from parent Finesse Container. If parent is not available, mode will be retrieved from the systemInfo rest object 5730 * @throws {Error} 5731 * If unable to retrieve systemAuthMode 5732 */ 5733 _getSystemAuthMode = function(){ 5734 var parentFinesse , sysInfo; 5735 // For gadgets hosted outside of finesse container , finesse parent object will not be available 5736 try{ 5737 parentFinesse = window.parent.finesse; 5738 } catch (e){ 5739 parentFinesse = undefined; 5740 } 5741 5742 if( parentFinesse ){ 5743 _config.systemAuthMode = parentFinesse.container.Config.systemAuthMode; 5744 } else { 5745 sysInfo = new finesse.restservices.SystemInfo({ 5746 id: _config.id, 5747 onLoad: function (systemInfo) { 5748 _config.systemAuthMode = systemInfo.getSystemAuthMode(); 5749 }, 5750 onError: function (errRsp) { 5751 throw new Error("Unable to retrieve systemAuthMode from config. Initialization failed......"); 5752 } 5753 }); 5754 5755 } 5756 } 5757 5758 return { 5759 5760 /** 5761 * @private 5762 * Adds an item to the list to be refreshed upon reconnect 5763 * @param {RestBase} object - rest object to be refreshed 5764 */ 5765 addToRefreshList: function (object) { 5766 _refreshList.push(object); 5767 }, 5768 5769 /** 5770 * Get the current state of the Bosh/Websocket connection. 5771 * Returns undefined when information not available. 5772 * @return {String} Connection status of Bosh/Websocket connection. 5773 * @example finesse.clientservices.ClientServices.getNotificationConnectionState(); 5774 */ 5775 getNotificationConnectionState: function () { 5776 return _connInfo && _connInfo.status; 5777 }, 5778 5779 /** 5780 * @private 5781 * Removes the given item from the refresh list 5782 * @param {RestBase} object - rest object to be removed 5783 */ 5784 removeFromRefreshList: function (object) { 5785 var i; 5786 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5787 if (_refreshList[i] === object) { 5788 _refreshList.splice(i, 1); 5789 break; 5790 } 5791 } 5792 }, 5793 5794 /** 5795 * @private 5796 * The location of the tunnel HTML URL. 5797 * @returns {String} 5798 * The location of the tunnel HTML URL. 5799 */ 5800 getTunnelURL: function () { 5801 return _tunnel.getTunnelURL(); 5802 }, 5803 5804 /** 5805 * @private 5806 * Indicates whether the tunnel frame is loaded. 5807 * @returns {Boolean} 5808 * True if the tunnel frame is loaded, false otherwise. 5809 */ 5810 isTunnelLoaded: function () { 5811 return _tunnel.isTunnelLoaded(); 5812 }, 5813 5814 /** 5815 * @private 5816 * Indicates whether the ClientServices instance is a Master. 5817 * @returns {Boolean} 5818 * True if this instance of ClientServices is a Master, false otherwise. 5819 */ 5820 isMaster: function () { 5821 return _isMaster; 5822 }, 5823 5824 /** 5825 * @private 5826 * Get the resource ID. An ID is only available if the BOSH connection has 5827 * been able to connect successfully. 5828 * @returns {String} 5829 * The resource ID string. Null if the BOSH connection was never 5830 * successfully created and/or the resource ID has not been associated. 5831 */ 5832 getResourceID: function () { 5833 if (_connInfo !== undefined) { 5834 return _connInfo.resourceID; 5835 } 5836 return null; 5837 }, 5838 5839 /* 5840 getHub: function () { 5841 return _hub; 5842 }, 5843 */ 5844 /** 5845 * @private 5846 * Add a callback to be invoked when the BOSH connection is attempting 5847 * to connect. If the connection is already trying to connect, the 5848 * callback will be invoked immediately. 5849 * @param {Function} handler 5850 * An empty param function to be invoked on connecting. Only one 5851 * handler can be registered at a time. Handlers already registered 5852 * will be overwritten. 5853 */ 5854 registerOnConnectingHandler: function (handler) { 5855 _registerHandler(_STATUS.CONNECTING, handler); 5856 }, 5857 5858 /** 5859 * @private 5860 * Removes the on connecting callback that was registered. 5861 */ 5862 unregisterOnConnectingHandler: function () { 5863 _onConnectingHandler = undefined; 5864 }, 5865 5866 /** 5867 * Add a callback to be invoked when all of the following conditions are met: 5868 * <ul> 5869 * <li>When Finesse goes IN_SERVICE</li> 5870 * <li>The BOSH connection is established</li> 5871 * <li>The Finesse user presence becomes available</li> 5872 * </ul> 5873 * If all these conditions are met at the time this function is called, then 5874 * the handler will be invoked immediately. 5875 * @param {Function} handler 5876 * An empty param function to be invoked on connect. Only one handler 5877 * can be registered at a time. Handlers already registered will be 5878 * overwritten. 5879 * @example 5880 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 5881 */ 5882 registerOnConnectHandler: function (handler) { 5883 _registerHandler(_STATUS.CONNECTED, handler); 5884 }, 5885 5886 /** 5887 * @private 5888 * Removes the on connect callback that was registered. 5889 */ 5890 unregisterOnConnectHandler: function () { 5891 _onConnectHandler = undefined; 5892 }, 5893 5894 /** 5895 * Add a callback to be invoked when any of the following occurs: 5896 * <ul> 5897 * <li>Finesse is no longer IN_SERVICE</li> 5898 * <li>The BOSH connection is lost</li> 5899 * <li>The presence of the Finesse user is no longer available</li> 5900 * </ul> 5901 * If any of these conditions are met at the time this function is 5902 * called, the callback will be invoked immediately. 5903 * @param {Function} handler 5904 * An empty param function to be invoked on disconnected. Only one 5905 * handler can be registered at a time. Handlers already registered 5906 * will be overwritten. 5907 * @example 5908 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 5909 */ 5910 registerOnDisconnectHandler: function (handler) { 5911 _registerHandler(_STATUS.DISCONNECTED, handler); 5912 }, 5913 5914 /** 5915 * @private 5916 * Removes the on disconnect callback that was registered. 5917 */ 5918 unregisterOnDisconnectHandler: function () { 5919 _onDisconnectHandler = undefined; 5920 }, 5921 5922 /** 5923 * @private 5924 * Add a callback to be invoked when the BOSH is currently disconnecting. If 5925 * the connection is already disconnecting, invoke the callback immediately. 5926 * @param {Function} handler 5927 * An empty param function to be invoked on disconnected. Only one 5928 * handler can be registered at a time. Handlers already registered 5929 * will be overwritten. 5930 */ 5931 registerOnDisconnectingHandler: function (handler) { 5932 _registerHandler(_STATUS.DISCONNECTING, handler); 5933 }, 5934 5935 /** 5936 * @private 5937 * Removes the on disconnecting callback that was registered. 5938 */ 5939 unregisterOnDisconnectingHandler: function () { 5940 _onDisconnectingHandler = undefined; 5941 }, 5942 5943 /** 5944 * @private 5945 * Add a callback to be invoked when the BOSH connection is attempting 5946 * to connect. If the connection is already trying to connect, the 5947 * callback will be invoked immediately. 5948 * @param {Function} handler 5949 * An empty param function to be invoked on connecting. Only one 5950 * handler can be registered at a time. Handlers already registered 5951 * will be overwritten. 5952 */ 5953 registerOnReconnectingHandler: function (handler) { 5954 _registerHandler(_STATUS.RECONNECTING, handler); 5955 }, 5956 5957 /** 5958 * @private 5959 * Removes the on reconnecting callback that was registered. 5960 */ 5961 unregisterOnReconnectingHandler: function () { 5962 _onReconnectingHandler = undefined; 5963 }, 5964 5965 /** 5966 * @private 5967 * Add a callback to be invoked when the BOSH connection is unloading 5968 * 5969 * @param {Function} handler 5970 * An empty param function to be invoked on connecting. Only one 5971 * handler can be registered at a time. Handlers already registered 5972 * will be overwritten. 5973 */ 5974 registerOnUnloadingHandler: function (handler) { 5975 _registerHandler(_STATUS.UNLOADING, handler); 5976 }, 5977 5978 /** 5979 * @private 5980 * Removes the on unloading callback that was registered. 5981 */ 5982 unregisterOnUnloadingHandler: function () { 5983 _onUnloadingHandler = undefined; 5984 }, 5985 5986 /** 5987 * @private 5988 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5989 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5990 * configuration is encapsulated in here anyways. 5991 * This removes the dependency 5992 * @param {String} url 5993 * The url(relative/absolute) to make the request to. 5994 * It is expected that any encoding to the URL is already done. 5995 * @param {Function} handler 5996 * Callback handler for makeRequest to invoke when the response returns. 5997 * Completely passed through to gadgets.io.makeRequest 5998 * @param {Object} params 5999 * The params object that gadgets.io.makeRequest expects. Authorization and locale 6000 * headers are mixed in. 6001 */ 6002 makeRequest: function (url, handler, params) { 6003 // ClientServices needs to be initialized with a config for restHost, auth, and locale 6004 _isInited(); 6005 6006 // Allow mixin of auth and locale headers 6007 params = params || {}; 6008 6009 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 6010 // using GET http method because then the params are added to the url as query params, which 6011 // exposes the authorization string in the url. This is a placeholder until oauth comes in 6012 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 6013 6014 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 6015 6016 // Add Basic auth to request header 6017 params[gadgets.io.RequestParameters.HEADERS].Authorization = _util.getAuthHeaderString(_config); 6018 6019 //Locale 6020 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 6021 6022 if( _util.isAbsoluteUrl(url) ) { 6023 _log("makeRequest to absolute url: " + url); 6024 gadgets.io.makeRequest(url, handler, params); 6025 } else { 6026 _log("makeRequest to relative url: " + url); 6027 var restHost = "http://localhost:8082"; 6028 var config = finesse.container.Config; 6029 var windowLocation = window.location.hostname; 6030 6031 /** 6032 * if makeRequest is called from cfadmin gadget in SPOG, resthost shoule be actual restRest from the Config, 6033 * if we use localhost in SPOG it will be resilved to PCCE 6034 */ 6035 if ( finesse.vars && finesse.vars.isCfAdminGadget && config.restHost && config.restHost.toLocaleLowerCase() !== windowLocation ) { 6036 _log("makeRequest to relative url. hostname(" + windowLocation + ") and restHost(" + config.restHost + ") are different"); 6037 restHost = config.scheme + "://" + config.restHost + ":" + config.hostPort; 6038 } 6039 gadgets.io.makeRequest( restHost + (url.startsWith("/") ? url : "/" + url), handler, params); 6040 } 6041 }, 6042 6043 /** 6044 * @private 6045 * Utility function to make a subscription to a particular topic. Only one 6046 * callback function is registered to a particular topic at any time. 6047 * @param {String} topic 6048 * The full topic name. The topic name should follow the OpenAjax 6049 * convention using dot notation (ex: finesse.api.User.1000). 6050 * @param {Function} callback 6051 * The function that should be invoked with the data when an event 6052 * is delivered to the specific topic. 6053 * @param {Function} contextId 6054 * A unique id which gets appended to the topic for storing subscription details, 6055 * when multiple subscriptions to the same topic is required. 6056 * @returns {Boolean} 6057 * True if the subscription was made successfully and the callback was 6058 * been registered. False if the subscription already exist, the 6059 * callback was not overwritten. 6060 */ 6061 subscribe: function (topic, callback, disableDuringFailover, contextId) { 6062 _isInited(); 6063 6064 var topicId = topic + (contextId === undefined ? "" : contextId); 6065 6066 //Ensure that the same subscription isn't made twice. 6067 if (!_subscriptionID[topicId]) { 6068 //Store the subscription ID using the topic name as the key. 6069 _subscriptionID[topicId] = _hub.subscribe(topic, 6070 //Invoke the callback just with the data object. 6071 function (topic, data) { 6072 if (!disableDuringFailover || _isMaster || !_failoverMode) { 6073 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 6074 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 6075 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 6076 // - Master instance will get all events regardless, because it is responsible for managing failover 6077 // - If we are not in a failover mode, everything goes 6078 // _refreshObjects will reconcile anything that was missed once we are back in action 6079 callback(data); 6080 } 6081 }); 6082 return true; 6083 } 6084 return false; 6085 }, 6086 6087 /** 6088 * @private 6089 * Unsubscribe from a particular topic. 6090 * @param {String} topic 6091 * The full topic name. 6092 * @param {String} contextId 6093 * A unique id which gets appended to the topic for storing subscription details, 6094 * when multiple subscriptions to the same topic is required. 6095 */ 6096 unsubscribe: function (topic, contextId) { 6097 _isInited(); 6098 topic = topic + (contextId === undefined ? "" : contextId); 6099 6100 //Unsubscribe from the topic using the subscription ID recorded when 6101 //the subscription was made, then delete the ID from data structure. 6102 if (_subscriptionID[topic]) { 6103 _hub.unsubscribe(_subscriptionID[topic]); 6104 delete _subscriptionID[topic]; 6105 } 6106 }, 6107 6108 6109 /** 6110 * @private 6111 * Make a request to the request channel to have the Master subscribe 6112 * to a node. 6113 * @param {String} node 6114 * The node to subscribe to. 6115 */ 6116 subscribeNode: function (node, handler) { 6117 if (handler && typeof handler !== "function") { 6118 throw new Error("ClientServices.subscribeNode: handler is not a function"); 6119 } 6120 6121 // Construct the request to send to MasterPublisher through the OpenAjax Hub 6122 var data = { 6123 type: "SubscribeNodeReq", 6124 data: {node: node}, 6125 invokeID: _util.generateUUID() 6126 }, 6127 responseTopic = _topics.RESPONSES + "." + data.invokeID, 6128 _this = this; 6129 6130 // We need to first subscribe to the response channel 6131 this.subscribe(responseTopic, function (rsp) { 6132 // Since this channel is only used for this singular request, 6133 // we are not interested anymore. 6134 // This is also critical to not leaking memory by having OpenAjax 6135 // store a bunch of orphaned callback handlers that enclose on 6136 // our entire ClientServices singleton 6137 _this.unsubscribe(responseTopic); 6138 if (handler) { 6139 handler(data.invokeID, rsp); 6140 } 6141 }); 6142 // Then publish the request on the request channel 6143 _hub.publish(_topics.REQUESTS, data); 6144 }, 6145 6146 /** 6147 * @private 6148 * Make a request to the request channel to have the Master unsubscribe 6149 * from a node. 6150 * @param {String} node 6151 * The node to unsubscribe from. 6152 */ 6153 unsubscribeNode: function (node, subid, handler) { 6154 if (handler && typeof handler !== "function") { 6155 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 6156 } 6157 6158 // Construct the request to send to MasterPublisher through the OpenAjax Hub 6159 var data = { 6160 type: "UnsubscribeNodeReq", 6161 data: { 6162 node: node, 6163 subid: subid 6164 }, 6165 isForceOp: (typeof handler.isForceOp != 'undefined') ? handler.isForceOp: false, 6166 invokeID: _util.generateUUID() 6167 }, 6168 responseTopic = _topics.RESPONSES + "." + data.invokeID, 6169 _this = this; 6170 6171 // We need to first subscribe to the response channel 6172 this.subscribe(responseTopic, function (rsp) { 6173 // Since this channel is only used for this singular request, 6174 // we are not interested anymore. 6175 // This is also critical to not leaking memory by having OpenAjax 6176 // store a bunch of orphaned callback handlers that enclose on 6177 // our entire ClientServices singleton 6178 _this.unsubscribe(responseTopic); 6179 if (handler) { 6180 handler(rsp); 6181 } 6182 }); 6183 // Then publish the request on the request channel 6184 _hub.publish(_topics.REQUESTS, data); 6185 }, 6186 6187 6188 /** 6189 * @private 6190 * Make a request to the request channel to get the list of all subscriptions for the logged in user. 6191 */ 6192 getNodeSubscriptions: function (handler) { 6193 if (handler && typeof handler !== "function") { 6194 throw new Error("ClientServices.getNodeSubscriptions: handler is not a function"); 6195 } 6196 6197 // Construct the request to send to MasterPublisher through the OpenAjax Hub 6198 var data = { 6199 type: "SubscriptionsInfoReq", 6200 data: { 6201 }, 6202 invokeID: _util.generateUUID() 6203 }, 6204 responseTopic = _topics.RESPONSES + "." + data.invokeID, 6205 _this = this; 6206 6207 // We need to first subscribe to the response channel 6208 this.subscribe(responseTopic, function (rsp) { 6209 // Since this channel is only used for this singular request, 6210 // we are not interested anymore. 6211 // This is also critical to not leaking memory by having OpenAjax 6212 // store a bunch of orphaned callback handlers that enclose on 6213 // our entire ClientServices singleton 6214 _this.unsubscribe(responseTopic); 6215 if (handler) { 6216 handler(JSON.parse(rsp)); 6217 } 6218 }); 6219 // Then publish the request on the request channel 6220 _hub.publish(_topics.REQUESTS, data); 6221 }, 6222 6223 /** 6224 * @private 6225 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 6226 */ 6227 makeConnectionReq : function () { 6228 // Disallow others (non-masters) from administering BOSH connections that are not theirs 6229 if (_isMaster && _publisher) { 6230 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 6231 } else { 6232 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 6233 } 6234 }, 6235 6236 /** 6237 * @private 6238 * Set's the global logger for this Client Services instance. 6239 * @param {Object} logger 6240 * Logger object with the following attributes defined:<ul> 6241 * <li><b>log:</b> function (msg) to simply log a message 6242 * </ul> 6243 */ 6244 setLogger: function (logger) { 6245 // We want to check the logger coming in so we don't have to check every time it is called. 6246 if (logger && typeof logger === "object" && typeof logger.log === "function") { 6247 _logger = logger; 6248 } else { 6249 // We are resetting it to an empty object so that _logger.log in .log is falsy. 6250 _logger = {}; 6251 } 6252 }, 6253 6254 /** 6255 * @private 6256 * Centralized logger.log method for external logger 6257 * @param {String} msg 6258 * Message to log 6259 */ 6260 log: _log, 6261 6262 /** 6263 * @class 6264 * Allow clients to make Finesse API requests and consume Finesse events by 6265 * calling a set of exposed functions. The Services layer will do the dirty 6266 * work of establishing a shared BOSH connection (for designated Master 6267 * modules), consuming events for client subscriptions, and constructing API 6268 * requests. 6269 * 6270 * @constructs 6271 */ 6272 _fakeConstuctor: function () { 6273 /* This is here so we can document init() as a method rather than as a constructor. */ 6274 }, 6275 6276 /** 6277 * Initiates the Client Services with the specified config parameters. 6278 * Enabling the Client Services as Master will trigger the establishment 6279 * of a BOSH event connection. 6280 * @param {finesse.gadget.Config} config 6281 * Configuration object containing properties used for making REST requests:<ul> 6282 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 6283 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 6284 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 6285 * appropriate authorization string is provided, otherwise it is 6286 * required.</li> 6287 * <li><b>password:</b> The password belonging to the user. This is an optional param as 6288 * long as the appropriate authorization string is provided, 6289 * otherwise it is required.</li> 6290 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 6291 * param is provided to allow the ability to hide the password 6292 * param. If provided, the id and the password extracted from this 6293 * string will be used over the config.id and config.password.</li> 6294 * </ul> 6295 * @throws {Error} If required constructor parameter is missing. 6296 * @example 6297 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 6298 */ 6299 init: function (config) { 6300 if (!_inited) { 6301 //Validate the properties within the config object if one is provided. 6302 if (!(typeof config === "object" && 6303 typeof config.host === "string" && config.host.length > 0 && config.restHost && 6304 (typeof config.authorization === "string" || 6305 (typeof config.id === "string")))) { 6306 throw new Error("Config object contains invalid properties."); 6307 } 6308 6309 // Initialize configuration 6310 _config = config; 6311 6312 // Set shortcuts 6313 _util = Utilities; 6314 _authModes = _util.getAuthModes(); 6315 _topics = Topics; 6316 6317 //TODO: document when this is properly supported 6318 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 6319 _hub = config.hub || gadgets.Hub; 6320 _io = config.io || gadgets.io; 6321 6322 //If the authorization string is provided, then use that to 6323 //extract the ID and the password. Otherwise use the ID and 6324 //password from the respective ID and password params. 6325 if (_config.authorization) { 6326 var creds = _util.getCredentials(_config.authorization); 6327 _config.id = creds.id; 6328 _config.password = creds.password; 6329 } 6330 else { 6331 _config.authorization = _util.b64Encode( 6332 _config.id + ":" + _config.password); 6333 } 6334 6335 //In case if gadgets create their own config instance , add systemAuthMode property inside config object 6336 if(!_config.systemAuthMode || _config.systemAuthMode === ""){ 6337 _getSystemAuthMode(); 6338 } 6339 6340 if(_config.systemAuthMode === _authModes.SSO){ 6341 _accessTokenRefreshHandler(undefined , {authToken : _util.getToken()}); 6342 if(!_config.authToken){ 6343 throw new Error("ClientServices.init() - Access token is unavailable inside Config object."); 6344 } 6345 6346 if (_hub){ 6347 _hub.subscribe(_topics.ACCESS_TOKEN_REFRESHED_EVENT, _accessTokenRefreshHandler); 6348 } 6349 } 6350 6351 _inited = true; 6352 6353 if (_hub) { 6354 //Subscribe to receive connection information. Since it is possible that 6355 //the client comes up after the Master comes up, the client will need 6356 //to make a request to have the Master send the latest connection info. 6357 //It would be possible that all clients get connection info again. 6358 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 6359 _makeConnectionInfoReq(); 6360 } 6361 } 6362 6363 //Return the CS object for object chaining. 6364 return this; 6365 }, 6366 6367 /** 6368 * @private 6369 * Initializes the BOSH component of this ClientServices instance. This establishes 6370 * the BOSH connection and will trigger the registered handlers as the connection 6371 * status changes respectively:<ul> 6372 * <li>registerOnConnectingHandler</li> 6373 * <li>registerOnConnectHandler</li> 6374 * <li>registerOnDisconnectHandler</li> 6375 * <li>registerOnDisconnectingHandler</li> 6376 * <li>registerOnReconnectingHandler</li> 6377 * <li>registerOnUnloadingHandler</li> 6378 * <ul> 6379 * 6380 * @param {Object} config 6381 * An object containing the following (optional) handlers for the request:<ul> 6382 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 6383 * This is used to construct the JID: user@domain.com</li> 6384 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 6385 * Available from the SystemInfo object. 6386 * This is used for creating or removing subscriptions.</li> 6387 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 6388 * </ul> 6389 */ 6390 initBosh: function (config) { 6391 //Validate the properties within the config object if one is provided. 6392 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 6393 throw new Error("Config object contains invalid properties."); 6394 } 6395 6396 // Mixin the required information for establishing the BOSH connection 6397 _config.xmppDomain = config.xmppDomain; 6398 _config.pubsubDomain = config.pubsubDomain; 6399 _config.resource = config.resource; 6400 6401 //Initiate Master launch sequence 6402 _becomeMaster(); 6403 }, 6404 6405 /** 6406 * @private 6407 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 6408 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 6409 * @param {Object} failoverMode 6410 * true if failing, false or something falsy when recovered 6411 */ 6412 setFailoverMode: function (failoverMode) { 6413 if (_isMaster) { 6414 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 6415 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 6416 }); 6417 } 6418 }, 6419 6420 /** 6421 * Method to get the destination host where the rest calls will be made proxied through shindig. 6422 * @return {String} 6423 * @example finesse.clientservices.ClientServices.getRestHost(); 6424 */ 6425 getRestHost: function () { 6426 return (_config && _config.restHost) || 'localhost'; 6427 }, 6428 6429 /** 6430 * @private 6431 * Private accessor used to inject mocked private dependencies for unit testing 6432 */ 6433 _getTestObj: function () { 6434 return { 6435 setPublisher: function (publisher) { 6436 _publisher = publisher; 6437 } 6438 }; 6439 } 6440 }; 6441 }()); 6442 6443 window.finesse = window.finesse || {}; 6444 window.finesse.clientservices = window.finesse.clientservices || {}; 6445 window.finesse.clientservices.ClientServices = ClientServices; 6446 6447 return ClientServices; 6448 6449 }); 6450 /** 6451 * The following comment prevents JSLint errors concerning undefined global variables. 6452 * It tells JSLint that these identifiers are defined elsewhere. 6453 */ 6454 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 6455 6456 /** The following comment is to prevent jslint errors about 6457 * using variables before they are defined. 6458 */ 6459 /*global Handlebars */ 6460 6461 /** 6462 * JavaScript class to implement common notification 6463 * functionality. 6464 * 6465 * @requires Class 6466 * @requires finesse.FinesseBase 6467 */ 6468 /** @private */ 6469 define('restservices/Notifier',[ 6470 'FinesseBase', 6471 'clientservices/ClientServices' 6472 ], 6473 function (FinesseBase, ClientServices) { 6474 var Notifier = FinesseBase.extend({ 6475 /** 6476 * Initializes the notifier object. 6477 */ 6478 init : function () { 6479 this._super(); 6480 this._listenerCallback = []; 6481 }, 6482 6483 /** 6484 * Add a listener. 6485 * 6486 * @param callback_function 6487 * @param scope 6488 * is the callback function to add 6489 */ 6490 addListener : function (callback_function, scope) { 6491 var len = this._listenerCallback.length, i, cb, add = true; 6492 for (i = 0; i < len; i += 1) { 6493 cb = this._listenerCallback[i].callback; 6494 if (cb === callback_function) { 6495 // this callback already exists 6496 add = false; 6497 break; 6498 } 6499 } 6500 if (add) { 6501 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 6502 } 6503 }, 6504 6505 /** 6506 * Remove a listener. 6507 * 6508 * @param callback_function 6509 * is the callback function to remove 6510 * @return {Boolean} true if removed 6511 */ 6512 removeListener : function (callback_function) { 6513 6514 var result = false, len = this._listenerCallback.length, i, cb; 6515 for (i = len - 1; i >= 0; i -=1) { 6516 cb = this._listenerCallback[i].callback; 6517 if (cb === callback_function) { 6518 this._listenerCallback[i] = undefined; 6519 this._listenerCallback.splice(i, 1); 6520 result = true; 6521 break; 6522 } 6523 } 6524 6525 return result; 6526 }, 6527 6528 /** 6529 * Removes all listeners 6530 * @return {undefined} 6531 */ 6532 reset: function () { 6533 this._listenerCallback = []; 6534 }, 6535 6536 /** 6537 * Notify all listeners. 6538 * 6539 * @param obj 6540 * is the object that has changed 6541 */ 6542 notifyListeners : function (obj) { 6543 var len = this._listenerCallback.length, i, callbackFunction, scope; 6544 6545 for (i = 0; i < len; i += 1) { 6546 // Be sure that one bad callback does not prevent other listeners 6547 // from receiving. 6548 try { 6549 callbackFunction = this._listenerCallback[i].callback; 6550 scope = this._listenerCallback[i].scope; 6551 if (typeof callbackFunction === 'function') { 6552 callbackFunction.call(scope, obj); 6553 } 6554 } catch (err) { 6555 ClientServices.log("Notifier.js#notifyListeners: Exception caught: ", err); 6556 } 6557 } 6558 }, 6559 6560 /** 6561 * Gets a copy of the listeners. 6562 * @return changeListenerCopy (array of callbacks) 6563 */ 6564 getListeners : function () { 6565 var changeListenerCopy = [], len = this._listenerCallback.length, i; 6566 6567 for (i = 0; i < len; i += 1) { 6568 changeListenerCopy.push(this._listenerCallback[i].callback); 6569 } 6570 6571 return changeListenerCopy; 6572 }, 6573 6574 /** 6575 * Verifies that the handler is function. 6576 * @param handler to verify 6577 * @return the handler 6578 * @throws Error if not a function 6579 */ 6580 _isAFunction : function (handler) { 6581 if (handler === undefined || typeof handler === "function") { 6582 return handler; 6583 } else { 6584 throw new Error("handler must be a function"); 6585 } 6586 } 6587 }); 6588 6589 window.finesse = window.finesse || {}; 6590 window.finesse.restservices = window.finesse.restservices || {}; 6591 window.finesse.restservices.Notifier = Notifier; 6592 6593 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 6594 finesse.restservices = finesse.restservices || {}; 6595 6596 return Notifier; 6597 }); 6598 6599 /** 6600 * Service to handle caching for rest object in Session storage 6601 */ 6602 define('utilities/SessionStorageCachingService',[], function () { 6603 var SessionStorageCachingService = (function () { 6604 var logger = {}; 6605 return { 6606 /** 6607 * list of objects to be cached in session storage 6608 */ 6609 SESSION_STORAGE_CACHED_LIST: ['User'], 6610 /** 6611 * add/set item in the session storage 6612 */ 6613 setItem: function(key, value) { 6614 sessionStorage.setItem(key,value); 6615 }, 6616 6617 /** 6618 * get value from session storage 6619 */ 6620 getItem: function(key) { 6621 return sessionStorage.getItem(key); 6622 }, 6623 6624 /** 6625 * Remove item from cache (sesion storage) 6626 */ 6627 removeItem: function(key) { 6628 sessionStorage.removeItem(key); 6629 }, 6630 6631 /** 6632 * Check if rest need to be cached 6633 */ 6634 isCacheable: function(restType) { 6635 return this.SESSION_STORAGE_CACHED_LIST.indexOf(restType) > -1; 6636 }, 6637 6638 /** 6639 * clear the cache 6640 */ 6641 clearCache: function() { 6642 var self = this; 6643 this.SESSION_STORAGE_CACHED_LIST.forEach(function(value) { 6644 self.removeItem(value); 6645 }); 6646 } 6647 } 6648 6649 }()); 6650 6651 window.finesse = window.finesse || {}; 6652 window.finesse.SessionStorageCachingService = SessionStorageCachingService; 6653 return SessionStorageCachingService; 6654 }); 6655 6656 /** 6657 * Service to handle caching for rest objects. 6658 */ 6659 define('utilities/RestCachingService',[], function () { 6660 var RestCachingService = (function () { 6661 var logger = {}; 6662 return { 6663 /** 6664 * These list of object should be served from cache only in case of failover 6665 */ 6666 CACHED_REST_LIST_FOR_FAILOVER: [ 6667 'WrapUpReasons', 6668 'Media', 6669 'Workflows', 6670 'Team', 6671 'PhoneBooks', 6672 'MediaPropertiesLayouts', 6673 'ECCVariableConfig', 6674 'ReasonCodes', 6675 'WrapUpReasons' 6676 ], 6677 /** 6678 * List of rest names, where object should be created from cache if these rest objects are already in the cache 6679 */ 6680 REST_TO_BE_SERVRED_FROM_CACHE: ['WrapUpReasons', 'ReasonCodes'], 6681 6682 getRestNameForRequestFromCache: function (restName, url) { 6683 var cachedList = this.getFailoverCacheableRestList(), restFound = "";; 6684 if(cachedList){ 6685 if(cachedList.indexOf(restName) > -1){ 6686 return restName; 6687 } 6688 } 6689 6690 }, 6691 6692 getFailoverCacheableRestList: function(){ 6693 return this.CACHED_REST_LIST_FOR_FAILOVER; 6694 }, 6695 6696 getCacheableRestList: function(){ 6697 return this.REST_TO_BE_SERVRED_FROM_CACHE; 6698 }, 6699 6700 /** 6701 * Check if Rest Cache Data service is reachable. 6702 * Reachable means RestCacheDataContainer is initialized and IndexDB is supported by the browser. 6703 */ 6704 isRestCacheServiceReachable: function() { 6705 return finesse.idb && finesse.idb.RestCacheDataContainer && finesse.idb.RestCacheDataContainer.isReachable(); 6706 }, 6707 6708 isCacheableForFailover: function(restType) { 6709 // Return false for supervisor as .getUsers on Team object have dynamic data in case of failover and is used in TPG 6710 // Returns false incase of cfadmin for team. 6711 if(restType === 'Team' && (!top.window.finesse.container.PageServices || 6712 top.window.finesse.container.PageServices.getUser().hasSupervisorRole())) { 6713 return false; 6714 } 6715 return this.CACHED_REST_LIST_FOR_FAILOVER.indexOf(restType) > -1 6716 }, 6717 6718 isCacheable: function(restType) { 6719 return this.REST_TO_BE_SERVRED_FROM_CACHE.indexOf(restType) > -1 6720 } 6721 } 6722 6723 }()); 6724 6725 window.finesse = window.finesse || {}; 6726 window.finesse.RestCachingService = RestCachingService; 6727 return RestCachingService; 6728 }); 6729 6730 /** 6731 * JavaScript base object that all REST objects should inherit 6732 * from because it encapsulates and provides the common functionality that 6733 * all REST objects need. 6734 * 6735 * @requires finesse.clientservices.ClientServices 6736 * @requires Class 6737 */ 6738 6739 /** @private */ 6740 define('restservices/RestBase',[ 6741 "FinesseBase", 6742 "utilities/Utilities", 6743 "restservices/Notifier", 6744 "clientservices/ClientServices", 6745 "clientservices/Topics", 6746 "utilities/SessionStorageCachingService", 6747 "utilities/RestCachingService" 6748 ], 6749 function (FinesseBase, Utilities, Notifier, ClientServices, Topics, SessionStorageCachingService, RestCachingService) { 6750 6751 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 6752 6753 doNotLog: false, 6754 6755 /** 6756 * Used by _processUpdate() and restRequest(). 6757 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 6758 * so that one of the callbacks can be fired when a corresponding event is 6759 * received inside _processUpdate(). 6760 * @private 6761 */ 6762 _pendingCallbacks: {}, 6763 6764 /** 6765 * Gets the REST class for the current object. This object throws an 6766 * exception because subtype must implement. 6767 * @throws {Error} because subtype must implement 6768 * @private 6769 */ 6770 getRestClass: function () { 6771 throw new Error("getRestClass(): Not implemented in subtype."); 6772 }, 6773 6774 /** 6775 * Gets the REST type for the current object. This object throws an 6776 * exception because subtype must implement. 6777 * @throws {Error} because subtype must implement. 6778 * @private 6779 */ 6780 getRestType: function () { 6781 throw new Error("getRestType(): Not implemented in subtype."); 6782 }, 6783 6784 /** 6785 * Gets the node path for the current object. 6786 * @private 6787 * @returns {String} The node path 6788 */ 6789 getXMPPNodePath: function () { 6790 return this.getRestUrl(); 6791 }, 6792 6793 /** 6794 * Boolean function that specifies whether the REST object supports 6795 * requests. True by default. Subclasses should override if false. 6796 * @private 6797 */ 6798 supportsRequests: true, 6799 6800 /** 6801 * Boolean function that specifies whether the REST object supports 6802 * subscriptions. True by default. Subclasses should override if false. 6803 * @private 6804 */ 6805 supportsSubscriptions: true, 6806 6807 /** 6808 * Boolean function that specifies whether the REST object should retain 6809 * a copy of the REST response. False by default. Subclasses should override if true. 6810 * @private 6811 */ 6812 keepRestResponse: false, 6813 6814 /** 6815 * Number that represents the REST Response status that is returned. Only 6816 * set if keepRestResponse is set to true. 6817 * @public 6818 */ 6819 restResponseStatus: null, 6820 6821 /** 6822 * Object to store additional headers to be sent with the REST Request, null by default. 6823 * @private 6824 */ 6825 extraHeaders: null, 6826 6827 /** 6828 * Boolean function that specifies whether the REST object explicitly 6829 * subscribes. False by default. Subclasses should override if true. 6830 * @private 6831 */ 6832 explicitSubscription: false, 6833 6834 /** 6835 * Boolean function that specifies whether subscribing should be 6836 * automatically done at construction. Defaults to true. 6837 * This be overridden at object construction, not by implementing subclasses 6838 * @private 6839 */ 6840 autoSubscribe: true, 6841 6842 /** 6843 * A unique id which gets appended to the topic for storing subscription details, 6844 * when multiple subscriptions to the same topic is required. 6845 * @private 6846 */ 6847 contextId: '', 6848 6849 /** 6850 Default ajax request timout value 6851 */ 6852 ajaxRequestTimeout : 5000, 6853 6854 /** 6855 * Private reference to default logger 6856 * @private 6857 */ 6858 _logger: { 6859 log: ClientServices.log, 6860 error: ClientServices.log 6861 }, 6862 6863 /** 6864 * @class 6865 * JavaScript representation of a REST object. Also exposes methods to operate 6866 * on the object against the server. This object is typically extended into individual 6867 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 6868 * 6869 * @constructor 6870 * @param {String} id 6871 * The ID that uniquely identifies the REST object. 6872 * @param {Object} callbacks 6873 * An object containing callbacks for instantiation and runtime 6874 * Callback to invoke upon successful instantiation, passes in REST object. 6875 * @param {Function} callbacks.onLoad(this) 6876 * Callback to invoke upon loading the data for the first time. 6877 * @param {Function} callbacks.onChange(this) 6878 * Callback to invoke upon successful update object (PUT) 6879 * @param {Function} callbacks.onAdd(this) 6880 * Callback to invoke upon successful update to add object (POST) 6881 * @param {Function} callbacks.onDelete(this) 6882 * Callback to invoke upon successful update to delete object (DELETE) 6883 * @param {Function} callbacks.onError(rsp) 6884 * Callback to invoke on update error (refresh or event) 6885 * as passed by finesse.restservices.RestBase.restRequest() <br> 6886 * { <br> 6887 * status: {Number} The HTTP status code returned <br> 6888 * content: {String} Raw string of response <br> 6889 * object: {Object} Parsed object of response <br> 6890 * error: {Object} Wrapped exception that was caught <br> 6891 * error.errorType: {String} Type of error that was caught <br> 6892 * error.errorMessage: {String} Message associated with error <br> 6893 * } <br> 6894 * @param {RestBase} [restObj] 6895 * A RestBase parent object which this object has an association with. 6896 * @constructs 6897 */ 6898 init: function (options, callbacks, restObj) { 6899 /** 6900 * Initialize the base class 6901 */ 6902 var _this = this; 6903 6904 this._super(); 6905 6906 if (typeof options === "object") { 6907 this._id = options.id; 6908 this._restObj = options.parentObj; 6909 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 6910 this.contextId = options.contextId ? options.contextId : this.contextId; 6911 // Pass timeout value in options object if we want to override default ajax request timeout of 5 seconds while fetching the resource 6912 this.ajaxRequestTimeout = options.timeout || this.ajaxRequestTimeout; 6913 this.doNotSubscribe = options.doNotSubscribe; 6914 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 6915 if (typeof options.supportsRequests != "undefined") { 6916 this.supportsRequests = options.supportsRequests; 6917 } 6918 callbacks = { 6919 onLoad: options.onLoad, 6920 onChange: options.onChange, 6921 onAdd: options.onAdd, 6922 onDelete: options.onDelete, 6923 onError: options.onError 6924 }; 6925 } else { 6926 this._id = options; 6927 this._restObj = restObj; 6928 } 6929 6930 // Common stuff 6931 6932 this._data = {}; 6933 6934 //Contains the full rest response to be processed by upper layers if needed 6935 this._restResponse = undefined; 6936 6937 this._lastUpdate = {}; 6938 6939 this._util = Utilities; 6940 6941 this._rcs = RestCachingService; 6942 6943 //Should be correctly initialized in either a window OR gadget context 6944 this._config = finesse.container.Config; 6945 6946 // Setup all the notifiers - change, load and error. 6947 this._changeNotifier = new Notifier(); 6948 this._loadNotifier = new Notifier(); 6949 this._addNotifier = new Notifier(); 6950 this._deleteNotifier = new Notifier(); 6951 this._errorNotifier = new Notifier(); 6952 6953 this._loaded = false; 6954 6955 // Protect against null dereferencing of options allowing its 6956 // (nonexistent) keys to be read as undefined 6957 callbacks = callbacks || {}; 6958 6959 this.addHandler('load', callbacks.onLoad); 6960 this.addHandler('change', callbacks.onChange); 6961 this.addHandler('add', callbacks.onAdd); 6962 this.addHandler('delete', callbacks.onDelete); 6963 this.addHandler('error', callbacks.onError); 6964 6965 // Attempt to get the RestType then synchronize 6966 try { 6967 this.getRestType(); 6968 6969 // Only subscribe if this REST object supports subscriptions 6970 // and autoSubscribe was not requested to be disabled as a construction option 6971 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 6972 this.subscribe({ 6973 success: function () { 6974 //TODO: figure out how to use Function.call() or Function.apply() here... 6975 //this is exactly the same as the below else case other than the scope of "this" 6976 if (typeof options === "object" && options.data) { 6977 if (!_this._processObject(_this._normalize(options.data))) { 6978 // notify of error if we fail to construct 6979 _this._errorNotifier.notifyListeners(_this); 6980 } 6981 } else { 6982 // Only subscribe if this REST object supports requests 6983 if (_this.supportsRequests) { 6984 _this._synchronize(); 6985 } 6986 } 6987 }, 6988 error: function (err) { 6989 _this._errorNotifier.notifyListeners(err); 6990 } 6991 }); 6992 } else { 6993 if (typeof options === "object" && options.data) { 6994 if (!this._processObject(this._normalize(options.data))) { 6995 // notify of error if we fail to construct 6996 this._errorNotifier.notifyListeners(this); 6997 } 6998 } else { 6999 // Only subscribe if this REST object supports requests 7000 if (this.supportsRequests) { 7001 this._synchronize(); 7002 } 7003 } 7004 } 7005 7006 } catch (err) { 7007 this._logger.error('id=' + this._id + ': ' + err); 7008 } 7009 }, 7010 7011 /** 7012 * Determines if the object has a particular property. 7013 * @param obj is the object to examine 7014 * @param property is the property to check for 7015 * @returns {Boolean} 7016 */ 7017 hasProperty: function (obj, prop) { 7018 return (obj !== null) && (obj.hasOwnProperty(prop)); 7019 }, 7020 7021 /** 7022 * Gets a property from the object. 7023 * @param obj is the object to examine 7024 * @param property is the property to get 7025 * @returns {Property Value} or {Null} if not found 7026 */ 7027 getProperty: function (obj, property) { 7028 var result = null; 7029 7030 if (this.hasProperty(obj, property) === false) { 7031 result = null; 7032 } else { 7033 result = obj[property]; 7034 } 7035 return result; 7036 }, 7037 7038 /** 7039 * Utility to extracts the ID from the specified REST URI. This is with the 7040 * assumption that the ID is always the last element in the URI after the 7041 * "/" delimiter. 7042 * @param {String} restUri 7043 * The REST uri (i.e. /finesse/api/User/1000). 7044 * @private 7045 * @returns {String} Id 7046 */ 7047 _extractId: function (restObj) { 7048 var obj, restUri = "", strLoc; 7049 for (obj in restObj) { 7050 if (restObj.hasOwnProperty(obj)) { 7051 restUri = restObj[obj].uri; 7052 break; 7053 } 7054 } 7055 return Utilities.getId(restUri); 7056 }, 7057 7058 /** 7059 * Gets the data for this object. 7060 * @returns {Object} which is contained in data 7061 */ 7062 getData: function () { 7063 return this._data; 7064 }, 7065 7066 /** 7067 * Gets the complete REST response to the request made 7068 * @returns {Object} which is contained in data 7069 * @private 7070 */ 7071 getRestResponse: function () { 7072 return this._restResponse; 7073 }, 7074 7075 getBaseRestUrl: function() { 7076 return "/finesse/api"; 7077 }, 7078 /** 7079 * Gets the REST response status to the request made 7080 * @returns {Integer} response status 7081 * @private 7082 */ 7083 getRestResponseStatus: function () { 7084 return this.restResponseStatus; 7085 }, 7086 7087 /** 7088 * The REST URL in which this object can be referenced. 7089 * @return {String} 7090 * The REST URI for this object. 7091 * @private 7092 */ 7093 getRestUrl: function () { 7094 var 7095 restObj = this._restObj, 7096 restUrl = ""; 7097 7098 //Prepend the base REST object if one was provided. 7099 if (restObj instanceof RestBase) { 7100 restUrl += restObj.getRestUrl(); 7101 } 7102 //Otherwise prepend with the default webapp name. 7103 else { 7104 restUrl += this.getBaseRestUrl(); 7105 } 7106 7107 //Append the REST type. 7108 restUrl += "/" + this.getRestType(); 7109 7110 //Append ID if it is not undefined, null, or empty. 7111 if (this._id) { 7112 restUrl += "/" + this._id; 7113 } 7114 return restUrl; 7115 }, 7116 7117 /** 7118 * Append additional query parameter in the rest url 7119 */ 7120 getRestUrlAdditionalParameters: function () { 7121 return ""; 7122 }, 7123 7124 /** 7125 * Getter for the id of this RestBase 7126 * @returns {String} 7127 * The id of this RestBase 7128 */ 7129 getId: function () { 7130 return this._id; 7131 }, 7132 7133 /** 7134 * Synchronize this object with the server using REST GET request. 7135 * @returns {Object} <br> 7136 * { <br> 7137 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request <br> 7138 * } <br> 7139 * @private 7140 */ 7141 _synchronize: function (retries) { 7142 // Fetch this REST object 7143 if (typeof this._id === "string") { 7144 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000, cachedRestResponse, clientServices = ClientServices; 7145 7146 7147 var url = this.getRestUrl(); 7148 var restName = this.getRestType(); 7149 var cacheKey = Topics.getTopic(url); 7150 7151 // if there is failover scenario the verify which all rest object should be servred from cache. 7152 var isCacheable = this._config.failoverReload === 'true' ? _this._rcs.isCacheableForFailover.bind(_this._rcs) : _this._rcs.isCacheable.bind(_this._rcs); 7153 7154 // check if index db is accessiable and the rest object should be cerarted from cache. 7155 if(_this._rcs.isRestCacheServiceReachable() && isCacheable(restName)) { 7156 var options = { 7157 key: cacheKey, 7158 onsuccess: function (result) { 7159 // get rest object from cache, otherwise fetch from server 7160 if(result && result.data && result.data.text) { 7161 _this._logger.log("Rest Caching: Rest data successfully fetched from cache : "+ _this.getRestType()); 7162 var cachedRestResponse = result.data; 7163 cachedRestResponse.object = _this._util.xml2js(cachedRestResponse.text); 7164 cachedRestResponse.status = cachedRestResponse.rc; 7165 _this._processSuccessObject(_this, cachedRestResponse, isLoaded, retries); 7166 } else { 7167 _this._logger.log("Rest Caching: Rest data not available in the cache, making rest call : " + _this.getRestType()); 7168 return _this._makeRestRequest(_this, isLoaded,retries); 7169 } 7170 }, 7171 onerror: function (err) { 7172 _this._logger.log("Rest Caching: Error while fetching rest object from cache. Making rest call",url, err); 7173 return _this._makeRestRequest(_this, isLoaded,retries); 7174 } 7175 } 7176 _this._logger.log("Rest Caching: Check if rest data is available in the cache for : "+ cacheKey); 7177 finesse.idb.RestCacheDataContainer.fetchData(options); 7178 return; 7179 } 7180 7181 if(SessionStorageCachingService.isCacheable(this.getRestType()) && SessionStorageCachingService.getItem(this.getRestType())) { 7182 var cachedRestResponse = {}; 7183 cachedRestResponse.object = _this._util.xml2js(SessionStorageCachingService.getItem(this.getRestType())); 7184 _this._logger.log("Rest Caching: Object is created from cached data for : "+ cacheKey); 7185 //CSCvs00013 fixed. making asynchronous to object be created 7186 setTimeout(function(){ 7187 _this._processSuccessObject(_this, cachedRestResponse, isLoaded, retries); 7188 }, 0); 7189 return; 7190 } 7191 7192 this._makeRestRequest(_this, isLoaded,retries); 7193 7194 } else { 7195 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 7196 } 7197 }, 7198 7199 /** 7200 * Make rest calls 7201 */ 7202 _makeRestRequest: function(_this, isLoaded,retries) { 7203 return this._doGET( 7204 { 7205 success: function (rsp) { 7206 _this._processSuccessObject(_this, rsp, isLoaded, retries); 7207 }, 7208 error: function (rsp) { 7209 if (retries > 0) { 7210 setTimeout(function () { 7211 _this._synchronize(retries - 1); 7212 }, _RETRY_INTERVAL); 7213 7214 } else { 7215 _this._errorNotifier.notifyListeners(rsp); 7216 } 7217 } 7218 } 7219 ); 7220 }, 7221 7222 /** 7223 * Adds an handler to this object. 7224 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 7225 * @param {String} notifierType 7226 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 7227 * @param {Function} callback 7228 * The function callback to invoke. 7229 * @example 7230 * // Handler for additions to the Dialogs collection object. 7231 * // When Dialog (a RestBase object) is created, add a change handler. 7232 * _handleDialogAdd = function(dialog) { 7233 * dialog.addHandler('change', _handleDialogChange); 7234 * } 7235 */ 7236 addHandler: function (notifierType, callback, scope) { 7237 var notifier = null; 7238 try { 7239 Utilities.validateHandler(callback); 7240 7241 notifier = this._getNotifierReference(notifierType); 7242 7243 notifier.addListener(callback, scope); 7244 7245 // If load handler is added and object has 7246 // already been loaded, invoke callback 7247 // immediately 7248 if (notifierType === 'load' && this._loaded) { 7249 callback.call((scope || window), this); 7250 } 7251 } catch (err) { 7252 this._logger.error('id=' + this._id + ': ' + err); 7253 } 7254 }, 7255 7256 /** 7257 * Removes a handler from this object. 7258 * @param {String} notifierType 7259 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 7260 * @param {Function} callback 7261 * The function to remove. 7262 */ 7263 removeHandler: function (notifierType, callback) { 7264 var notifier = null; 7265 try { 7266 Utilities.validateHandler(callback); 7267 7268 notifier = this._getNotifierReference(notifierType); 7269 7270 if (typeof(callback) === "undefined") 7271 { 7272 // Remove all listeners for the type 7273 notifier.reset(); 7274 } 7275 else 7276 { 7277 // Remove the specified listener 7278 finesse.utilities.Utilities.validateHandler(callback); 7279 notifier.removeListener(callback); 7280 } 7281 } catch (err) { 7282 this._logger.error('id=' + this._id + ': ' + err); 7283 } 7284 }, 7285 7286 /** 7287 * Utility method gating any operations that require complete instantiation 7288 * @throws Error 7289 * If this object was not fully instantiated yet 7290 * @returns {finesse.restservices.RestBase} 7291 * This RestBase object to allow cascading 7292 */ 7293 isLoaded: function () { 7294 if (!this._loaded) { 7295 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 7296 } 7297 return this; // Allow cascading 7298 }, 7299 7300 7301 7302 /** 7303 * Force an update on this object. Since an asynchronous GET is performed, 7304 * it is necessary to have an onChange handler registered in order to be 7305 * notified when the response of this returns. 7306 * @param {Integer} retries 7307 * The number or retry attempts to make. 7308 * @returns {Object} <br> 7309 * { <br> 7310 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request <br> 7311 * } <br> 7312 */ 7313 refresh: function (retries) { 7314 var _this = this; 7315 7316 if (this.explicitSubscription) { 7317 this._subscribeNode({ 7318 success: function () { 7319 //Disallow GETs if object doesn't support it. 7320 if (!_this.supportsRequests) { 7321 throw new Error("Object doesn't support request operations."); 7322 } 7323 7324 _this._synchronize(retries); 7325 7326 return this; // Allow cascading 7327 }, 7328 error: function (err) { 7329 _this._errorNotifier.notifyListeners(err); 7330 } 7331 }); 7332 } else { 7333 //Disallow GETs if object doesn't support it. 7334 if (!this.supportsRequests) { 7335 throw new Error("Object doesn't support request operations."); 7336 } 7337 7338 return this._synchronize(retries); 7339 } 7340 }, 7341 7342 /** 7343 * Utility method to validate against the known schema of this RestBase 7344 * @param {Object} obj 7345 * The object to validate 7346 * @returns {Boolean} 7347 * True if the object fits the schema of this object. This usually 7348 * means all required keys or nested objects are present. 7349 * False otherwise. 7350 * @private 7351 */ 7352 _validate: function (obj) { 7353 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 7354 if (!valid) 7355 { 7356 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 7357 } 7358 return valid; 7359 }, 7360 7361 /** 7362 * Utility method to check if GET request to be bypassed by Webproxy Server 7363 * @returns {Boolean} 7364 * 7365 * @private 7366 */ 7367 _shouldAllowBypassServerCache: function () { 7368 return window.finesse.restservices.bypassServerCache; 7369 }, 7370 7371 /** 7372 * Utility method to fetch this RestBase from the server 7373 * @param {finesse.interfaces.RequestHandlers} handlers 7374 * An object containing the handlers for the request 7375 * @returns {Object} <br> 7376 * { <br> 7377 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request <br> 7378 * } <br> 7379 * @private 7380 */ 7381 _doGET: function (handlers) { 7382 return this.restRequest(this.getRestUrl(), handlers); 7383 }, 7384 7385 /** 7386 * Common update event handler used by the pubsub callback closure. 7387 * Processes the update event then notifies listeners. 7388 * @param {Object} scope 7389 * An object containing callbacks to handle the asynchronous get 7390 * @param {Object} update 7391 * An object containing callbacks to handle the asynchronous get 7392 * @private 7393 */ 7394 _updateEventHandler: function (scope, update) { 7395 if (scope._processUpdate(update)) { 7396 switch (update.object.Update.event) { 7397 case "POST": 7398 scope._addNotifier.notifyListeners(scope); 7399 break; 7400 case "PUT": 7401 scope._changeNotifier.notifyListeners(scope); 7402 break; 7403 case "DELETE": 7404 scope._deleteNotifier.notifyListeners(scope); 7405 break; 7406 } 7407 } 7408 }, 7409 7410 /** 7411 * Utility method to create a callback to be given to OpenAjax to invoke when a message 7412 * is published on the topic of our REST URL (also XEP-0060 node). 7413 * This needs to be its own defined method so that subclasses can have their own implementation. 7414 * @returns {Function} callback(update) 7415 * The callback to be invoked when an update event is received. This callback will 7416 * process the update and notify listeners. 7417 * @private 7418 */ 7419 _createPubsubCallback: function () { 7420 var _this = this; 7421 return function (update) { 7422 _this._updateEventHandler(_this, update); 7423 }; 7424 }, 7425 7426 /** 7427 * Subscribe to pubsub infra using the REST URL as the topic name. 7428 * @param {finesse.interfaces.RequestHandlers} handlers 7429 * An object containing the handlers for the request 7430 * @private 7431 * @returns {finesse.restservices.RestBase} 7432 */ 7433 subscribe: function (callbacks) { 7434 // Only need to do a subscription to client pubsub. No need to trigger 7435 // a subscription on the Finesse server due to implicit subscribe (at 7436 // least for now). 7437 var _this = this, 7438 topic = Topics.getTopic(this.getXMPPNodePath()), 7439 handlers, 7440 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true, this.contextId); 7441 7442 callbacks = callbacks || {}; 7443 7444 handlers = { 7445 /** @private */ 7446 success: function () { 7447 // Add item to the refresh list in ClientServices to refresh if 7448 // we recover due to our resilient connection. However, do 7449 // not add if doNotRefresh flag is set. 7450 if (!_this.doNotRefresh) { 7451 ClientServices.addToRefreshList(_this); 7452 } 7453 7454 if (typeof callbacks.success === "function") { 7455 callbacks.success(); 7456 } 7457 }, 7458 /** @private */ 7459 error: function (err) { 7460 if (successful) { 7461 ClientServices.unsubscribe(topic, this.contextId); 7462 } 7463 7464 if (typeof callbacks.error === "function") { 7465 callbacks.error(err); 7466 } 7467 } 7468 }; 7469 7470 // Request a node subscription only if this object requires explicit subscriptions 7471 if (this.explicitSubscription === true) { 7472 this._subscribeNode(handlers); 7473 } else { 7474 if (successful) { 7475 this._subid = "OpenAjaxOnly"; 7476 handlers.success(); 7477 } else { 7478 handlers.error(); 7479 } 7480 } 7481 7482 return this; 7483 }, 7484 7485 /** 7486 * Unsubscribe to pubsub infra using the REST URL as the topic name. 7487 * @param {finesse.interfaces.RequestHandlers} handlers 7488 * An object containing the handlers for the request 7489 * @private 7490 * @returns {finesse.restservices.RestBase} 7491 */ 7492 unsubscribe: function (callbacks) { 7493 // Only need to do a subscription to client pubsub. No need to trigger 7494 // a subscription on the Finesse server due to implicit subscribe (at 7495 // least for now). 7496 var _this = this, 7497 topic = Topics.getTopic(this.getRestUrl()), 7498 handlers; 7499 7500 // no longer keep track of object to refresh on reconnect 7501 ClientServices.removeFromRefreshList(_this); 7502 7503 callbacks = callbacks || {}; 7504 7505 handlers = { 7506 /** @private */ 7507 success: function () { 7508 if (typeof callbacks.success === "function") { 7509 callbacks.success(); 7510 } 7511 }, 7512 /** @private */ 7513 error: function (err) { 7514 if (typeof callbacks.error === "function") { 7515 callbacks.error(err); 7516 } 7517 } 7518 }; 7519 7520 if (this._subid) { 7521 ClientServices.unsubscribe(topic, this.contextId); 7522 // Request a node unsubscribe only if this object requires explicit subscriptions 7523 if (this.explicitSubscription === true) { 7524 this._unsubscribeNode(handlers); 7525 } else { 7526 this._subid = undefined; 7527 handlers.success(); 7528 } 7529 } else { 7530 handlers.success(); 7531 } 7532 7533 return this; 7534 }, 7535 7536 /** 7537 * Private utility to perform node subscribe requests for explicit subscriptions 7538 * @param {finesse.interfaces.RequestHandlers} handlers 7539 * An object containing the handlers for the request 7540 * @private 7541 */ 7542 _subscribeNode: function (callbacks) { 7543 var _this = this; 7544 7545 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 7546 callbacks = callbacks || {}; 7547 7548 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 7549 if (err) { 7550 if (typeof callbacks.error === "function") { 7551 callbacks.error(err); 7552 } 7553 } else { 7554 // Store the subid on a successful subscribe 7555 _this._subid = subid; 7556 if (typeof callbacks.success === "function") { 7557 callbacks.success(); 7558 } 7559 } 7560 }); 7561 }, 7562 7563 /** 7564 * Private utility to perform node unsubscribe requests for explicit subscriptions 7565 * @param {finesse.interfaces.RequestHandlers} handlers 7566 * An object containing the handlers for the request 7567 * @private 7568 */ 7569 _unsubscribeNode: function (callbacks) { 7570 var _this = this; 7571 7572 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 7573 callbacks = callbacks || {}; 7574 7575 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 7576 _this._subid = undefined; 7577 if (err) { 7578 if (typeof callbacks.error === "function") { 7579 callbacks.error(err); 7580 } 7581 } else { 7582 if (typeof callbacks.success === "function") { 7583 callbacks.success(); 7584 } 7585 } 7586 }); 7587 }, 7588 7589 /** 7590 * Validate and store the object into the internal data store. 7591 * @param {Object} object 7592 * The JavaScript object that should match of schema of this REST object. 7593 * @returns {Boolean} 7594 * True if the object was validated and stored successfully. 7595 * @private 7596 */ 7597 _processObject: function (object) { 7598 if (this._validate(object)) { 7599 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7600 7601 // If loaded for the first time, call the load notifiers. 7602 if (!this._loaded) { 7603 this._loaded = true; 7604 this._loadNotifier.notifyListeners(this); 7605 } 7606 7607 return true; 7608 } 7609 return false; 7610 }, 7611 7612 /** 7613 * process the success response 7614 */ 7615 _processSuccessObject: function(_this, rsp, isLoaded, retries) { 7616 if (!_this._processResponse(rsp)) { 7617 if (retries > 0) { 7618 setTimeout(function () { 7619 _this._synchronize(retries - 1); 7620 }, _RETRY_INTERVAL); 7621 } else { 7622 _this._errorNotifier.notifyListeners(_this); 7623 } 7624 } else { 7625 // If this object was already "loaded" prior to 7626 // the _doGET request, then call the 7627 // changeNotifier 7628 if (isLoaded) { 7629 _this._changeNotifier.notifyListeners(_this); 7630 } 7631 } 7632 }, 7633 7634 /** 7635 * Normalize the object to mitigate the differences between the backend 7636 * and what this REST object should hold. For example, the backend sends 7637 * send an event with the root property name being lower case. In order to 7638 * match the GET, the property should be normalized to an upper case. 7639 * @param {Object} object 7640 * The object which should be normalized. 7641 * @returns {Object} 7642 * Return the normalized object. 7643 * @private 7644 */ 7645 _normalize: function (object) { 7646 var 7647 restType = this.getRestType(), 7648 // Get the REST object name with first character being lower case. 7649 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 7650 7651 // Normalize payload to match REST object. The payload for an update 7652 // use a lower case object name as oppose to upper case. Only normalize 7653 // if necessary. 7654 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 7655 //Since the object is going to be modified, clone the object so that 7656 //it doesn't affect others (due to OpenAjax publishing to other 7657 //subscriber. 7658 object = jQuery.extend(true, {}, object); 7659 7660 object[restType] = object[objRestType]; 7661 delete(object[objRestType]); 7662 } 7663 return object; 7664 }, 7665 7666 /** 7667 * Utility method to process the response of a successful get 7668 * @param {Object} rsp 7669 * The response of a successful get 7670 * @returns {Boolean} 7671 * True if the update was successfully processed (the response object 7672 * passed the schema validation) and updated the internal data cache, 7673 * false otherwise. 7674 * @private 7675 */ 7676 _processResponse: function (rsp) { 7677 try { 7678 if (this.keepRestResponse) { 7679 this._restResponse = rsp.content; 7680 this.restResponseStatus = rsp.status; 7681 } 7682 return this._processObject(rsp.object); 7683 } 7684 catch (err) { 7685 this._logger.error(this.getRestType() + ': ' + err); 7686 } 7687 return false; 7688 }, 7689 7690 /** 7691 * Method that is called at the end of _processUpdate() which by default 7692 * will just delete the requestId-to-callbacks mapping but can be overridden. 7693 * @param {String} requestId The requestId of the event 7694 */ 7695 _postProcessUpdateStrategy: function (requestId) { 7696 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 7697 delete this._pendingCallbacks[requestId]; 7698 }, 7699 7700 /** 7701 * Utility method to process the update notification. 7702 * @param {Object} update 7703 * The payload of an update notification. 7704 * @returns {Boolean} 7705 * True if the update was successfully processed (the update object 7706 * passed the schema validation) and updated the internal data cache, 7707 * false otherwise. 7708 * @private 7709 */ 7710 _processUpdate: function (update) { 7711 try { 7712 var updateObj, requestId, fakeResponse, receivedError; 7713 7714 // The backend will send the data object with a lower case. To be 7715 // consistent with what should be represented in this object, the 7716 // object name should be upper case. This will normalize the object. 7717 updateObj = this._normalize(update.object.Update.data); 7718 7719 // Store the last event. 7720 this._lastUpdate = update.object; 7721 7722 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 7723 7724 if (requestId && this._pendingCallbacks[requestId]) { 7725 7726 /* 7727 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 7728 * a simulated/"fake" AJAX response object from the information in the received event. 7729 * The constructed object should conform to the contract for response objects specified 7730 * in _createAjaxHandler(). 7731 */ 7732 fakeResponse = {}; 7733 7734 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 7735 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 7736 //doing a parse(stringify(update)). 7737 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 7738 7739 fakeResponse.object = {}; 7740 7741 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 7742 7743 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 7744 receivedError = updateObj.apiErrors.apiError; 7745 fakeResponse.object.ApiErrors = {}; 7746 fakeResponse.object.ApiErrors.ApiError = {}; 7747 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 7748 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 7749 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 7750 /** 7751 * Adding peripheral error codes and desc in response object 7752 */ 7753 fakeResponse.object.ApiErrors.ApiError.PeripheralErrorCode = receivedError.peripheralErrorCode || undefined; 7754 fakeResponse.object.ApiErrors.ApiError.PeripheralErrorMsg = receivedError.peripheralErrorMsg || undefined; 7755 fakeResponse.object.ApiErrors.ApiError.PeripheralErrorText = receivedError.peripheralErrorText || undefined; 7756 7757 /* 7758 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 7759 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 7760 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 7761 **/ 7762 fakeResponse.status = 400; 7763 7764 } else { //Success case 7765 7766 fakeResponse.object = this._lastUpdate; 7767 7768 /* 7769 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 7770 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 7771 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 7772 **/ 7773 fakeResponse.status = 200; 7774 } 7775 7776 try { 7777 7778 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 7779 this._pendingCallbacks[requestId].error(fakeResponse); 7780 } 7781 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 7782 /*else if (this._pendingCallbacks[requestId].success) { 7783 this._pendingCallbacks[requestId].success(fakeResponse); 7784 }*/ 7785 7786 } catch (callbackErr) { 7787 7788 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 7789 7790 } 7791 7792 this._postProcessUpdateStrategy(requestId); 7793 7794 } 7795 7796 return this._processObject(updateObj); 7797 } 7798 catch (err) { 7799 this._logger.error(this.getRestType() + ': ' + err); 7800 } 7801 return false; 7802 }, 7803 7804 /** 7805 * Utility method to create ajax response handler closures around the 7806 * provided callbacks. Callbacks should be passed through from .ajax(). 7807 * makeRequest is responsible for garbage collecting these closures. 7808 * @param {finesse.interfaces.RequestHandlers} handlers 7809 * An object containing the handlers for the request 7810 * @returns {Object} <br> 7811 * { <br> 7812 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns <br> 7813 * callback: {function} Callback handler to be invoked when the response returns <br> 7814 * } <br> 7815 * @private 7816 */ 7817 _createAjaxHandler: function (options) { 7818 //We should not need to check this again since it has already been done in .restRequest() 7819 //options = options || {}; 7820 7821 //Flag to indicate whether or not to process the response 7822 var abort = false, 7823 7824 //Get a reference to the parent User object 7825 _this = this; 7826 7827 return { 7828 7829 abort: function () { 7830 abort = true; 7831 }, 7832 7833 callback: function (rsp) { 7834 7835 if (abort) { 7836 // Do not process the response 7837 return; 7838 } 7839 7840 var requestId, error = false, rspObj; 7841 7842 if (options.success || options.error) { 7843 rspObj = { 7844 status: rsp.rc, 7845 content: rsp.text, 7846 isUnsent: rsp.isUnsent 7847 }; 7848 7849 if (!_this.doNotLog) { 7850 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "', isUnsent = " + rspObj.isUnsent); 7851 } 7852 7853 //Some responses may not have a body. 7854 if (rsp.text && rsp.text.length > 0) { 7855 try { 7856 rspObj.object = _this._util.xml2js(rsp.text); 7857 } catch (e) { 7858 error = true; 7859 rspObj.error = { 7860 errorType: "parseError", 7861 errorMessage: "Could not serialize XML: " + e 7862 }; 7863 } 7864 } else { 7865 rspObj.object = {}; 7866 } 7867 7868 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 7869 if (options.success) { 7870 options.success(rspObj); 7871 } 7872 } else { 7873 if (options.error) { 7874 options.error(rspObj); 7875 } 7876 } 7877 7878 /* 7879 * If a synchronous error happened after a non-GET request (usually a validation error), we 7880 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 7881 * will arrive later. The corresponding requestId should be present in the response headers. 7882 * 7883 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 7884 * 'requestId' below. 7885 **/ 7886 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 7887 requestId = rsp.headers.requestid[0]; 7888 if (_this._pendingCallbacks[requestId]) { 7889 delete _this._pendingCallbacks[requestId]; 7890 } 7891 } 7892 } 7893 } 7894 }; 7895 }, 7896 7897 /** 7898 * Utility method to make an asynchronous request 7899 * @param {String} url 7900 * The unencoded URL to which the request is sent (will be encoded) 7901 * @param {Object} options 7902 * An object containing additional options for the request. 7903 * @param {Object} options.content 7904 * An object to send in the content body of the request. Will be 7905 * serialized into XML before sending. 7906 * @param {String} options.method 7907 * The type of request. Defaults to "GET" when none is specified. 7908 * @param {Function} options.success(rsp) 7909 * A callback function to be invoked for a successful request. 7910 * { 7911 * status: {Number} The HTTP status code returned 7912 * content: {String} Raw string of response 7913 * object: {Object} Parsed object of response 7914 * } 7915 * @param {Function} options.error(rsp) 7916 * A callback function to be invoked for an unsuccessful request. 7917 * { 7918 * status: {Number} The HTTP status code returned 7919 * content: {String} Raw string of response 7920 * object: {Object} Parsed object of response 7921 * error: {Object} Wrapped exception that was caught 7922 * error.errorType: {String} Type of error that was caught 7923 * error.errorMessage: {String} Message associated with error 7924 * } 7925 * @returns {Object} <br> 7926 * { <br> 7927 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request <br> 7928 * } <br> 7929 * @private 7930 */ 7931 restRequest: function(url,options) { 7932 // get the resource name of the rest to use it as cache key 7933 var restName = url.substring(url.lastIndexOf('/') + 1, url.length); 7934 // Protect against null dereferencing of options 7935 // allowing its (nonexistent) keys to be read as 7936 // undefined 7937 options = options || {}; 7938 options.success = this._util 7939 .validateHandler(options.success); 7940 options.error = this._util 7941 .validateHandler(options.error); 7942 7943 // true if this should be a GET request, false 7944 // otherwise 7945 if (!options.method || options.method === "GET") { 7946 // Disable caching for GETs 7947 if (url.indexOf("?") > -1) { 7948 url += "&"; 7949 } else { 7950 url += "?"; 7951 } 7952 7953 // append additional query parameters 7954 url += this.getRestUrlAdditionalParameters(); 7955 url += "nocache=" + this._util.currentTimeMillis(); 7956 7957 if(this._shouldAllowBypassServerCache()) { 7958 url += '&bypassServerCache=true'; 7959 } 7960 7961 } else { 7962 /** 7963 * If not GET, generate a requestID and add it 7964 * to the headers, then wrap callbacks into an 7965 * object and store it in _pendingCallbacks. If 7966 * we receive a synchronous error response 7967 * instead of a 202 as expected, the AJAX 7968 * handler will clean up _pendingCallbacks. 7969 */ 7970 /* 7971 * TODO: Clean up _pendingCallbacks if an entry 7972 * persists after a certain amount of time has 7973 * passed. In the block below, can store the 7974 * current time (new Date().getTime()) alongside 7975 * the callbacks in the new _pendingCallbacks 7976 * entry. Then iterate through a copty of 7977 * _pendingCallbacks, deleting all entries 7978 * inside _pendingCallbacks that are older than 7979 * a certain threshold (2 minutes for example.) 7980 * This solves a potential memory leak issue if 7981 * we never receive an event for a given stored 7982 * requestId; we don't want to store unfired 7983 * callbacks forever. 7984 */ 7985 /** @private */ 7986 options.uuid = this._util.generateUUID(); 7987 // params[gadgets.io.RequestParameters.HEADERS].requestId 7988 // = options.uuid; 7989 // By default, Shindig strips nearly all of the 7990 // response headers, but this parameter tells 7991 // Shindig 7992 // to send the headers through unmodified; we 7993 // need to be able to read the 'requestId' 7994 // header if we 7995 // get a synchronous error as a result of a 7996 // non-GET request. (See the bottom of 7997 // _createAjaxHandler().) 7998 // params[gadgets.io.RequestParameters.GET_FULL_HEADERS] 7999 // = "true"; 8000 this._pendingCallbacks[options.uuid] = {}; 8001 this._pendingCallbacks[options.uuid].success = options.success; 8002 this._pendingCallbacks[options.uuid].error = options.error; 8003 } 8004 8005 /** 8006 * only checking for the host name for now. We could have extended it to scheme and port, but at this point it is not required. 8007 */ 8008 var restHost = ClientServices.getRestHost().toLowerCase(); 8009 if(restHost === "localhost" || restHost === window.location.hostname.toLowerCase()) { 8010 return this._restRequestThroughAjax(url,options, restName); 8011 } else { 8012 return this._restRequestThroughShindig(url,options); 8013 } 8014 }, 8015 8016 /** 8017 * Utility method to make AJAX request 8018 * @param {String} url 8019 * The unencoded URL to which the request is sent 8020 * @param {Object} options 8021 * An object containing additional options for the request. 8022 * @returns {Object} <br> 8023 * { <br> 8024 * abort: {function} Function that signifies the callback handler to NOT process the response of this request <br> 8025 * } <br> 8026 */ 8027 _restRequestThroughAjax : function(url, options, restName) { 8028 var encodedUrl, ajaxHandler, scheme = window.location.protocol, host = window.location.hostname, 8029 port = window.location.port, dataTypeAX, contentTypeAX, mtype, postdata = "", auth, rspObj, 8030 locale = this._config.locale, userName=this._config.id, processdata=true; 8031 8032 encodedUrl = encodeURI(url) 8033 + (window.errorOnRestRequest ? "ERROR" : ""); 8034 8035 ajaxHandler = this._createAjaxHandler(options); 8036 // ClientServices.makeRequest(encodedUrl, 8037 // ajaxHandler.callback, params); 8038 mtype = options.method || 'GET'; 8039 encodedUrl = scheme + "//" + host + ":" + port 8040 + encodedUrl; 8041 8042 if (typeof options.content === "object" 8043 && typeof options.content !== "undefined") { 8044 // Except get, all the other operations accepts 8045 // application/xml only. 8046 // @Consumes in all the webservices except GET 8047 // are having this. 8048 // Sending the local storage logs in zip file uses multipart REST API as of now. 8049 if (options.contentType === 'multipart') { 8050 contentTypeAX = false; 8051 postdata = options.content; 8052 processdata=false; 8053 } else { 8054 8055 postdata = this._util.js2xml(options.content); 8056 if (postdata !== null && postdata !== "") { 8057 contentTypeAX = "application/xml"; 8058 } else { 8059 // in desktop, reason code GET request was 8060 // called with empty object content 8061 // if not able to parse any content to post 8062 // data, then it is GET only 8063 contentTypeAX = ""; 8064 dataTypeAX = "text"; 8065 } 8066 } 8067 } else { 8068 // No content type for GET operation, by default 8069 // it will take text/plain || application/xml. 8070 // for dataType - GET will result plain text, 8071 // which we are taking as xml. 8072 // Queried list of @Produces from code base, all 8073 // the GET operations accepts text/plain OR 8074 // application/xml and produces application/xml 8075 contentTypeAX = ""; 8076 dataTypeAX = "text"; 8077 } 8078 auth = this._util.getAuthHeaderString(this._config); 8079 8080 if (!this.doNotLog) { 8081 this._logger.log(this.getRestType() 8082 + ": requestId='" + options.uuid 8083 + "', Making REST request: method=" 8084 + (options.method || "GET") + ", url='" 8085 + encodedUrl + "'"); 8086 } 8087 8088 // find if rest response should be returned from cache or server call need to make 8089 var cacheKey = ""; 8090 8091 if(this._rcs.getRestNameForRequestFromCache(restName, url)) { 8092 //remove the cache query parameter 8093 if(url.indexOf('?') > -1){ 8094 cacheKey = Topics.getTopic(url.split('?')[0]); 8095 } else { 8096 cacheKey = Topics.getTopic(url); 8097 } 8098 } 8099 8100 if(this._rcs.isRestCacheServiceReachable() && cacheKey && (!options.method || options.method === 'GET')) { 8101 _this = this; 8102 var dbOptions = { 8103 key: cacheKey, 8104 onsuccess: function (result) { 8105 if(result && result.data && result.data.text){ 8106 _this._logger.log("Rest Caching: Rest data successfully fetched from cache : "+ _this.getRestType()); 8107 // Used in Automation - To confirm that cache data was used during failover 8108 sessionStorage.setItem("restCacheUsed", "true"); 8109 ajaxHandler.callback(result.data); 8110 } else { 8111 _this._logger.log("Rest Caching: Rest data not available in the cache, making rest call : " + _this.getRestType()); 8112 callAjaxRequest(_this); 8113 } 8114 }, 8115 onerror: function () { 8116 callAjaxRequest(); 8117 } 8118 } 8119 _this._logger.log("Rest Caching: Check if rest data is available in the cache for : "+ cacheKey); 8120 finesse.idb.RestCacheDataContainer.fetchData(dbOptions); 8121 return; 8122 } 8123 8124 if(SessionStorageCachingService.isCacheable(this.getRestType()) && options.method === 'GET' && SessionStorageCachingService.getItem(this.getRestType())) { 8125 ajaxHandler.callback(Utilities.xml2js(SessionStorageCachingService.getItem(this.getRestType()))); 8126 return; 8127 } 8128 8129 var self = this; 8130 callAjaxRequest(); 8131 function callAjaxRequest(_this) { 8132 self = _this? _this : self; 8133 $.ajax({ 8134 url : encodedUrl, 8135 headers : self.extraHeaders || {}, 8136 beforeSend : function(xhr) { 8137 xhr.setRequestHeader( 8138 "Authorization", auth); 8139 xhr.setRequestHeader("locale", 8140 locale); 8141 xhr.setRequestHeader("f_username", 8142 userName); 8143 if (options.uuid) { 8144 xhr.setRequestHeader( 8145 "requestId", 8146 options.uuid); 8147 } 8148 }, 8149 dataType : dataTypeAX, // xml or json? 8150 contentType : contentTypeAX, 8151 processData: processdata, 8152 type : mtype, 8153 data : postdata, 8154 timeout : self.ajaxRequestTimeout, 8155 success : function(response, status, 8156 xhr) { 8157 var rspObj = { 8158 rc : xhr.status, 8159 text : response, 8160 isUnsent : xhr.readyState==0, 8161 headers : { 8162 requestid : xhr 8163 .getResponseHeader("requestId") 8164 }, 8165 restType: self.getRestType() 8166 }; 8167 // check of rest responsed should be cached or not 8168 var _rcs = window.finesse.RestCachingService; 8169 if(_rcs.isCacheableForFailover(self.getRestType()) || _rcs.isCacheableForFailover(options.restType)){ 8170 var cacheKey = ""; 8171 if(options.restType){ 8172 cacheKey = Topics.getTopic(self.getRestUrl() + options.restType); 8173 } else { 8174 cacheKey = Topics.getTopic(self.getRestUrl()); 8175 } 8176 8177 if(_rcs.isRestCacheServiceReachable()){ 8178 self._logger.log("Saving rest data in the cache: "+cacheKey) 8179 finesse.idb.RestCacheDataContainer.saveOrUpdateData([{ 8180 key: cacheKey, data: rspObj 8181 }]); 8182 } 8183 } 8184 if(SessionStorageCachingService.isCacheable(self.getRestType()) && rspObj.text !== null) { 8185 self._logger.log("Saving rest data in the session storage cache: "+self.getRestType()) 8186 SessionStorageCachingService.setItem(self.getRestType(),rspObj.text); 8187 } 8188 ajaxHandler.callback(rspObj); 8189 }, 8190 error : function(jqXHR, request, error) { 8191 var resObj = { 8192 rc : jqXHR.status, 8193 text : jqXHR.responseText, 8194 isUnsent : jqXHR.readyState==0, 8195 headers : { 8196 requestid : jqXHR 8197 .getResponseHeader("requestId") 8198 } 8199 }; 8200 ajaxHandler.callback(resObj); 8201 } 8202 }); 8203 } 8204 return { 8205 abort : ajaxHandler.abort 8206 }; 8207 }, 8208 8209 /** 8210 * Utility method to make request through Shindig 8211 * @param {String} url 8212 * The unencoded URL to which the request is sent 8213 * @param {Object} options 8214 * An object containing additional options for the request. 8215 * @returns {Object} <br> 8216 * { <br> 8217 * abort: {function} Function that signifies the callback handler to NOT process the response of this request <br> 8218 * } <br> 8219 */ 8220 _restRequestThroughShindig: function(url, options) { 8221 var params, encodedUrl, ajaxHandler; 8222 8223 params = {}; 8224 options = options || {}; 8225 8226 params[gadgets.io.RequestParameters.HEADERS] = this.extraHeaders || {}; 8227 params[gadgets.io.RequestParameters.METHOD] = options.method; 8228 8229 8230 if (!options.method || options.method === "GET") { 8231 8232 } else { 8233 params[gadgets.io.RequestParameters.HEADERS].requestId = options.uuid; 8234 params[gadgets.io.RequestParameters.GET_FULL_HEADERS] = "true"; 8235 8236 } 8237 8238 encodedUrl = encodeURI(url) + (window.errorOnRestRequest ? "ERROR" : ""); 8239 8240 if (!this.doNotLog) { 8241 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', Making REST request: method=" + (options.method || "GET") + ", url='" + encodedUrl + "'"); 8242 } 8243 8244 8245 if (typeof options.content === "object") { 8246 8247 params[gadgets.io.RequestParameters.HEADERS]["Content-Type"] = "application/xml"; 8248 params[gadgets.io.RequestParameters.POST_DATA] = this._util.js2xml(options.content); 8249 8250 if (!this.doNotLog) { 8251 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', POST_DATA='" + params[gadgets.io.RequestParameters.POST_DATA] + "'"); 8252 } 8253 } 8254 8255 ajaxHandler = this._createAjaxHandler(options); 8256 ClientServices.makeRequest(encodedUrl, ajaxHandler.callback, params); 8257 8258 return { 8259 abort: ajaxHandler.abort 8260 }; 8261 }, 8262 8263 /** 8264 * Retrieves a reference to a particular notifierType. 8265 * 8266 * @param notifierType 8267 * is a string which indicates the notifier to retrieve 8268 * ('load', 'change', 'add', 'delete', 'error') 8269 * @return {Notifier} 8270 * @private 8271 */ 8272 _getNotifierReference: function (notifierType) { 8273 var notifierReference = null; 8274 if (notifierType === 'load') { 8275 notifierReference = this._loadNotifier; 8276 } else if (notifierType === 'change') { 8277 notifierReference = this._changeNotifier; 8278 } else if (notifierType === 'add') { 8279 notifierReference = this._addNotifier; 8280 } else if (notifierType === 'delete') { 8281 notifierReference = this._deleteNotifier; 8282 } else if (notifierType === 'error') { 8283 notifierReference = this._errorNotifier; 8284 } else { 8285 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 8286 } 8287 8288 return notifierReference; 8289 } 8290 }); 8291 8292 window.finesse = window.finesse || {}; 8293 window.finesse.restservices = window.finesse.restservices || {}; 8294 window.finesse.restservices.RestBase = RestBase; 8295 8296 return RestBase; 8297 }); 8298 8299 /** The following comment is to prevent jslint errors about 8300 * using variables before they are defined. 8301 */ 8302 /*global finesse*/ 8303 8304 /** 8305 * JavaScript base object that all REST collection objects should 8306 * inherit from because it encapsulates and provides the common functionality 8307 * that all REST objects need. 8308 * 8309 * @requires finesse.clientservices.ClientServices 8310 * @requires Class 8311 * @requires finesse.FinesseBase 8312 * @requires finesse.restservices.RestBase 8313 */ 8314 8315 /** 8316 * @class 8317 * JavaScript representation of a REST collection object. 8318 * 8319 * @constructor 8320 * @param {Function} callbacks.onCollectionAdd(this) 8321 * Callback to invoke upon successful item addition to the collection. 8322 * @param {Function} callbacks.onCollectionDelete(this) 8323 * Callback to invoke upon successful item deletion from the collection. 8324 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 8325 */ 8326 /** @private */ 8327 define('restservices/RestCollectionBase',[ 8328 'restservices/RestBase', 8329 'utilities/Utilities', 8330 'restservices/Notifier' 8331 ], 8332 function (RestBase, Utilities, Notifier) { 8333 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 8334 8335 /** 8336 * Boolean function that specifies whether the collection handles subscribing 8337 * and propagation of events for the individual REST object items the 8338 * collection holds. False by default. Subclasses should override if true. 8339 * @private 8340 */ 8341 supportsRestItemSubscriptions: false, 8342 8343 /** 8344 * Gets the constructor the individual items that make of the collection. 8345 * For example, a Dialogs collection object will hold a list of Dialog items. 8346 * @throws Error because subtype must implement. 8347 * @private 8348 */ 8349 getRestItemClass: function () { 8350 throw new Error("getRestItemClass(): Not implemented in subtype."); 8351 }, 8352 8353 /** 8354 * Gets the REST type of the individual items that make of the collection. 8355 * For example, a Dialogs collection object will hold a list of Dialog items. 8356 * @throws Error because subtype must implement. 8357 * @private 8358 */ 8359 getRestItemType: function () { 8360 throw new Error("getRestItemType(): Not implemented in subtype."); 8361 }, 8362 8363 /** 8364 * The base REST URL in which items this object contains can be referenced. 8365 * @return {String} 8366 * The REST URI for items this object contains. 8367 * @private 8368 */ 8369 getRestItemBaseUrl: function () { 8370 var 8371 restUrl = "/finesse/api"; 8372 8373 //Append the REST type. 8374 restUrl += "/" + this.getRestItemType(); 8375 8376 return restUrl; 8377 }, 8378 8379 /* 8380 * Creates a new object from the given data 8381 * @param data - data object 8382 * @private 8383 */ 8384 _objectCreator: function (data) { 8385 var objectId = this._extractId(data), 8386 newRestObj = this._collection[objectId], 8387 _this = this; 8388 8389 //Prevent duplicate entries into collection. 8390 if (!newRestObj) { 8391 //Create a new REST object using the subtype defined by the 8392 //overridden method. 8393 newRestObj = new (this.getRestItemClass())({ 8394 doNotSubscribe: this.handlesItemSubscription, 8395 doNotRefresh: this.handlesItemRefresh, 8396 id: objectId, 8397 data: data, 8398 onLoad: function (newObj) { 8399 //Normalize and add REST object to collection datastore. 8400 _this._collection[objectId] = newObj; 8401 _this._collectionAddNotifier.notifyListeners(newObj); 8402 _this.length += 1; 8403 } 8404 }); 8405 } 8406 else { 8407 //If entry already exist in collection, process the new event, 8408 //and notify all change listeners since an existing object has 8409 //change. This could happen in the case when the Finesse server 8410 //cycles, and sends a snapshot of the user's calls. 8411 newRestObj._processObject(data); 8412 newRestObj._changeNotifier.notifyListeners(newRestObj); 8413 } 8414 }, 8415 8416 /* 8417 * Deletes and object and notifies its handlers 8418 * @param data - data object 8419 * @private 8420 */ 8421 _objectDeleter: function (data) { 8422 var objectId = this._extractId(data), 8423 object = this._collection[objectId]; 8424 if (object) { 8425 //Even though this is a delete, let's make sure the object we are passing has got good data 8426 object._processObject(data); 8427 //Notify listeners and delete from internal datastore. 8428 this._collectionDeleteNotifier.notifyListeners(object); 8429 delete this._collection[objectId]; 8430 this.length -= 1; 8431 } 8432 }, 8433 8434 /** 8435 * Creates an anonymous function for notifiying error listeners of a particular object 8436 * data. 8437 * @param obj - the objects whose error listeners to notify 8438 * @returns {Function} 8439 * Callback for notifying of errors 8440 * @private 8441 */ 8442 _createErrorNotifier: function (obj) { 8443 return function (err) { 8444 obj._errorNotifier.notifyListeners(err); 8445 }; 8446 }, 8447 8448 /** 8449 * Replaces the collection with a refreshed list using the passed in 8450 * data. 8451 * @param data - data object (usually this._data) 8452 * @private 8453 */ 8454 _buildRefreshedCollection: function (data) { 8455 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 8456 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 8457 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 8458 } else { 8459 dataArray = []; 8460 } 8461 8462 // iterate through each item in the new data and add to or update collection 8463 for (i = 0; i < dataArray.length; i += 1) { 8464 dataObject = {}; 8465 dataObject[this.getRestItemType()] = dataArray[i]; 8466 objectId = this._extractId(dataObject); 8467 8468 this._objectCreator(dataObject); 8469 newIds.push(objectId); 8470 8471 // resubscribe if the object requires an explicit subscription 8472 object = this._collection[objectId]; 8473 if (this.handlesItemRefresh && object.explicitSubscription) { 8474 object._subscribeNode({ 8475 error: this._createErrorNotifier(object) 8476 }); 8477 } 8478 } 8479 8480 // now clean up items (if any) that were removed 8481 for (objectId in this._collection) { 8482 if (this._collection.hasOwnProperty(objectId)) { 8483 foundFlag = false; 8484 for (i = newIds.length - 1; i >= 0; i -= 1) { 8485 if (newIds[i] === objectId) { 8486 foundFlag = true; 8487 break; 8488 } 8489 } 8490 // did not find in updated list, so delete it 8491 if (!foundFlag) { 8492 this._objectDeleter({'data': this._collection[objectId]._data}); 8493 } 8494 } 8495 } 8496 }, 8497 8498 /** 8499 * The actual refresh operation, refactored out so we don't have to repeat code 8500 * @private 8501 */ 8502 _RESTRefresh: function () { 8503 var _this = this; 8504 this._doGET({ 8505 success: function(rsp) { 8506 if (_this._processResponse(rsp)) { 8507 _this._buildRefreshedCollection(_this._data); 8508 } else { 8509 _this._errorNotifier.notifyListeners(_this); 8510 } 8511 }, 8512 error: function(rsp) { 8513 _this._errorNotifier.notifyListeners(rsp); 8514 } 8515 }); 8516 }, 8517 8518 /** 8519 * Force an update on this object. Since an asynchronous GET is performed, 8520 * it is necessary to have an onChange handler registered in order to be 8521 * notified when the response of this returns. 8522 * @returns {finesse.restservices.RestBaseCollection} 8523 * This RestBaseCollection object to allow cascading 8524 */ 8525 refresh: function() { 8526 var _this = this, isLoaded = this._loaded; 8527 8528 // resubscribe if the collection requires an explicit subscription 8529 if (this.explicitSubscription) { 8530 this._subscribeNode({ 8531 success: function () { 8532 _this._RESTRefresh(); 8533 }, 8534 error: function (err) { 8535 _this._errorNotifier.notifyListeners(err); 8536 } 8537 }); 8538 } else { 8539 this._RESTRefresh(); 8540 } 8541 8542 return this; // Allow cascading 8543 }, 8544 8545 /** 8546 * @private 8547 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 8548 * format of an array of {(Object Type): object} objects. For example, a 8549 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 8550 * @param skipOuterObject If {true} is passed in for this param, then the "data" 8551 * property is returned instead of an object with the 8552 * data appended. 8553 * @return {Array} 8554 */ 8555 extractCollectionData: function (skipOuterObject) { 8556 var restObjs, 8557 obj, 8558 result = [], 8559 _this = this; 8560 8561 if (this._data) 8562 { 8563 restObjs = this._data[this.getRestItemType()]; 8564 8565 if (restObjs) 8566 { 8567 // check if there are multiple objects to pass 8568 if (!$.isArray(restObjs)) 8569 { 8570 restObjs = [restObjs]; 8571 } 8572 8573 // if so, create an object for each and add to result array 8574 $.each(restObjs, function (id, object) { 8575 if (skipOuterObject === true) 8576 { 8577 obj = object; 8578 } 8579 else 8580 { 8581 obj = {}; 8582 obj[_this.getRestItemType()] = object; 8583 } 8584 result.push(obj); 8585 }); 8586 } 8587 8588 } 8589 8590 return result; 8591 }, 8592 8593 /** 8594 * For Finesse, collections are handled uniquely on a POST and 8595 * doesn't necessary follow REST conventions. A POST on a collection 8596 * doesn't mean that the collection has been created, it means that an 8597 * item has been added to the collection. This function will generate 8598 * a closure which will handle this logic appropriately. 8599 * @param {Object} scope 8600 * The scope of where the callback should be invoked. 8601 * @private 8602 */ 8603 _addHandlerCb: function (scope) { 8604 return function (restItem) { 8605 var data = restItem.extractCollectionData(); 8606 8607 $.each(data, function (id, object) { 8608 scope._objectCreator(object); 8609 }); 8610 }; 8611 }, 8612 8613 /** 8614 * For Finesse, collections are handled uniquely on a DELETE and 8615 * doesn't necessary follow REST conventions. A DELETE on a collection 8616 * doesn't mean that the collection has been deleted, it means that an 8617 * item has been deleted from the collection. This function will generate 8618 * a closure which will handle this logic appropriately. 8619 * @param {Object} scope 8620 * The scope of where the callback should be invoked. 8621 * @private 8622 */ 8623 _deleteHandlerCb: function (scope) { 8624 return function (restItem) { 8625 var data = restItem.extractCollectionData(); 8626 8627 $.each(data, function (id, obj) { 8628 scope._objectDeleter(obj); 8629 }); 8630 }; 8631 }, 8632 8633 /** 8634 * Utility method to process the update notification for Rest Items 8635 * that are children of the collection whose events are published to 8636 * the collection's node. 8637 * @param {Object} update 8638 * The payload of an update notification. 8639 * @returns {Boolean} 8640 * True if the update was successfully processed (the update object 8641 * passed the schema validation) and updated the internal data cache, 8642 * false otherwise. 8643 * @private 8644 */ 8645 _processRestItemUpdate: function (update) { 8646 var object, objectId, updateObj = update.object.Update; 8647 8648 //Extract the ID from the source if the Update was an error. 8649 if (updateObj.data.apiErrors) { 8650 objectId = Utilities.getId(updateObj.source); 8651 } 8652 //Otherwise extract from the data object itself. 8653 else { 8654 objectId = this._extractId(updateObj.data); 8655 } 8656 8657 object = this._collection[objectId]; 8658 if (object) { 8659 if (object._processUpdate(update)) { 8660 switch (updateObj.event) { 8661 case "POST": 8662 object._addNotifier.notifyListeners(object); 8663 break; 8664 case "PUT": 8665 object._changeNotifier.notifyListeners(object); 8666 break; 8667 case "DELETE": 8668 object._deleteNotifier.notifyListeners(object); 8669 break; 8670 } 8671 } 8672 } 8673 }, 8674 8675 /** 8676 * SUBCLASS IMPLEMENTATION (override): 8677 * For collections, this callback has the additional responsibility of passing events 8678 * of collection item updates to the item objects themselves. The collection needs to 8679 * do this because item updates are published to the collection's node. 8680 * @returns {Function} 8681 * The callback to be invoked when an update event is received 8682 * @private 8683 */ 8684 _createPubsubCallback: function () { 8685 var _this = this; 8686 return function (update) { 8687 //If the source of the update is our REST URL, this means the collection itself is modified 8688 if (update.object.Update.source === _this.getRestUrl()) { 8689 _this._updateEventHandler(_this, update); 8690 } else { 8691 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 8692 //rest item update of one of our children that was published on our node (OpenAjax topic) 8693 _this._processRestItemUpdate(update); 8694 } 8695 }; 8696 }, 8697 8698 /** 8699 * @class 8700 * This is the base collection object. 8701 * 8702 * @constructs 8703 * @augments finesse.restservices.RestBase 8704 * @see finesse.restservices.Contacts 8705 * @see finesse.restservices.Dialogs 8706 * @see finesse.restservices.PhoneBooks 8707 * @see finesse.restservices.Queues 8708 * @see finesse.restservices.WorkflowActions 8709 * @see finesse.restservices.Workflows 8710 * @see finesse.restservices.WrapUpReasons 8711 */ 8712 _fakeConstuctor: function () { 8713 /* This is here to hide the real init constructor from the public docs */ 8714 }, 8715 8716 /** 8717 * @private 8718 * @param {Object} options 8719 * An object with the following properties:<ul> 8720 * <li><b>id:</b> The id of the object being constructed</li> 8721 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 8722 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 8723 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 8724 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 8725 * This does not include adding and deleting members of the collection</li> 8726 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 8727 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 8728 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 8729 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8730 * <li><b>content:</b> {String} Raw string of response</li> 8731 * <li><b>object:</b> {Object} Parsed object of response</li> 8732 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8733 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8734 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8735 * </ul></li> 8736 * </ul></li> 8737 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8738 **/ 8739 init: function (options) { 8740 8741 options = options || {}; 8742 options.id = ""; 8743 8744 //Make internal datastore collection to hold a list of objects. 8745 this._collection = {}; 8746 this.length = 0; 8747 8748 //Collections will have additional callbacks that will be invoked when 8749 //an item has been added/deleted. 8750 this._collectionAddNotifier = new Notifier(); 8751 this._collectionDeleteNotifier = new Notifier(); 8752 8753 //Initialize the base class. 8754 this._super(options); 8755 8756 this.addHandler('collectionAdd', options.onCollectionAdd); 8757 this.addHandler('collectionDelete', options.onCollectionDelete); 8758 8759 //For Finesse, collections are handled uniquely on a POST/DELETE and 8760 //doesn't necessary follow REST conventions. A POST on a collection 8761 //doesn't mean that the collection has been created, it means that an 8762 //item has been added to the collection. A DELETE means that an item has 8763 //been removed from the collection. Due to this, we are attaching 8764 //special callbacks to the add/delete that will handle this logic. 8765 this.addHandler("add", this._addHandlerCb(this)); 8766 this.addHandler("delete", this._deleteHandlerCb(this)); 8767 }, 8768 8769 /** 8770 * Returns the collection. 8771 * @returns {Object} 8772 * The collection as an object 8773 */ 8774 getCollection: function () { 8775 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 8776 return this._collection; 8777 }, 8778 8779 /** 8780 * Utility method to build the internal collection data structure (object) based on provided data 8781 * @param {Object} data 8782 * The data to build the internal collection from 8783 * @private 8784 */ 8785 _buildCollection: function (data) { 8786 var i, object, objectId, dataArray; 8787 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 8788 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 8789 for (i = 0; i < dataArray.length; i += 1) { 8790 8791 object = {}; 8792 object[this.getRestItemType()] = dataArray[i]; 8793 objectId = this._extractId(object); 8794 this._collection[objectId] = new (this.getRestItemClass())({ 8795 doNotSubscribe: this.handlesItemSubscription, 8796 doNotRefresh: this.handlesItemRefresh, 8797 id: objectId, 8798 data: object 8799 }); 8800 this.length += 1; 8801 } 8802 } 8803 }, 8804 8805 /** 8806 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 8807 * Override this in subclasses if you need only object with certain attribute values. 8808 * @param {Object} item Item to test. 8809 * @return {Boolean} False to keep, true to filter out (discard); 8810 */ 8811 _filterOutItem: function (item) { 8812 return false; 8813 }, 8814 8815 /** 8816 * Validate and store the object into the internal data store. 8817 * SUBCLASS IMPLEMENTATION (override): 8818 * Performs collection specific logic to _buildCollection internally based on provided data 8819 * @param {Object} object 8820 * The JavaScript object that should match of schema of this REST object. 8821 * @returns {Boolean} 8822 * True if the object was validated and stored successfully. 8823 * @private 8824 */ 8825 _processObject: function (object) { 8826 var i, 8827 restItemType = this.getRestItemType(), 8828 items; 8829 if (this._validate(object)) { 8830 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 8831 8832 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 8833 if (this._data) 8834 { 8835 items = this._data[restItemType]; 8836 8837 if (typeof(items) !== "undefined") 8838 { 8839 if (typeof(items.length) === "undefined") 8840 { 8841 // Single object 8842 if (this._filterOutItem(items)) 8843 { 8844 this._data[restItemType] = items = []; 8845 } 8846 8847 } 8848 else 8849 { 8850 // filter out objects 8851 for (i = items.length - 1; i !== -1; i = i - 1) 8852 { 8853 if (this._filterOutItem(items[i])) 8854 { 8855 items.splice(i, 1); 8856 } 8857 } 8858 } 8859 } 8860 } 8861 8862 // If loaded for the first time, call the load notifiers. 8863 if (!this._loaded) { 8864 this._buildCollection(this._data); 8865 this._loaded = true; 8866 this._loadNotifier.notifyListeners(this); 8867 } 8868 8869 return true; 8870 8871 } 8872 return false; 8873 }, 8874 8875 /** 8876 * Retrieves a reference to a particular notifierType. 8877 * @param {String} notifierType 8878 * Specifies the notifier to retrieve (load, change, error, add, delete) 8879 * @return {Notifier} The notifier object. 8880 */ 8881 _getNotifierReference: function (notifierType) { 8882 var notifierReference; 8883 8884 try { 8885 //Use the base method to get references for load/change/error. 8886 notifierReference = this._super(notifierType); 8887 } catch (err) { 8888 //Check for add/delete 8889 if (notifierType === "collectionAdd") { 8890 notifierReference = this._collectionAddNotifier; 8891 } else if (notifierType === "collectionDelete") { 8892 notifierReference = this._collectionDeleteNotifier; 8893 } else { 8894 //Rethrow exception from base class. 8895 throw err; 8896 } 8897 } 8898 return notifierReference; 8899 } 8900 }); 8901 8902 window.finesse = window.finesse || {}; 8903 window.finesse.restservices = window.finesse.restservices || {}; 8904 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 8905 8906 return RestCollectionBase; 8907 }); 8908 8909 /** 8910 * JavaScript representation of the Finesse Dialog object. 8911 * 8912 * @requires finesse.clientservices.ClientServices 8913 * @requires Class 8914 * @requires finesse.FinesseBase 8915 * @requires finesse.restservices.RestBase 8916 */ 8917 8918 /** @private */ 8919 define('restservices/DialogBase',[ 8920 'restservices/RestBase', 8921 'utilities/Utilities' 8922 ], 8923 function (RestBase, Utilities) { 8924 var DialogBase = RestBase.extend(/** @lends finesse.restservices.DialogBase.prototype */{ 8925 8926 /** 8927 * @class 8928 * A DialogBase is an attempted connection between or among multiple participants, 8929 * for example, a regular phone call, a chat, or an email. 8930 * 8931 * This object is typically extended into individual 8932 * REST Objects (like Dialog, MediaDialog, etc...), and shouldn't be used directly. 8933 * 8934 * @augments finesse.restservices.RestBase 8935 * @constructs 8936 */ 8937 _fakeConstuctor: function () { 8938 /* This is here to hide the real init constructor from the public docs */ 8939 }, 8940 8941 /** 8942 * @private 8943 * 8944 * @param {Object} options 8945 * An object with the following properties:<ul> 8946 * <li><b>id:</b> The id of the object being constructed</li> 8947 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8948 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8949 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8950 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8951 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8952 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8953 * <li><b>content:</b> {String} Raw string of response</li> 8954 * <li><b>object:</b> {Object} Parsed object of response</li> 8955 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8956 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8957 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8958 * </ul></li> 8959 * </ul></li> 8960 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8961 **/ 8962 init: function (options) { 8963 this._super(options); 8964 }, 8965 8966 /** 8967 * @private 8968 * Gets the REST class for the current object - this is the Dialog class. 8969 * @returns {Object} The Dialog class. 8970 */ 8971 getRestClass: function () { 8972 throw new Error("getRestClass(): Not implemented in subtype."); 8973 }, 8974 8975 /** 8976 * @private 8977 * The constant for agent device. 8978 */ 8979 _agentDeviceType: "AGENT_DEVICE", 8980 8981 /** 8982 * @private 8983 * Gets the REST type for the current object - this is a "Dialog". 8984 * @returns {String} The Dialog string. 8985 */ 8986 getRestType: function () { 8987 return "Dialog"; 8988 }, 8989 8990 /** 8991 * @private 8992 * Override default to indicate that this object doesn't support making 8993 * requests. 8994 */ 8995 supportsRequests: false, 8996 8997 /** 8998 * @private 8999 * Override default to indicate that this object doesn't support subscriptions. 9000 */ 9001 supportsSubscriptions: false, 9002 9003 9004 /** 9005 * Getter for the media type. 9006 * @returns {String} The media type. 9007 */ 9008 getMediaType: function () { 9009 this.isLoaded(); 9010 return this.getData().mediaType; 9011 }, 9012 9013 /** 9014 * @private 9015 * Getter for the uri. 9016 * @returns {String} The uri. 9017 */ 9018 getDialogUri: function () { 9019 this.isLoaded(); 9020 return this.getData().uri; 9021 }, 9022 9023 /** 9024 * Getter for the callType. 9025 * @deprecated Use getMediaProperties().callType instead. 9026 * @returns {String} The callType. 9027 */ 9028 getCallType: function () { 9029 this.isLoaded(); 9030 return this.getData().mediaProperties.callType; 9031 }, 9032 9033 9034 /** 9035 * Getter for the Dialog state. 9036 * @returns {String} The Dialog state. 9037 */ 9038 getState: function () { 9039 this.isLoaded(); 9040 return this.getData().state; 9041 }, 9042 9043 /** 9044 * Retrieves a list of participants within the Dialog object. 9045 * @returns {Object} Array list of participants. 9046 * Participant entity properties are as follows:<ul> 9047 * <li>state - The state of the Participant. 9048 * <li>stateCause - The state cause of the Participant. 9049 * <li>mediaAddress - The media address of the Participant. 9050 * <li>startTime - The start Time of the Participant. 9051 * <li>stateChangeTime - The time when participant state has changed. 9052 * <li>actions - These are the actions that a Participant can perform</ul> 9053 */ 9054 getParticipants: function () { 9055 this.isLoaded(); 9056 var participants = this.getData().participants.Participant; 9057 //Due to the nature of the XML->JSO converter library, a single 9058 //element in the XML array will be considered to an object instead of 9059 //a real array. This will handle those cases to ensure that an array is 9060 //always returned. 9061 9062 return Utilities.getArray(participants); 9063 }, 9064 9065 /** 9066 * Updates this dialog's call variables. This function will not do any call variable validation, caller should take care validations before calling this function. 9067 * 9068 * @param {Object} call variables to modify. Ex: {"callVariable1": "value1", "callVariable2": "value2"} 9069 * @param {finesse.interfaces.RequestHandlers} options 9070 * An object containing the handlers for the request 9071 */ 9072 updateCallVariables: function (callvariablesList, options) 9073 { 9074 this.isLoaded(); 9075 var mediaProperties = {}; 9076 var callvariables =[]; 9077 for (var callvariable in callvariablesList) { 9078 if(!callvariablesList.hasOwnProperty(callvariable)) continue; 9079 var entry = {"name":callvariable, "value": callvariablesList[callvariable] }; 9080 callvariables.push(entry); 9081 } 9082 var callVariableEntry = {"CallVariable": callvariables}; 9083 9084 mediaProperties['callvariables'] = callVariableEntry; 9085 options = options || {}; 9086 options.content = {}; 9087 options.content[this.getRestType()] = 9088 { 9089 "mediaProperties": mediaProperties, 9090 "requestedAction": DialogBase.Actions.UPDATE_CALL_DATA 9091 }; 9092 options.method = "PUT"; 9093 this.restRequest(this.getRestUrl(), options); 9094 9095 return this; 9096 }, 9097 /** 9098 * This method retrieves the participant timer counters 9099 * 9100 * @param {String} participantExt Extension of participant. 9101 * @returns {Object} Array of Participants which contains properties :<ul> 9102 * <li>state - The state of the Participant. 9103 * <li>startTime - The start Time of the Participant. 9104 * <li>stateChangeTime - The time when participant state has changed.</ul> 9105 * 9106 */ 9107 getParticipantTimerCounters : function (participantExt) { 9108 var part, participantTimerCounters = {}, idx, participants; 9109 9110 participants = this.getParticipants(); 9111 9112 9113 //Loop through all the participants and find the right participant (based on participantExt) 9114 for(idx=0;idx<participants.length;idx=idx+1) 9115 { 9116 part = participants[idx]; 9117 9118 if (part.mediaAddress === participantExt) 9119 { 9120 participantTimerCounters.startTime= part.startTime; 9121 participantTimerCounters.stateChangeTime= part.stateChangeTime; 9122 participantTimerCounters.state= part.state; 9123 break; 9124 } 9125 } 9126 9127 return participantTimerCounters; 9128 }, 9129 9130 9131 /** 9132 * Retrieves a list of media properties from the dialog object. 9133 * @returns {Object} Map of call variables; names mapped to values. 9134 * Variables may include the following:<ul> 9135 * <li>dialedNumber: The number dialed. 9136 * <li>callType: The type of call. Call types include:<ul> 9137 * <li>ACD_IN 9138 * <li>PREROUTE_ACD_IN 9139 * <li>PREROUTE_DIRECT_AGENT 9140 * <li>TRANSFER 9141 * <li>OTHER_IN 9142 * <li>OUT 9143 * <li>AGENT_INSIDE 9144 * <li>CONSULT 9145 * <li>CONFERENCE 9146 * <li>SUPERVISOR_MONITOR 9147 * <li>OUTBOUND 9148 * <li>OUTBOUND_PREVIEW</ul> 9149 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 9150 * <li>wrapUpReason: A description of the call. 9151 * <li>queueNumber: Number of the agent Skill Group the call is attributed to. 9152 * <li>queueName: Name of the agent Skill Group the call is attributed to. 9153 * <li>callKeyCallId: unique number of the call routed on a particular day. 9154 * <li>callKeyPrefix: represents the day when the call is routed. 9155 * <li>callKeySequenceNum: represents the sequence number of call. 9156 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 9157 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 9158 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 9159 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 9160 * <li>BACampaign 9161 * <li>BAAccountNumber 9162 * <li>BAResponse 9163 * <li>BAStatus<ul> 9164 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 9165 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 9166 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 9167 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 9168 * <li>BADialedListID 9169 * <li>BATimeZone 9170 * <li>BABuddyName</ul></ul> 9171 * 9172 */ 9173 getMediaProperties: function () { 9174 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 9175 9176 this.isLoaded(); 9177 9178 // We have to convert to jQuery object to do a proper compare 9179 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 9180 9181 if ((this._lastMediaPropertiesJQuery !== undefined) 9182 && (this._lastMediaPropertiesMap !== undefined) 9183 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 9184 9185 return this._lastMediaPropertiesMap; 9186 } 9187 9188 currentMediaPropertiesMap = {}; 9189 9190 mpData = this.getData().mediaProperties; 9191 9192 if (mpData) { 9193 if (mpData.callvariables && mpData.callvariables.CallVariable) { 9194 if (mpData.callvariables.CallVariable.length === undefined) { 9195 mpData.callvariables.CallVariable = [mpData.callvariables.CallVariable]; 9196 } 9197 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 9198 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 9199 }); 9200 } 9201 9202 jQuery.each(mpData, function (key, value) { 9203 if (key !== 'callvariables') { 9204 currentMediaPropertiesMap[key] = value; 9205 } 9206 }); 9207 } 9208 9209 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 9210 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 9211 9212 return this._lastMediaPropertiesMap; 9213 }, 9214 9215 9216 9217 /** 9218 * @private 9219 * Invoke a request to the server given a content body and handlers. 9220 * 9221 * @param {Object} contentBody 9222 * A JS object containing the body of the action request. 9223 * @param {finesse.interfaces.RequestHandlers} handlers 9224 * An object containing the handlers for the request 9225 */ 9226 _makeRequest: function (contentBody, handlers) { 9227 // Protect against null dereferencing of options allowing its 9228 // (nonexistent) keys to be read as undefined 9229 handlers = handlers || {}; 9230 9231 this.restRequest(this.getRestUrl(), { 9232 method: 'PUT', 9233 success: handlers.success, 9234 error: handlers.error, 9235 content: contentBody 9236 }); 9237 } 9238 9239 }); 9240 9241 DialogBase.Actions = /** @lends finesse.restservices.DialogBase.Actions.prototype */ { 9242 9243 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 9244 /** 9245 * @class Set of action constants for a Dialog. 9246 * @constructs 9247 */ 9248 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9249 }; 9250 9251 window.finesse = window.finesse || {}; 9252 window.finesse.restservices = window.finesse.restservices || {}; 9253 window.finesse.restservices.DialogBase = DialogBase; 9254 9255 9256 return DialogBase; 9257 }); 9258 9259 /** 9260 * JavaScript representation of the Finesse Dialog object. 9261 * 9262 * @requires finesse.clientservices.ClientServices 9263 * @requires Class 9264 * @requires finesse.FinesseBase 9265 * @requires finesse.restservices.RestBase 9266 */ 9267 9268 /** @private */ 9269 define('restservices/Dialog',[ 9270 'restservices/DialogBase', 9271 'utilities/Utilities' 9272 ], 9273 function (DialogBase, Utilities) { 9274 var Dialog = DialogBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 9275 9276 /** 9277 * @class 9278 * A Dialog is an attempted connection between or among multiple participants, 9279 * for example, a regular phone call, a conference, or a silent monitor session. 9280 * 9281 * @augments finesse.restservices.DialogBase 9282 * @constructs 9283 */ 9284 _fakeConstuctor: function () { 9285 /* This is here to hide the real init constructor from the public docs */ 9286 }, 9287 9288 /** 9289 * @private 9290 * 9291 * @param {Object} options 9292 * An object with the following properties:<ul> 9293 * <li><b>id:</b> The id of the object being constructed</li> 9294 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9295 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9296 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9297 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9298 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9299 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9300 * <li><b>content:</b> {String} Raw string of response</li> 9301 * <li><b>object:</b> {Object} Parsed object of response</li> 9302 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9303 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9304 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9305 * </ul></li> 9306 * </ul></li> 9307 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9308 **/ 9309 init: function (options) { 9310 this._super(options); 9311 }, 9312 9313 /** 9314 * @private 9315 * Gets the REST class for the current object - this is the Dialog class. 9316 * @returns {Object} The Dialog class. 9317 */ 9318 getRestClass: function () { 9319 return Dialog; 9320 }, 9321 9322 /** 9323 * The requestId reaper timeout in ms 9324 */ 9325 REQUESTID_REAPER_TIMEOUT: 5000, 9326 9327 /** 9328 * Getter for the from address. 9329 * @returns {String} The from address. 9330 */ 9331 getFromAddress: function () { 9332 this.isLoaded(); 9333 return this.getData().fromAddress; 9334 }, 9335 9336 /** 9337 * Getter for the to address. 9338 * @returns {String} The to address. 9339 */ 9340 getToAddress: function () { 9341 this.isLoaded(); 9342 return this.getData().toAddress; 9343 }, 9344 9345 /** 9346 * Getter for the callback number without prefix. 9347 * This is required to schedule a callback if there is any dialer prefix added for direct_preview outbound calls 9348 * @returns {String} The callback number.undefined if callbackNumber is not available 9349 */ 9350 getCallbackNumber: function () { 9351 this.isLoaded(); 9352 return this.getData().callbackNumber; 9353 }, 9354 9355 /** 9356 * Getter for the secondaryId of a dialog. 9357 * A CONSULT call has two call legs (primary leg and a consult leg). 9358 * As the CONSULT call is completed (either with TRANSFER or CONFERENCE), call legs would be merged. 9359 * The surviving call's Dialog will contain the dropped call's Dialog Id in secondaryId field. 9360 * For CCE deployments, DIRECT_TRANSFER also have the secondaryId populated as mentioned above. 9361 * @returns {String} The id of the secondary dialog. 9362 * @since 11.6(1)-ES1 onwards 9363 */ 9364 getSecondaryId: function () { 9365 this.isLoaded(); 9366 return this.getData().secondaryId; 9367 }, 9368 9369 /** 9370 * gets the participant timer counters 9371 * 9372 * @param {String} participantExt Extension of participant. 9373 * @returns {Object} Array of Participants which contains properties :<ul> 9374 * <li>state - The state of the Participant. 9375 * <li>startTime - The start Time of the Participant. 9376 * <li>stateChangeTime - The time when participant state has changed.</ul> 9377 */ 9378 getParticipantTimerCounters : function (participantExt) { 9379 var part, participantTimerCounters = {}, idx, participants; 9380 9381 participants = this.getParticipants(); 9382 9383 9384 //Loop through all the participants and find the right participant (based on participantExt) 9385 for(idx=0;idx<participants.length;idx=idx+1) 9386 { 9387 part = participants[idx]; 9388 9389 if (part.mediaAddress === participantExt) 9390 { 9391 participantTimerCounters.startTime= part.startTime; 9392 participantTimerCounters.stateChangeTime= part.stateChangeTime; 9393 participantTimerCounters.state= part.state; 9394 break; 9395 } 9396 } 9397 9398 return participantTimerCounters; 9399 }, 9400 9401 /** 9402 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 9403 * (It is not a CTI Route Point, IVR Port, or the caller) 9404 * 9405 * @param {String} filterExtension used to remove a single extension from the list 9406 * @returns {Object} Array of Participants that can be dropped. 9407 * Participant entity properties are as follows:<ul> 9408 * <li>state - The state of the Participant. 9409 * <li>stateCause - The state cause of the Participant. 9410 * <li>mediaAddress - The media address of the Participant. 9411 * <li>startTime - The start Time of the Participant. 9412 * <li>stateChangeTime - The time when participant state has changed. 9413 * <li>actions - These are the actions that a Participant can perform</ul> 9414 */ 9415 getDroppableParticipants: function (filterExtension) { 9416 this.isLoaded(); 9417 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 9418 9419 participants = this.getParticipants(); 9420 9421 if (filterExtension) 9422 { 9423 filterExtensionToRemove = filterExtension; 9424 } 9425 9426 //Loop through all the participants to remove non-agents & remove filterExtension 9427 //We could have removed filterExtension using splice, but we have to iterate through 9428 //the list anyway. 9429 for(idx=0;idx<participants.length;idx=idx+1) 9430 { 9431 part = participants[idx]; 9432 9433 //Skip the filterExtension 9434 if (part.mediaAddress !== filterExtensionToRemove) 9435 { 9436 callStateOk = this._isParticipantStateDroppable(part); 9437 9438 //Remove non-agents & make sure callstate 9439 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 9440 { 9441 droppableParticipants.push(part); 9442 } 9443 } 9444 } 9445 9446 return Utilities.getArray(droppableParticipants); 9447 }, 9448 9449 _isParticipantStateDroppable : function (part) 9450 { 9451 var isParticipantStateDroppable = false; 9452 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 9453 { 9454 isParticipantStateDroppable = true; 9455 } 9456 9457 return isParticipantStateDroppable; 9458 }, 9459 9460 /** 9461 * Is the participant droppable 9462 * 9463 * @param {String} participantExt Extension of participant. 9464 * @returns {Boolean} True is droppable. 9465 */ 9466 isParticipantDroppable : function (participantExt) { 9467 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 9468 9469 droppableParticipants = this.getDroppableParticipants(); 9470 9471 if (droppableParticipants) 9472 { 9473 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 9474 { 9475 part = droppableParticipants[idx]; 9476 9477 if (part.mediaAddress === participantExt) 9478 { 9479 callStateOk = this._isParticipantStateDroppable(part); 9480 9481 //Remove non-agents & make sure callstate 9482 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 9483 { 9484 isDroppable = true; 9485 break; 9486 } 9487 } 9488 } 9489 } 9490 9491 return isDroppable; 9492 }, 9493 9494 /** 9495 * Retrieves information about the currently scheduled callback, if any. 9496 * @returns {Object} If no callback has been set, will return undefined. If 9497 * a callback has been set, it will return a map with one or more of the 9498 * following entries, depending on what values have been set. 9499 * callbackTime - the callback time, if it has been set. 9500 * callbackNumber - the callback number, if it has been set. 9501 */ 9502 getCallbackInfo: function() { 9503 this.isLoaded(); 9504 return this.getData().scheduledCallbackInfo; 9505 }, 9506 9507 /** 9508 * Invoke a consult call out to a destination. 9509 * 9510 * @param {String} mediaAddress 9511 * The media address of the user performing the consult call. 9512 * @param {String} toAddress 9513 * The destination address of the consult call. 9514 * @param {finesse.interfaces.RequestHandlers} handlers 9515 * An object containing the handlers for the request 9516 */ 9517 makeConsultCall: function (mediaAddress, toAddress, handlers) { 9518 this.isLoaded(); 9519 var contentBody = {}; 9520 contentBody[this.getRestType()] = { 9521 "targetMediaAddress": mediaAddress, 9522 "toAddress": toAddress, 9523 "requestedAction": Dialog.Actions.CONSULT_CALL 9524 }; 9525 this._makeRequest(contentBody, handlers); 9526 return this; // Allow cascading 9527 }, 9528 9529 /** 9530 * Invoke a single step transfer request. 9531 * 9532 * @param {String} mediaAddress 9533 * The media address of the user performing the single step transfer. 9534 * @param {String} toAddress 9535 * The destination address of the single step transfer. 9536 * @param {finesse.interfaces.RequestHandlers} handlers 9537 * An object containing the handlers for the request 9538 */ 9539 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 9540 this.isLoaded(); 9541 var contentBody = {}; 9542 contentBody[this.getRestType()] = { 9543 "targetMediaAddress": mediaAddress, 9544 "toAddress": toAddress, 9545 "requestedAction": Dialog.Actions.TRANSFER_SST 9546 }; 9547 this._makeRequest(contentBody, handlers); 9548 return this; // Allow cascading 9549 }, 9550 9551 /** 9552 * Update this dialog's wrap-up reason. 9553 * 9554 * @param {String} wrapUpReason 9555 * The new wrap-up reason for this dialog 9556 * @param {finesse.interfaces.RequestHandlers} handlers 9557 * An object containing the handlers for the request 9558 */ 9559 updateWrapUpReason: function (wrapUpItems, options) 9560 { 9561 this.isLoaded(); 9562 var mediaProperties = {}; 9563 if (window.finesse.container.Config.deploymentType === 'UCCX') { 9564 mediaProperties = { 9565 "wrapUpItems": {wrapUpItem: wrapUpItems} 9566 } ; 9567 } else { 9568 mediaProperties = { 9569 "wrapUpReason": wrapUpItems 9570 }; 9571 } 9572 9573 options = options || {}; 9574 options.content = {}; 9575 options.content[this.getRestType()] = 9576 { 9577 "mediaProperties": mediaProperties, 9578 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 9579 }; 9580 options.method = "PUT"; 9581 this.restRequest(this.getRestUrl(), options); 9582 9583 return this; 9584 }, 9585 9586 /** 9587 * Invoke a request to server based on the action given. 9588 * @param {String} mediaAddress 9589 * The media address of the user performing the action. 9590 * @param {finesse.restservices.Dialog.Actions} action 9591 * The action string indicating the action to invoke on dialog. 9592 * @param {finesse.interfaces.RequestHandlers} handlers 9593 * An object containing the handlers for the request 9594 */ 9595 requestAction: function (mediaAddress, action, handlers) { 9596 this.isLoaded(); 9597 var contentBody = {}; 9598 contentBody[this.getRestType()] = { 9599 "targetMediaAddress": mediaAddress, 9600 "requestedAction": action 9601 }; 9602 this._makeRequest(contentBody, handlers); 9603 return this; // Allow cascading 9604 }, 9605 9606 /** 9607 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 9608 * 9609 * @param targetMediaAddress is the address to drop 9610 * @param {finesse.interfaces.RequestHandlers} handlers 9611 * An object containing the handlers for the request 9612 */ 9613 dropParticipant: function (targetMediaAddress, handlers) { 9614 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 9615 }, 9616 9617 /** 9618 * Invoke a request to server to send DTMF digit tones. 9619 * @param {String} mediaAddress 9620 * @param {finesse.interfaces.RequestHandlers} handlers 9621 * An object containing the handlers for the request 9622 * @param {String} digit 9623 * The digit which causes invocation of an action on the dialog. 9624 */ 9625 sendDTMFRequest: function (mediaAddress, handlers, digit) { 9626 this.isLoaded(); 9627 var contentBody = {}; 9628 contentBody[this.getRestType()] = { 9629 "targetMediaAddress": mediaAddress, 9630 "requestedAction": "SEND_DTMF", 9631 "actionParams": { 9632 "ActionParam": { 9633 "name": "dtmfString", 9634 "value": digit 9635 } 9636 } 9637 }; 9638 this._makeRequest(contentBody, handlers); 9639 return this; // Allow cascading 9640 }, 9641 9642 /** 9643 * Invoke a request to server to set the time for a callback. 9644 * @param {String} mediaAddress 9645 * @param {String} callbackTime 9646 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 9647 * (ex: 2013-12-24T23:59) 9648 * @param {finesse.interfaces.RequestHandlers} handlers 9649 * An object containing the handlers for the request 9650 */ 9651 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 9652 this.isLoaded(); 9653 var contentBody = {}; 9654 contentBody[this.getRestType()] = { 9655 "targetMediaAddress": mediaAddress, 9656 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 9657 "actionParams": { 9658 "ActionParam": { 9659 "name": "callbackTime", 9660 "value": callbackTime 9661 } 9662 } 9663 }; 9664 this._makeRequest(contentBody, handlers); 9665 return this; // Allow cascading 9666 }, 9667 9668 /** 9669 * Invoke a request to server to set the number for a callback. 9670 * @param {String} mediaAddress 9671 * @param {String} callbackNumber 9672 * The requested number to call for the callback 9673 * @param {finesse.interfaces.RequestHandlers} handlers 9674 * An object containing the handlers for the request 9675 */ 9676 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 9677 this.isLoaded(); 9678 var contentBody = {}; 9679 contentBody[this.getRestType()] = { 9680 "targetMediaAddress": mediaAddress, 9681 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 9682 "actionParams": { 9683 "ActionParam": { 9684 "name": "callbackNumber", 9685 "value": callbackNumber 9686 } 9687 } 9688 }; 9689 this._makeRequest(contentBody, handlers); 9690 return this; // Allow cascading 9691 }, 9692 9693 /** 9694 * Invoke a request to server to cancel a callback. 9695 * @param {String} mediaAddress 9696 * @param {finesse.interfaces.RequestHandlers} handlers 9697 * An object containing the handlers for the request 9698 */ 9699 cancelCallback: function (mediaAddress, handlers) { 9700 this.isLoaded(); 9701 var contentBody = {}; 9702 contentBody[this.getRestType()] = { 9703 "targetMediaAddress": mediaAddress, 9704 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 9705 }; 9706 this._makeRequest(contentBody, handlers); 9707 return this; // Allow cascading 9708 }, 9709 9710 /** 9711 * Invoke a request to server to reclassify the call type. 9712 * @param {String} mediaAddress 9713 * The media address of the user performing the consult call. 9714 * @param {String} classification 9715 * The classification to assign to the call. Valid values are "VOICE", "FAX", 9716 * "ANS_MACHINE", "INVALID", "BUSY" (CCX only), and "DO_NOT_CALL". 9717 * @param {finesse.interfaces.RequestHandlers} handlers 9718 * An object containing the handlers for the request 9719 */ 9720 reclassifyCall: function (mediaAddress, classification, handlers) { 9721 this.isLoaded(); 9722 var contentBody = {}; 9723 contentBody[this.getRestType()] = { 9724 "targetMediaAddress": mediaAddress, 9725 "requestedAction": Dialog.Actions.RECLASSIFY, 9726 "actionParams": { 9727 "ActionParam": { 9728 "name": "outboundClassification", 9729 "value": classification 9730 } 9731 } 9732 }; 9733 this._makeRequest(contentBody, handlers); 9734 return this; // Allow cascading 9735 }, 9736 9737 /** 9738 * Utility method to create a closure containing the requestId and the Dialogs object so 9739 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 9740 * @param {String} requestId The requestId of the event 9741 * @return {Function} The function to be executed by setTimeout 9742 */ 9743 _createRequestIdReaper: function (requestId) { 9744 var that = this; 9745 return function () { 9746 that._logger.log("Dialog: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 9747 delete that._pendingCallbacks[requestId]; 9748 }; 9749 }, 9750 9751 /** 9752 * Overriding implementation of the one in RestBase.js 9753 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 9754 * @param {String} requestId The requestId of the event 9755 */ 9756 _postProcessUpdateStrategy: function (requestId) { 9757 this._logger.log("Dialog: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9758 var callbacksObj = this._pendingCallbacks[requestId]; 9759 if (callbacksObj && !callbacksObj.used) { 9760 this._logger.log("Dialog: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9761 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 9762 callbacksObj.used = true; 9763 } 9764 } 9765 9766 }); 9767 9768 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 9769 /** 9770 * Drops the Participant from the Dialog. 9771 */ 9772 DROP: "DROP", 9773 /** 9774 * Answers a Dialog. 9775 */ 9776 ANSWER: "ANSWER", 9777 /** 9778 * Holds the Dialog. 9779 */ 9780 HOLD: "HOLD", 9781 /** 9782 * Barges into a Call Dialog. 9783 */ 9784 BARGE_CALL: "BARGE_CALL", 9785 /** 9786 * Allow as Supervisor to Drop a Participant from the Dialog. 9787 */ 9788 PARTICIPANT_DROP: "PARTICIPANT_DROP", 9789 /** 9790 * Makes a new Call Dialog. 9791 */ 9792 MAKE_CALL: "MAKE_CALL", 9793 /** 9794 * Retrieves a Dialog that is on Hold. 9795 */ 9796 RETRIEVE: "RETRIEVE", 9797 /** 9798 * Sets the time or number for a callback. Can be 9799 * either a new callback, or updating an existing one. 9800 */ 9801 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 9802 /** 9803 * Cancels a callback. 9804 */ 9805 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 9806 /** 9807 * Initiates a Consult Call. 9808 */ 9809 CONSULT_CALL: "CONSULT_CALL", 9810 /** 9811 * Initiates a Transfer of a Dialog. 9812 */ 9813 TRANSFER: "TRANSFER", 9814 /** 9815 * Initiates a Single-Step Transfer of a Dialog. 9816 */ 9817 TRANSFER_SST: "TRANSFER_SST", 9818 /** 9819 * Initiates a Conference of a Dialog. 9820 */ 9821 CONFERENCE: "CONFERENCE", 9822 /** 9823 * Changes classification for a call 9824 */ 9825 RECLASSIFY: "RECLASSIFY", 9826 /** 9827 * Updates data on a Call Dialog. 9828 */ 9829 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 9830 /** 9831 * Initiates a Recording on a Call Dialog. 9832 */ 9833 START_RECORDING : "START_RECORDING", 9834 /** 9835 * Sends DTMF (dialed digits) to a Call Dialog. 9836 */ 9837 DTMF : "SEND_DTMF", 9838 /** 9839 * Accepts a Dialog that is being Previewed. 9840 */ 9841 ACCEPT: "ACCEPT", 9842 /** 9843 * Rejects a Dialog. 9844 */ 9845 REJECT: "REJECT", 9846 /** 9847 * Closes a Dialog. 9848 */ 9849 CLOSE : "CLOSE", 9850 /** 9851 * @class Set of action constants for a Dialog. These should be used for 9852 * {@link finesse.restservices.Dialog#requestAction}. 9853 * @constructs 9854 */ 9855 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9856 }; 9857 9858 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 9859 /** 9860 * Indicates that the call is ringing at a device. 9861 */ 9862 ALERTING: "ALERTING", 9863 /** 9864 * Indicates that the phone is off the hook at a device. 9865 */ 9866 INITIATING: "INITIATING", 9867 /** 9868 * Indicates that the dialog has a least one active participant. 9869 */ 9870 ACTIVE: "ACTIVE", 9871 /** 9872 * Indicates that the dialog has no active participants. 9873 */ 9874 DROPPED: "DROPPED", 9875 /** 9876 * Indicates that the phone is dialing at the device. 9877 */ 9878 INITIATED: "INITIATED", 9879 /** 9880 * Indicates that the dialog has failed. 9881 * @see Dialog.ReasonStates 9882 */ 9883 FAILED: "FAILED", 9884 /** 9885 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 9886 */ 9887 ACCEPTED: "ACCEPTED", 9888 /** 9889 * @class Possible Dialog State constants. 9890 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 9891 * @constructs 9892 */ 9893 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9894 }; 9895 9896 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 9897 /** 9898 * Indicates that an incoming call is ringing on the device. 9899 */ 9900 ALERTING: "ALERTING", 9901 /** 9902 * Indicates that an outgoing call, not yet active, exists on the device. 9903 */ 9904 INITIATING: "INITIATING", 9905 /** 9906 * Indicates that the participant is active on the call. 9907 */ 9908 ACTIVE: "ACTIVE", 9909 /** 9910 * Indicates that the participant has dropped from the call. 9911 */ 9912 DROPPED: "DROPPED", 9913 /** 9914 * Indicates that the participant has held their connection to the call. 9915 */ 9916 HELD: "HELD", 9917 /** 9918 * Indicates that the phone is dialing at a device. 9919 */ 9920 INITIATED: "INITIATED", 9921 /** 9922 * Indicates that the call failed. 9923 * @see Dialog.ReasonStates 9924 */ 9925 FAILED: "FAILED", 9926 /** 9927 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 9928 */ 9929 WRAP_UP: "WRAP_UP", 9930 /** 9931 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 9932 */ 9933 ACCEPTED: "ACCEPTED", 9934 /** 9935 * @class Possible Dialog Participant State constants. 9936 * @constructs 9937 */ 9938 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9939 }; 9940 9941 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 9942 /** 9943 * Dialog was Busy. This will typically be for a Failed Dialog. 9944 */ 9945 BUSY: "BUSY", 9946 /** 9947 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 9948 */ 9949 BAD_DESTINATION: "BAD_DESTINATION", 9950 /** 9951 * All Other Reasons. This will typically be for a Failed Dialog. 9952 */ 9953 OTHER: "OTHER", 9954 /** 9955 * The Device Resource for the Dialog was not available. 9956 */ 9957 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 9958 /** 9959 * @class Possible dialog state reasons code constants. 9960 * @constructs 9961 */ 9962 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9963 }; 9964 9965 window.finesse = window.finesse || {}; 9966 window.finesse.restservices = window.finesse.restservices || {}; 9967 window.finesse.restservices.Dialog = Dialog; 9968 9969 9970 return Dialog; 9971 }); 9972 9973 /** 9974 * JavaScript representation of the Finesse Dialogs collection 9975 * object which contains a list of Dialog objects. 9976 * 9977 * @requires finesse.clientservices.ClientServices 9978 * @requires Class 9979 * @requires finesse.FinesseBase 9980 * @requires finesse.restservices.RestBase 9981 * @requires finesse.restservices.Dialog 9982 */ 9983 /** @private */ 9984 define('restservices/Dialogs',[ 9985 'restservices/RestCollectionBase', 9986 'restservices/Dialog' 9987 ], 9988 function (RestCollectionBase, Dialog) { 9989 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 9990 9991 /** 9992 * if includeNonVoice is true then voice and non-voice both the dialogs will be loaded 9993 */ 9994 includeNonVoice: false, 9995 /** 9996 * @class 9997 * JavaScript representation of a Dialogs collection object. Also exposes 9998 * methods to operate on the object against the server. 9999 * @augments finesse.restservices.RestCollectionBase 10000 * @constructs 10001 * @see finesse.restservices.Dialog 10002 * @example 10003 * _dialogs = _user.getDialogs( { 10004 * onCollectionAdd : _handleDialogAdd, 10005 * onCollectionDelete : _handleDialogDelete, 10006 * onLoad : _handleDialogsLoaded 10007 * }); 10008 * 10009 * _dialogCollection = _dialogs.getCollection(); 10010 * for (var dialogId in _dialogCollection) { 10011 * if (_dialogCollection.hasOwnProperty(dialogId)) { 10012 * _dialog = _dialogCollection[dialogId]; 10013 * etc... 10014 * } 10015 * } 10016 */ 10017 _fakeConstuctor: function () { 10018 /* This is here to hide the real init constructor from the public docs */ 10019 }, 10020 10021 /** 10022 * @private 10023 * @param {Object} options 10024 * An object with the following properties:<ul> 10025 * <li><b>id:</b> The id of the object being constructed</li> 10026 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10027 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10028 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10029 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10030 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10031 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10032 * <li><b>content:</b> {String} Raw string of response</li> 10033 * <li><b>object:</b> {Object} Parsed object of response</li> 10034 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10035 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10036 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10037 * </ul></li> 10038 * </ul></li> 10039 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10040 **/ 10041 init: function (options) { 10042 this.includeNonVoice = options.includeNonVoice; 10043 this._super(options); 10044 }, 10045 10046 getRestUrlAdditionalParameters: function () { 10047 //if includeNonVoice is true then include query parameters for voice and non-voice both 10048 return this.includeNonVoice ? "type=voice&type=non-voice&" : ""; 10049 }, 10050 /** 10051 * @private 10052 * Gets the REST class for the current object - this is the Dialogs class. 10053 */ 10054 getRestClass: function () { 10055 return Dialogs; 10056 }, 10057 10058 /** 10059 * @private 10060 * Gets the REST class for the objects that make up the collection. - this 10061 * is the Dialog class. 10062 */ 10063 getRestItemClass: function () { 10064 return Dialog; 10065 }, 10066 10067 /** 10068 * @private 10069 * Gets the REST type for the current object - this is a "Dialogs". 10070 */ 10071 getRestType: function () { 10072 return "Dialogs"; 10073 }, 10074 10075 /** 10076 * @private 10077 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 10078 */ 10079 getRestItemType: function () { 10080 return "Dialog"; 10081 }, 10082 10083 /** 10084 * @private 10085 * Override default to indicates that the collection doesn't support making 10086 * requests. 10087 */ 10088 supportsRequests: true, 10089 10090 /** 10091 * @private 10092 * Override default to indicates that the collection subscribes to its objects. 10093 */ 10094 supportsRestItemSubscriptions: true, 10095 10096 /** 10097 * The requestId reaper timeout in ms 10098 */ 10099 REQUESTID_REAPER_TIMEOUT: 5000, 10100 10101 /** 10102 * @private 10103 * Create a new Dialog in this collection 10104 * 10105 * @param {String} toAddress 10106 * The to address of the new Dialog 10107 * @param {String} fromAddress 10108 * The from address of the new Dialog 10109 * @param {finesse.interfaces.RequestHandlers} handlers 10110 * An object containing the (optional) handlers for the request. 10111 * @return {finesse.restservices.Dialogs} 10112 * This Dialogs object, to allow cascading. 10113 */ 10114 createNewCallDialog: function (toAddress, fromAddress, handlers) 10115 { 10116 var contentBody = {}; 10117 contentBody[this.getRestItemType()] = { 10118 "requestedAction": "MAKE_CALL", 10119 "toAddress": toAddress, 10120 "fromAddress": fromAddress 10121 }; 10122 10123 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10124 handlers = handlers || {}; 10125 10126 this.restRequest(this.getRestUrl(), { 10127 method: 'POST', 10128 success: handlers.success, 10129 error: handlers.error, 10130 content: contentBody 10131 }); 10132 return this; // Allow cascading 10133 }, 10134 10135 /** 10136 * @private 10137 * Create a new Dialog in this collection as a result of a requested action 10138 * 10139 * @param {String} toAddress 10140 * The to address of the new Dialog 10141 * @param {String} fromAddress 10142 * The from address of the new Dialog 10143 * @param {finesse.restservices.Dialog.Actions} actionType 10144 * The associated action to request for creating this new dialog 10145 * @param {finesse.interfaces.RequestHandlers} handlers 10146 * An object containing the (optional) handlers for the request. 10147 * @return {finesse.restservices.Dialogs} 10148 * This Dialogs object, to allow cascading. 10149 */ 10150 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 10151 { 10152 var contentBody = {}; 10153 this._isLoaded = true; 10154 10155 contentBody[this.getRestItemType()] = { 10156 "requestedAction": actionType, 10157 "toAddress": toAddress, 10158 "fromAddress": fromAddress 10159 }; 10160 10161 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10162 handlers = handlers || {}; 10163 10164 this.restRequest(this.getRestUrl(), { 10165 method: 'POST', 10166 success: handlers.success, 10167 error: handlers.error, 10168 content: contentBody 10169 }); 10170 return this; // Allow cascading 10171 }, 10172 10173 /** 10174 * @private 10175 * Create a new Dialog in this collection as a result of a requested action 10176 * @param {String} fromAddress 10177 * The from address of the new Dialog 10178 * @param {String} toAddress 10179 * The to address of the new Dialog 10180 * @param {finesse.restservices.Dialog.Actions} actionType 10181 * The associated action to request for creating this new dialog 10182 * @param {String} dialogUri 10183 * The associated uri of SUPERVISOR_MONITOR call 10184 * @param {finesse.interfaces.RequestHandlers} handlers 10185 * An object containing the (optional) handlers for the request. 10186 * @return {finesse.restservices.Dialogs} 10187 * This Dialogs object, to allow cascading. 10188 */ 10189 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 10190 this.isLoaded(); 10191 10192 var contentBody = {}; 10193 contentBody[this.getRestItemType()] = { 10194 "fromAddress": fromAddress, 10195 "toAddress": toAddress, 10196 "requestedAction": actionType, 10197 "associatedDialogUri": dialogURI 10198 10199 }; 10200 // (nonexistent) keys to be read as undefined 10201 handlers = handlers || {}; 10202 this.restRequest(this.getRestUrl(), { 10203 method: 'POST', 10204 success: handlers.success, 10205 error: handlers.error, 10206 content: contentBody 10207 }); 10208 return this; // Allow cascading 10209 }, 10210 10211 /** 10212 * Utility method to get the number of dialogs in this collection. 10213 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 10214 * 'SUPERVISOR_MONITOR' from the count. 10215 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 10216 * @return {Number} The number of dialogs in this collection. 10217 */ 10218 getDialogCount: function (excludeSilentMonitor) { 10219 this.isLoaded(); 10220 10221 var dialogId, count = 0; 10222 if (excludeSilentMonitor) { 10223 for (dialogId in this._collection) { 10224 if (this._collection.hasOwnProperty(dialogId)) { 10225 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 10226 count += 1; 10227 } 10228 } 10229 } 10230 10231 return count; 10232 } else { 10233 return this.length; 10234 } 10235 }, 10236 10237 /** 10238 * Utility method to create a closure containing the requestId and the Dialogs object so 10239 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 10240 * @param {String} requestId The requestId of the event 10241 * @return {Function} The function to be executed by setTimeout 10242 */ 10243 _createRequestIdReaper: function (requestId) { 10244 var that = this; 10245 return function () { 10246 that._logger.log("Dialogs: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 10247 delete that._pendingCallbacks[requestId]; 10248 }; 10249 }, 10250 10251 /** 10252 * Overriding implementation of the one in RestBase.js 10253 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 10254 * @param {String} requestId The requestId of the event 10255 */ 10256 _postProcessUpdateStrategy: function (requestId) { 10257 this._logger.log("Dialogs: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 10258 var callbacksObj = this._pendingCallbacks[requestId]; 10259 if (callbacksObj && !callbacksObj.used) { 10260 this._logger.log("Dialogs: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 10261 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 10262 callbacksObj.used = true; 10263 } 10264 } 10265 10266 }); 10267 10268 window.finesse = window.finesse || {}; 10269 window.finesse.restservices = window.finesse.restservices || {}; 10270 window.finesse.restservices.Dialogs = Dialogs; 10271 10272 return Dialogs; 10273 }); 10274 10275 /** 10276 * JavaScript representation of the Finesse ClientLog object 10277 * 10278 * @requires finesse.clientservices.ClientServices 10279 * @requires Class 10280 * @requires finesse.FinesseBase 10281 * @requires finesse.restservices.RestBase 10282 */ 10283 10284 /** The following comment is to prevent jslint errors about 10285 * using variables before they are defined. 10286 */ 10287 /** @private */ 10288 /*global finesse*/ 10289 10290 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 10291 10292 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 10293 /** 10294 * @private 10295 * Returns whether this object supports transport logs 10296 */ 10297 doNotLog : true, 10298 10299 explicitSubscription : true, 10300 10301 /** 10302 * @class 10303 * @private 10304 * JavaScript representation of a ClientLog object. Also exposes methods to operate 10305 * on the object against the server. 10306 * 10307 * @param {Object} options 10308 * An object with the following properties:<ul> 10309 * <li><b>id:</b> The id of the object being constructed</li> 10310 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10311 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10312 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10313 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10314 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10315 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10316 * <li><b>content:</b> {String} Raw string of response</li> 10317 * <li><b>object:</b> {Object} Parsed object of response</li> 10318 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10319 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10320 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10321 * </ul></li> 10322 * </ul></li> 10323 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10324 * @constructs 10325 * @augments finesse.restservices.RestBase 10326 **/ 10327 init: function (options) { 10328 this._super({ 10329 id: "", 10330 data: {clientLog : null}, 10331 onAdd: options.onAdd, 10332 onChange: options.onChange, 10333 onLoad: options.onLoad, 10334 onError: options.onError, 10335 onDelete: options.onDelete, 10336 parentObj: options.parentObj 10337 }); 10338 }, 10339 10340 /** 10341 * @private 10342 * Gets the REST class for the current object - this is the ClientLog object. 10343 */ 10344 getRestClass: function () { 10345 return ClientLog; 10346 }, 10347 10348 /** 10349 * @private 10350 * Gets the REST type for the current object - this is a "ClientLog". 10351 */ 10352 getRestType: function () 10353 { 10354 return "ClientLog"; 10355 }, 10356 10357 /** 10358 * @private 10359 * Gets the node path for the current object 10360 * @returns {String} The node path 10361 */ 10362 getXMPPNodePath: function () { 10363 return this.getRestUrl(); 10364 }, 10365 10366 /** 10367 * @private 10368 * Utility method to fetch this object from the server, however we 10369 * override it for ClientLog to not do anything because GET is not supported 10370 * for ClientLog object. 10371 */ 10372 _doGET: function (handlers) { 10373 return; 10374 }, 10375 10376 /** 10377 * @private 10378 * Invoke a request to the server given a content body and handlers. 10379 * 10380 * @param {Object} contentBody 10381 * A JS object containing the body of the action request. 10382 * @param {Object} handlers 10383 * An object containing the following (optional) handlers for the request:<ul> 10384 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 10385 * response object as its only parameter:<ul> 10386 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10387 * <li><b>content:</b> {String} Raw string of response</li> 10388 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 10389 * <li>A error callback function for an unsuccessful request to be invoked with the 10390 * error response object as its only parameter:<ul> 10391 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10392 * <li><b>content:</b> {String} Raw string of response</li> 10393 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 10394 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10395 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10396 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10397 * </ul></li> 10398 * </ul> 10399 */ 10400 sendLogs: function (contentBody, handlers) { 10401 // Protect against null dereferencing of options allowing its 10402 // (nonexistent) keys to be read as undefined 10403 handlers = handlers || {}; 10404 10405 if (handlers && handlers.isZip) { 10406 var postdata = new FormData(); 10407 postdata.append("file", contentBody); 10408 this.restRequest(this.getRestUrl().replace('ClientLog', 'CompressedClientLog'), { 10409 method: 'POST', 10410 success: handlers.success, 10411 error: handlers.error, 10412 content: postdata, 10413 contentType: 'multipart' 10414 }); 10415 } else { 10416 this.restRequest(this.getRestUrl(), { 10417 method: 'POST', 10418 //success: handlers.success, 10419 error: handlers.error, 10420 content: contentBody 10421 }); 10422 } 10423 10424 } 10425 }); 10426 10427 window.finesse = window.finesse || {}; 10428 window.finesse.restservices = window.finesse.restservices || {}; 10429 window.finesse.restservices.ClientLog = ClientLog; 10430 10431 return ClientLog; 10432 }); 10433 10434 /** 10435 * JavaScript representation of the Finesse Queue object 10436 * @requires finesse.clientservices.ClientServices 10437 * @requires Class 10438 * @requires finesse.FinesseBase 10439 * @requires finesse.restservices.RestBase 10440 */ 10441 10442 /** @private */ 10443 define('restservices/Queue',[ 10444 'restservices/RestBase', 10445 'utilities/Utilities' 10446 ], 10447 function (RestBase, Utilities) { 10448 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 10449 10450 /** 10451 * @class 10452 * The Queue object represents a queue with uri, queue name, and statistics for that queue. 10453 * 10454 * @augments finesse.restservices.RestBase 10455 * @constructs 10456 */ 10457 _fakeConstuctor: function () { 10458 /* This is here to hide the real init constructor from the public docs */ 10459 }, 10460 10461 /** 10462 * @private 10463 * JavaScript representation of a Queue object. Also exposes methods to operate 10464 * on the object against the server. 10465 * 10466 * @constructor 10467 * @param {String} id 10468 * Not required... 10469 * @param {Object} callbacks 10470 * An object containing callbacks for instantiation and runtime 10471 * @param {Function} callbacks.onLoad(this) 10472 * Callback to invoke upon successful instantiation 10473 * @param {Function} callbacks.onLoadError(rsp) 10474 * Callback to invoke on instantiation REST request error 10475 * as passed by finesse.clientservices.ClientServices.ajax() 10476 * { 10477 * status: {Number} The HTTP status code returned 10478 * content: {String} Raw string of response 10479 * object: {Object} Parsed object of response 10480 * error: {Object} Wrapped exception that was caught 10481 * error.errorType: {String} Type of error that was caught 10482 * error.errorMessage: {String} Message associated with error 10483 * } 10484 * @param {Function} callbacks.onChange(this) 10485 * Callback to invoke upon successful update 10486 * @param {Function} callbacks.onError(rsp) 10487 * Callback to invoke on update error (refresh or event) 10488 * as passed by finesse.clientservices.ClientServices.ajax() 10489 * { 10490 * status: {Number} The HTTP status code returned 10491 * content: {String} Raw string of response 10492 * object: {Object} Parsed object of response 10493 * error: {Object} Wrapped exception that was caught 10494 * error.errorType: {String} Type of error that was caught 10495 * error.errorMessage: {String} Message associated with error 10496 * } 10497 * 10498 */ 10499 init: function (id, callbacks, restObj) { 10500 this._super(id, callbacks, restObj); 10501 }, 10502 10503 /** 10504 * @private 10505 * Gets the REST class for the current object - this is the Queue object. 10506 * @returns {Object} The Queue class. 10507 */ 10508 getRestClass: function () { 10509 return Queue; 10510 }, 10511 10512 /** 10513 * @private 10514 * Gets the REST type for the current object - this is a "Queue". 10515 * @returns {String} The Queue string. 10516 */ 10517 getRestType: function () { 10518 return "Queue"; 10519 }, 10520 10521 /** 10522 * @private 10523 * Returns whether this object supports subscriptions 10524 * @returns {Boolean} 10525 */ 10526 supportsSubscriptions: function () { 10527 return true; 10528 }, 10529 10530 /** 10531 * @private 10532 * Specifies whether this object's subscriptions need to be explicitly requested 10533 */ 10534 explicitSubscription: true, 10535 10536 /** 10537 * @private 10538 * Gets the node path for the current object - this is the team Users node 10539 * @returns {String} The node path 10540 */ 10541 getXMPPNodePath: function () { 10542 return this.getRestUrl(); 10543 }, 10544 10545 /** 10546 * Getter for the queue id 10547 * @returns {String} 10548 * The id of the Queue 10549 */ 10550 getId: function () { 10551 this.isLoaded(); 10552 return this._id; 10553 }, 10554 10555 /** 10556 * Getter for the queue name 10557 * @returns {String} 10558 * The name of the Queue 10559 */ 10560 getName: function () { 10561 this.isLoaded(); 10562 return this.getData().name; 10563 }, 10564 10565 /** 10566 * Getter for the queue statistics. 10567 * Supported statistics include:<br> 10568 * - agentsBusyOther<br> 10569 * - agentsLoggedOn<br> 10570 * - agentsNotReady<br> 10571 * - agentsReady<br> 10572 * - agentsTalkingInbound<br> 10573 * - agentsTalkingInternal<br> 10574 * - agentsTalkingOutbound<br> 10575 * - agentsWrapUpNotReady<br> 10576 * - agentsWrapUpReady<br> 10577 * - callsInQueue<br> 10578 * - startTimeOfLongestCallInQueue<br> 10579 * <br> 10580 * These statistics can be accessed via dot notation:<br> 10581 * i.e.: getStatistics().callsInQueue 10582 * @returns {Object} 10583 * The Object with different statistics as properties. 10584 */ 10585 getStatistics: function () { 10586 this.isLoaded(); 10587 return this.getData().statistics; 10588 }, 10589 10590 /** 10591 * Parses a uriString to retrieve the id portion 10592 * @param {String} uriString 10593 * @return {String} id 10594 */ 10595 _parseIdFromUriString : function (uriString) { 10596 return Utilities.getId(uriString); 10597 } 10598 10599 }); 10600 10601 window.finesse = window.finesse || {}; 10602 window.finesse.restservices = window.finesse.restservices || {}; 10603 window.finesse.restservices.Queue = Queue; 10604 10605 return Queue; 10606 }); 10607 10608 /** 10609 * JavaScript representation of the Finesse Queues collection 10610 * object which contains a list of Queue objects. 10611 * @requires finesse.clientservices.ClientServices 10612 * @requires Class 10613 * @requires finesse.FinesseBase 10614 * @requires finesse.restservices.RestBase 10615 * @requires finesse.restservices.RestCollectionBase 10616 */ 10617 10618 /** 10619 * @class 10620 * JavaScript representation of a Queues collection object. 10621 * 10622 * @constructor 10623 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 10624 */ 10625 10626 /** @private */ 10627 define('restservices/Queues',[ 10628 'restservices/RestCollectionBase', 10629 'restservices/Queue' 10630 ], 10631 function (RestCollectionBase, Queue) { 10632 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 10633 10634 /** 10635 * @class 10636 * JavaScript representation of a Queues collection object. 10637 * @augments finesse.restservices.RestCollectionBase 10638 * @constructs 10639 * @see finesse.restservices.Queue 10640 * @example 10641 * _queues = _user.getQueues( { 10642 * onCollectionAdd : _handleQueueAdd, 10643 * onCollectionDelete : _handleQueueDelete, 10644 * onLoad : _handleQueuesLoaded 10645 * }); 10646 * 10647 * _queueCollection = _queues.getCollection(); 10648 * for (var queueId in _queueCollection) { 10649 * if (_queueCollection.hasOwnProperty(queueId)) { 10650 * _queue = _queueCollection[queueId]; 10651 * etc... 10652 * } 10653 * } 10654 */ 10655 _fakeConstuctor: function () { 10656 /* This is here to hide the real init constructor from the public docs */ 10657 }, 10658 10659 /** 10660 * @private 10661 * JavaScript representation of a Queues object. Also exposes 10662 * methods to operate on the object against the server. 10663 * 10664 * @param {Object} options 10665 * An object with the following properties:<ul> 10666 * <li><b>id:</b> The id of the object being constructed</li> 10667 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10668 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10669 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10670 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10671 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10672 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10673 * <li><b>content:</b> {String} Raw string of response</li> 10674 * <li><b>object:</b> {Object} Parsed object of response</li> 10675 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10676 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10677 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10678 * </ul></li> 10679 * </ul></li> 10680 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10681 **/ 10682 init: function (options) { 10683 this._super(options); 10684 }, 10685 10686 /** 10687 * @private 10688 * Gets xmpp node path. 10689 * @returns {String} The node path 10690 */ 10691 getXMPPNodePath: function () { 10692 return this.getRestUrl(); 10693 }, 10694 10695 /** 10696 * @private 10697 * Gets the REST class for the current object - this is the Queues class. 10698 * @returns {Object} The Queues class. 10699 */ 10700 getRestClass: function () { 10701 return Queues; 10702 }, 10703 10704 /** 10705 * @private 10706 * Gets the REST class for the objects that make up the collection. - this 10707 * is the Queue class. 10708 * @returns {finesse.restservices.Queue} 10709 * The Queue class 10710 * @see finesse.restservices.Queue 10711 */ 10712 getRestItemClass: function () { 10713 return Queue; 10714 }, 10715 10716 /** 10717 * @private 10718 * Gets the REST type for the current object - this is a "Queues". 10719 * @returns {String} The Queues String 10720 */ 10721 getRestType: function () { 10722 return "Queues"; 10723 }, 10724 10725 /** 10726 * @private 10727 * Gets the REST type for the objects that make up the collection - this is "Queue". 10728 * @returns {String} The Queue String 10729 */ 10730 getRestItemType: function () { 10731 return "Queue"; 10732 }, 10733 10734 explicitSubscription: true, 10735 10736 handlesItemRefresh: true 10737 }); 10738 10739 window.finesse = window.finesse || {}; 10740 window.finesse.restservices = window.finesse.restservices || {}; 10741 window.finesse.restservices.Queues = Queues; 10742 10743 return Queues; 10744 }); 10745 10746 /** 10747 * JavaScript representation of the Finesse WrapUpReason object. 10748 * 10749 * @requires finesse.clientservices.ClientServices 10750 * @requires Class 10751 * @requires finesse.FinesseBase 10752 * @requires finesse.restservices.RestBase 10753 */ 10754 10755 /** @private */ 10756 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 10757 10758 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 10759 10760 /** 10761 * @class 10762 * A WrapUpReason is a code and description identifying a particular reason that a 10763 * User is in WORK (WrapUp) mode. 10764 * 10765 * @augments finesse.restservices.RestBase 10766 * @see finesse.restservices.User 10767 * @see finesse.restservices.User.States#WORK 10768 * @constructs 10769 */ 10770 _fakeConstuctor: function () { 10771 /* This is here to hide the real init constructor from the public docs */ 10772 }, 10773 10774 /** 10775 * @private 10776 * JavaScript representation of a WrapUpReason object. Also exposes 10777 * methods to operate on the object against the server. 10778 * 10779 * @param {Object} options 10780 * An object with the following properties:<ul> 10781 * <li><b>id:</b> The id of the object being constructed</li> 10782 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10783 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10784 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10785 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10786 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10787 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10788 * <li><b>content:</b> {String} Raw string of response</li> 10789 * <li><b>object:</b> {Object} Parsed object of response</li> 10790 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10791 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10792 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10793 * </ul></li> 10794 * </ul></li> 10795 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10796 **/ 10797 init: function (options) { 10798 this._super(options); 10799 }, 10800 10801 /** 10802 * @private 10803 * Gets the REST class for the current object - this is the WrapUpReason class. 10804 * @returns {Object} The WrapUpReason class. 10805 */ 10806 getRestClass: function () { 10807 return WrapUpReason; 10808 }, 10809 10810 /** 10811 * @private 10812 * Gets the REST type for the current object - this is a "WrapUpReason". 10813 * @returns {String} The WrapUpReason string. 10814 */ 10815 getRestType: function () { 10816 return "WrapUpReason"; 10817 }, 10818 10819 /** 10820 * @private 10821 * Gets the REST type for the current object - this is a "WrapUpReasons". 10822 * @returns {String} The WrapUpReasons string. 10823 */ 10824 getParentRestType: function () { 10825 return "WrapUpReasons"; 10826 }, 10827 10828 /** 10829 * @private 10830 * Override default to indicate that this object doesn't support making 10831 * requests. 10832 */ 10833 supportsRequests: false, 10834 10835 /** 10836 * @private 10837 * Override default to indicate that this object doesn't support subscriptions. 10838 */ 10839 supportsSubscriptions: false, 10840 10841 /** 10842 * Getter for the label. 10843 * @returns {String} The label. 10844 */ 10845 getLabel: function () { 10846 this.isLoaded(); 10847 return this.getData().label; 10848 }, 10849 10850 /** 10851 * @private 10852 * Getter for the forAll flag. 10853 * @returns {Boolean} True if global. 10854 */ 10855 getForAll: function () { 10856 this.isLoaded(); 10857 return this.getData().forAll; 10858 }, 10859 10860 /** 10861 * @private 10862 * Getter for the Uri value. 10863 * @returns {String} The Uri. 10864 */ 10865 getUri: function () { 10866 this.isLoaded(); 10867 return this.getData().uri; 10868 } 10869 }); 10870 10871 window.finesse = window.finesse || {}; 10872 window.finesse.restservices = window.finesse.restservices || {}; 10873 window.finesse.restservices.WrapUpReason = WrapUpReason; 10874 10875 return WrapUpReason; 10876 }); 10877 10878 /** 10879 * JavaScript representation of the Finesse WrapUpReasons collection 10880 * object which contains a list of WrapUpReason objects. 10881 * 10882 * @requires finesse.clientservices.ClientServices 10883 * @requires Class 10884 * @requires finesse.FinesseBase 10885 * @requires finesse.restservices.RestBase 10886 * @requires finesse.restservices.Dialog 10887 * @requires finesse.restservices.RestCollectionBase 10888 */ 10889 10890 /** @private */ 10891 define('restservices/WrapUpReasons',[ 10892 'restservices/RestCollectionBase', 10893 'restservices/WrapUpReason' 10894 ], 10895 function (RestCollectionBase, WrapUpReason) { 10896 10897 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 10898 teamId: null, 10899 /** 10900 * @class 10901 * JavaScript representation of a WrapUpReasons collection object. 10902 * @augments finesse.restservices.RestCollectionBase 10903 * @constructs 10904 * @see finesse.restservices.WrapUpReason 10905 * @example 10906 * _wrapUpReasons = _user.getWrapUpReasons ( { 10907 * onCollectionAdd : _handleWrapUpReasonAdd, 10908 * onCollectionDelete : _handleWrapUpReasonDelete, 10909 * onLoad : _handleWrapUpReasonsLoaded 10910 * }); 10911 * 10912 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 10913 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 10914 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 10915 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 10916 * etc... 10917 * } 10918 * } 10919 */ 10920 _fakeConstuctor: function () { 10921 /* This is here to hide the real init constructor from the public docs */ 10922 }, 10923 10924 /** 10925 * @private 10926 * JavaScript representation of a WrapUpReasons collection object. Also exposes 10927 * methods to operate on the object against the server. 10928 * 10929 * @param {Object} options 10930 * An object with the following properties:<ul> 10931 * <li><b>id:</b> The id of the object being constructed</li> 10932 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10933 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10934 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10935 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10936 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10937 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10938 * <li><b>content:</b> {String} Raw string of response</li> 10939 * <li><b>object:</b> {Object} Parsed object of response</li> 10940 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10941 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10942 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10943 * </ul></li> 10944 * </ul></li> 10945 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10946 **/ 10947 init: function (options) { 10948 if(options && options.teamId){ 10949 this.teamId = options.teamId; 10950 } 10951 10952 this._super(options); 10953 }, 10954 10955 getRestUrl: function () { 10956 if(this.teamId){ 10957 return this.getBaseRestUrl() + "/TeamResource/" + this.teamId + "/"+ this.getRestType(); 10958 } 10959 return this._super(); 10960 }, 10961 10962 /** 10963 * @private 10964 * Gets the REST class for the current object - this is the WrapUpReasons class. 10965 * @returns {Object} 10966 * The WrapUpReasons constructor. 10967 */ 10968 getRestClass: function () { 10969 return WrapUpReasons; 10970 }, 10971 10972 /** 10973 * @private 10974 * Gets the REST class for the objects that make up the collection. - this 10975 * is the WrapUpReason class. 10976 * @returns {finesse.restservices.WrapUpReason} 10977 * The WrapUpReason class 10978 * @see finesse.restservices.WrapUpReason 10979 */ 10980 getRestItemClass: function () { 10981 return WrapUpReason; 10982 }, 10983 10984 /** 10985 * @private 10986 * Gets the REST type for the current object - this is a "WrapUpReasons". 10987 * @returns {String} The WrapUpReasons String 10988 */ 10989 getRestType: function () { 10990 return "WrapUpReasons"; 10991 }, 10992 10993 /** 10994 * @private 10995 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 10996 * @returns {String} The WrapUpReason String 10997 */ 10998 getRestItemType: function () { 10999 return "WrapUpReason"; 11000 }, 11001 11002 /** 11003 * @private 11004 * Override default to indicates that the collection supports making 11005 * requests. 11006 */ 11007 supportsRequests: true, 11008 11009 /** 11010 * @private 11011 * Override default to indicate that this object doesn't support subscriptions. 11012 */ 11013 supportsRestItemSubscriptions: false, 11014 11015 /** 11016 * @private 11017 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 11018 * 11019 * @returns {finesse.restservices.WrapUpReasons} 11020 * This ReadyReasonCodes object to allow cascading. 11021 */ 11022 get: function () { 11023 // set loaded to false so it will rebuild the collection after the get 11024 this._loaded = false; 11025 // reset collection 11026 this._collection = {}; 11027 // perform get 11028 this._synchronize(); 11029 return this; 11030 } 11031 11032 }); 11033 11034 window.finesse = window.finesse || {}; 11035 window.finesse.restservices = window.finesse.restservices || {}; 11036 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 11037 11038 return WrapUpReasons; 11039 }); 11040 11041 /** 11042 * JavaScript representation of the Finesse Contact object. 11043 * @requires finesse.clientservices.ClientServices 11044 * @requires Class 11045 * @requires finesse.FinesseBase 11046 * @requires finesse.restservices.RestBase 11047 */ 11048 /** @private */ 11049 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 11050 11051 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 11052 11053 /** 11054 * @class 11055 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 11056 * a Phone Number, and a Description. 11057 * 11058 * @augments finesse.restservices.RestBase 11059 * @see finesse.restservices.PhoneBook 11060 * @constructs 11061 */ 11062 _fakeConstuctor: function () { 11063 /* This is here to hide the real init constructor from the public docs */ 11064 }, 11065 11066 /** 11067 * @private 11068 * @param {Object} options 11069 * An object with the following properties:<ul> 11070 * <li><b>id:</b> The id of the object being constructed</li> 11071 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11072 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11073 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11074 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11075 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11076 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11077 * <li><b>content:</b> {String} Raw string of response</li> 11078 * <li><b>object:</b> {Object} Parsed object of response</li> 11079 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11080 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11081 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11082 * </ul></li> 11083 * </ul></li> 11084 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11085 **/ 11086 init: function (options) { 11087 this._super(options); 11088 }, 11089 11090 /** 11091 * @private 11092 * Gets the REST class for the current object - this is the Contact class. 11093 * @returns {Object} The Contact class. 11094 */ 11095 getRestClass: function () { 11096 return Contact; 11097 }, 11098 11099 /** 11100 * @private 11101 * Gets the REST type for the current object - this is a "Contact". 11102 * @returns {String} The Contact string. 11103 */ 11104 getRestType: function () { 11105 return "Contact"; 11106 }, 11107 11108 /** 11109 * @private 11110 * Override default to indicate that this object doesn't support making 11111 * requests. 11112 */ 11113 supportsRequests: false, 11114 11115 /** 11116 * @private 11117 * Override default to indicate that this object doesn't support subscriptions. 11118 */ 11119 supportsSubscriptions: false, 11120 11121 /** 11122 * Method to get the firstName of the contact. 11123 * @returns {String} The firstName of the contact. 11124 * @example 11125 * restContact.getFirstName(); 11126 * 11127 */ 11128 getFirstName: function () { 11129 this.isLoaded(); 11130 return this.getData().firstName; 11131 }, 11132 11133 /** 11134 * Method to get the lastName of the contact. 11135 * @returns {String} The lastName of the contact. 11136 * @example 11137 * restContact.getLastName(); 11138 */ 11139 getLastName: function () { 11140 this.isLoaded(); 11141 return this.getData().lastName; 11142 }, 11143 11144 /** 11145 * Method to get the phone numnber of the contact. 11146 * @returns {String} The phoneNumber of the contact. 11147 * @example 11148 * restContact.getPhoneNumber(); 11149 */ 11150 getPhoneNumber: function () { 11151 this.isLoaded(); 11152 return this.getData().phoneNumber; 11153 }, 11154 11155 /** 11156 * Method to get the description of the contact. 11157 * @returns {String} The description of the contact. 11158 * @example 11159 * restContact.getDescription(); 11160 */ 11161 getDescription: function () { 11162 this.isLoaded(); 11163 return this.getData().description; 11164 }, 11165 11166 /** @private */ 11167 createPutSuccessHandler: function(contact, contentBody, successHandler){ 11168 return function (rsp) { 11169 // Update internal structure based on response. Here we 11170 // inject the contentBody from the PUT request into the 11171 // rsp.object element to mimic a GET as a way to take 11172 // advantage of the existing _processResponse method. 11173 rsp.object = contentBody; 11174 contact._processResponse(rsp); 11175 11176 //Remove the injected Contact object before cascading response 11177 rsp.object = {}; 11178 11179 //cascade response back to consumer's response handler 11180 successHandler(rsp); 11181 }; 11182 }, 11183 11184 /** @private */ 11185 createPostSuccessHandler: function (contact, contentBody, successHandler) { 11186 return function (rsp) { 11187 rsp.object = contentBody; 11188 contact._processResponse(rsp); 11189 11190 //Remove the injected Contact object before cascading response 11191 rsp.object = {}; 11192 11193 //cascade response back to consumer's response handler 11194 successHandler(rsp); 11195 }; 11196 }, 11197 11198 /** 11199 * Add 11200 * @private 11201 */ 11202 add: function (newValues, handlers) { 11203 // this.isLoaded(); 11204 var contentBody = {}; 11205 11206 contentBody[this.getRestType()] = { 11207 "firstName": newValues.firstName, 11208 "lastName": newValues.lastName, 11209 "phoneNumber": newValues.phoneNumber, 11210 "description": newValues.description 11211 }; 11212 11213 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11214 handlers = handlers || {}; 11215 11216 this.restRequest(this.getRestUrl(), { 11217 method: 'POST', 11218 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11219 error: handlers.error, 11220 content: contentBody 11221 }); 11222 11223 return this; // Allow cascading 11224 }, 11225 11226 /** 11227 * Update 11228 * @private 11229 */ 11230 update: function (newValues, handlers) { 11231 this.isLoaded(); 11232 var contentBody = {}; 11233 11234 contentBody[this.getRestType()] = { 11235 "uri": this.getId(), 11236 "firstName": newValues.firstName, 11237 "lastName": newValues.lastName, 11238 "phoneNumber": newValues.phoneNumber, 11239 "description": newValues.description 11240 }; 11241 11242 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11243 handlers = handlers || {}; 11244 11245 this.restRequest(this.getRestUrl(), { 11246 method: 'PUT', 11247 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11248 error: handlers.error, 11249 content: contentBody 11250 }); 11251 11252 return this; // Allow cascading 11253 }, 11254 11255 11256 /** 11257 * Delete 11258 * @private 11259 */ 11260 "delete": function ( handlers) { 11261 this.isLoaded(); 11262 11263 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11264 handlers = handlers || {}; 11265 11266 this.restRequest(this.getRestUrl(), { 11267 method: 'DELETE', 11268 success: this.createPutSuccessHandler(this, {}, handlers.success), 11269 error: handlers.error, 11270 content: undefined 11271 }); 11272 11273 return this; // Allow cascading 11274 } 11275 }); 11276 11277 window.finesse = window.finesse || {}; 11278 window.finesse.restservices = window.finesse.restservices || {}; 11279 window.finesse.restservices.Contact = Contact; 11280 11281 return Contact; 11282 }); 11283 11284 /** 11285 * JavaScript representation of the Finesse Contacts collection 11286 * object which contains a list of Contact objects. 11287 * 11288 * @requires finesse.clientservices.ClientServices 11289 * @requires Class 11290 * @requires finesse.FinesseBase 11291 * @requires finesse.restservices.RestBase 11292 * @requires finesse.restservices.Dialog 11293 * @requires finesse.restservices.RestCollectionBase 11294 */ 11295 /** @private */ 11296 define('restservices/Contacts',[ 11297 'restservices/RestCollectionBase', 11298 'restservices/Contact' 11299 ], 11300 function (RestCollectionBase, Contact) { 11301 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 11302 11303 /** 11304 * @class 11305 * JavaScript representation of a Contacts collection object. Also exposes 11306 * methods to operate on the object against the server. 11307 * @augments finesse.restservices.RestCollectionBase 11308 * @constructs 11309 * @see finesse.restservices.Contact 11310 * @see finesse.restservices.PhoneBook 11311 * @example 11312 * _contacts = _phonebook.getContacts( { 11313 * onCollectionAdd : _handleContactAdd, 11314 * onCollectionDelete : _handleContactDelete, 11315 * onLoad : _handleContactsLoaded 11316 * }); 11317 * 11318 * _contactCollection = _contacts.getCollection(); 11319 * for (var contactId in _contactCollection) { 11320 * if (_contactCollection.hasOwnProperty(contactId)) { 11321 * _contact = _contactCollection[contactId]; 11322 * etc... 11323 * } 11324 * } 11325 */ 11326 _fakeConstuctor: function () { 11327 /* This is here to hide the real init constructor from the public docs */ 11328 }, 11329 11330 /** 11331 * @private 11332 * JavaScript representation of a Contacts collection object. Also exposes 11333 * methods to operate on the object against the server. 11334 * 11335 * @param {Object} options 11336 * An object with the following properties:<ul> 11337 * <li><b>id:</b> The id of the object being constructed</li> 11338 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11339 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11340 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11341 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11342 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11343 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11344 * <li><b>content:</b> {String} Raw string of response</li> 11345 * <li><b>object:</b> {Object} Parsed object of response</li> 11346 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11347 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11348 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11349 * </ul></li> 11350 * </ul></li> 11351 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11352 **/ 11353 init: function (options) { 11354 this._super(options); 11355 }, 11356 11357 /** 11358 * @private 11359 * Gets the REST class for the current object - this is the Contacts class. 11360 */ 11361 getRestClass: function () { 11362 return Contacts; 11363 }, 11364 11365 /** 11366 * @private 11367 * Gets the REST class for the objects that make up the collection. - this 11368 * is the Contact class. 11369 */ 11370 getRestItemClass: function () { 11371 return Contact; 11372 }, 11373 11374 /** 11375 * @private 11376 * Gets the REST type for the current object - this is a "Contacts". 11377 */ 11378 getRestType: function () { 11379 return "Contacts"; 11380 }, 11381 11382 /** 11383 * @private 11384 * Gets the REST type for the objects that make up the collection - this is "Contacts". 11385 */ 11386 getRestItemType: function () { 11387 return "Contact"; 11388 }, 11389 11390 /** 11391 * @private 11392 * Override default to indicates that the collection supports making 11393 * requests. 11394 */ 11395 supportsRequests: true, 11396 11397 /** 11398 * @private 11399 * Override default to indicates that the collection subscribes to its objects. 11400 */ 11401 supportsRestItemSubscriptions: false, 11402 11403 /** 11404 * @private 11405 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 11406 * 11407 * @returns {finesse.restservices.Contacts} 11408 * This Contacts object, to allow cascading. 11409 */ 11410 get: function () { 11411 // set loaded to false so it will rebuild the collection after the get 11412 this._loaded = false; 11413 // reset collection 11414 this._collection = {}; 11415 // perform get 11416 this._synchronize(); 11417 return this; 11418 } 11419 11420 }); 11421 11422 window.finesse = window.finesse || {}; 11423 window.finesse.restservices = window.finesse.restservices || {}; 11424 window.finesse.restservices.Contacts = Contacts; 11425 11426 11427 return Contacts; 11428 }); 11429 11430 /** 11431 * JavaScript representation of the Finesse PhoneBook object. 11432 * 11433 * @requires finesse.clientservices.ClientServices 11434 * @requires Class 11435 * @requires finesse.FinesseBase 11436 * @requires finesse.restservices.RestBase 11437 */ 11438 11439 /** @private */ 11440 define('restservices/PhoneBook',[ 11441 'restservices/RestBase', 11442 'restservices/Contacts' 11443 ], 11444 function (RestBase, Contacts) { 11445 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 11446 11447 _contacts: null, 11448 11449 /** 11450 * @class 11451 * A PhoneBook is a list of Contacts available to a User for quick dial. 11452 * 11453 * @augments finesse.restservices.RestBase 11454 * @see finesse.restservices.Contacts 11455 * @constructs 11456 */ 11457 _fakeConstuctor: function () { 11458 /* This is here to hide the real init constructor from the public docs */ 11459 }, 11460 11461 /** 11462 * @private 11463 * JavaScript representation of a PhoneBook object. Also exposes 11464 * methods to operate on the object against the server. 11465 * 11466 * @param {Object} options 11467 * An object with the following properties:<ul> 11468 * <li><b>id:</b> The id of the object being constructed</li> 11469 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11470 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11471 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11472 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11473 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11474 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11475 * <li><b>content:</b> {String} Raw string of response</li> 11476 * <li><b>object:</b> {Object} Parsed object of response</li> 11477 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11478 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11479 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11480 * </ul></li> 11481 * </ul></li> 11482 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11483 **/ 11484 init: function (options) { 11485 this._super(options); 11486 }, 11487 11488 /** 11489 * @private 11490 * Gets the REST class for the current object - this is the PhoneBook class. 11491 * @returns {Object} The PhoneBook class. 11492 */ 11493 getRestClass: function () { 11494 return PhoneBook; 11495 }, 11496 11497 /** 11498 * @private 11499 * Gets the REST type for the current object - this is a "PhoneBook". 11500 * @returns {String} The PhoneBook string. 11501 */ 11502 getRestType: function () { 11503 return "PhoneBook"; 11504 }, 11505 11506 /** 11507 * @private 11508 * Override default to indicate that this object doesn't support making 11509 * requests. 11510 */ 11511 supportsRequests: false, 11512 11513 /** 11514 * @private 11515 * Override default to indicate that this object doesn't support subscriptions. 11516 */ 11517 supportsSubscriptions: false, 11518 11519 /** 11520 * Getter for the name of the Phone Book. 11521 * @returns {String} The name. 11522 */ 11523 getName: function () { 11524 this.isLoaded(); 11525 return this.getData().name; 11526 }, 11527 11528 /** 11529 * Getter for the type flag. 11530 * @returns {String} The type. 11531 */ 11532 getType: function () { 11533 this.isLoaded(); 11534 return this.getData().type; 11535 }, 11536 11537 /** 11538 * @private 11539 * Getter for the Uri value. 11540 * @returns {String} The Uri. 11541 */ 11542 getUri: function () { 11543 this.isLoaded(); 11544 return this.getData().uri; 11545 }, 11546 11547 /** 11548 * Getter for a Contacts collection object that is associated with PhoneBook. 11549 * @param {finesse.interfaces.RequestHandlers} handlers 11550 * An object containing the handlers for the request 11551 * @returns {finesse.restservices.Contacts} 11552 * A Contacts collection object. 11553 */ 11554 getContacts: function (callbacks) { 11555 var options = callbacks || {}; 11556 options.parentObj = this; 11557 this.isLoaded(); 11558 11559 if (this._contacts === null) { 11560 this._contacts = new Contacts(options); 11561 } 11562 11563 return this._contacts; 11564 }, 11565 11566 /** 11567 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 11568 * @returns {String} uri to contacts 11569 * or {finesse.restservices.Contacts} collection 11570 */ 11571 getEmbeddedContacts: function(){ 11572 this.isLoaded(); 11573 return this.getData().contacts; 11574 }, 11575 11576 /** @private */ 11577 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 11578 return function (rsp) { 11579 // Update internal structure based on response. Here we 11580 // inject the contentBody from the PUT request into the 11581 // rsp.object element to mimic a GET as a way to take 11582 // advantage of the existing _processResponse method. 11583 rsp.object = contentBody; 11584 phonebook._processResponse(rsp); 11585 11586 //Remove the injected PhoneBook object before cascading response 11587 rsp.object = {}; 11588 11589 //cascade response back to consumer's response handler 11590 successHandler(rsp); 11591 }; 11592 }, 11593 11594 /** @private */ 11595 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 11596 return function (rsp) { 11597 rsp.object = contentBody; 11598 phonebook._processResponse(rsp); 11599 11600 //Remove the injected PhoneBook object before cascading response 11601 rsp.object = {}; 11602 11603 //cascade response back to consumer's response handler 11604 successHandler(rsp); 11605 }; 11606 }, 11607 11608 /** 11609 * @private 11610 * Add a PhoneBook. 11611 * @param {Object} newValues 11612 * @param {String} newValues.name Name of PhoneBook 11613 * @param {String} newValues.type Type of PhoneBook 11614 * @param {finesse.interfaces.RequestHandlers} handlers 11615 * An object containing the handlers for the request 11616 * @returns {finesse.restservices.PhoneBook} 11617 * This PhoneBook object, to allow cascading 11618 */ 11619 add: function (newValues, handlers) { 11620 // this.isLoaded(); 11621 var contentBody = {}; 11622 11623 contentBody[this.getRestType()] = { 11624 "name": newValues.name, 11625 "type": newValues.type 11626 }; 11627 11628 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11629 handlers = handlers || {}; 11630 11631 this.restRequest(this.getRestUrl(), { 11632 method: 'POST', 11633 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11634 error: handlers.error, 11635 content: contentBody 11636 }); 11637 11638 return this; // Allow cascading 11639 }, 11640 11641 /** 11642 * @private 11643 * Update a PhoneBook. 11644 * @param {Object} newValues 11645 * @param {String} newValues.name Name of PhoneBook 11646 * @param {String} newValues.type Type of PhoneBook 11647 * @param {finesse.interfaces.RequestHandlers} handlers 11648 * An object containing the handlers for the request 11649 * @returns {finesse.restservices.PhoneBook} 11650 * This PhoneBook object, to allow cascading 11651 */ 11652 update: function (newValues, handlers) { 11653 this.isLoaded(); 11654 var contentBody = {}; 11655 11656 contentBody[this.getRestType()] = { 11657 "uri": this.getId(), 11658 "name": newValues.name, 11659 "type": newValues.type 11660 }; 11661 11662 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11663 handlers = handlers || {}; 11664 11665 this.restRequest(this.getRestUrl(), { 11666 method: 'PUT', 11667 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11668 error: handlers.error, 11669 content: contentBody 11670 }); 11671 11672 return this; // Allow cascading 11673 }, 11674 11675 11676 /** 11677 * Delete a PhoneBook. 11678 * @param {finesse.interfaces.RequestHandlers} handlers 11679 * An object containing the handlers for the request 11680 * @returns {finesse.restservices.PhoneBook} 11681 * This PhoneBook object, to allow cascading 11682 */ 11683 "delete": function ( handlers) { 11684 this.isLoaded(); 11685 11686 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11687 handlers = handlers || {}; 11688 11689 this.restRequest(this.getRestUrl(), { 11690 method: 'DELETE', 11691 success: this.createPutSuccessHandler(this, {}, handlers.success), 11692 error: handlers.error, 11693 content: undefined 11694 }); 11695 11696 return this; // Allow cascading 11697 } 11698 11699 11700 11701 }); 11702 11703 window.finesse = window.finesse || {}; 11704 window.finesse.restservices = window.finesse.restservices || {}; 11705 window.finesse.restservices.PhoneBook = PhoneBook; 11706 11707 return PhoneBook; 11708 }); 11709 11710 /** 11711 * JavaScript representation of the Finesse PhoneBooks collection 11712 * object which contains a list of PhoneBook objects. 11713 * 11714 * @requires finesse.clientservices.ClientServices 11715 * @requires Class 11716 * @requires finesse.FinesseBase 11717 * @requires finesse.restservices.RestBase 11718 * @requires finesse.restservices.Dialog 11719 * @requires finesse.restservices.RestCollectionBase 11720 */ 11721 /** @private */ 11722 define('restservices/PhoneBooks',[ 11723 'restservices/RestCollectionBase', 11724 'restservices/PhoneBook' 11725 ], 11726 function (RestCollectionBase, PhoneBook) { 11727 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 11728 11729 teamId: null, 11730 /** 11731 * @class 11732 * JavaScript representation of a PhoneBooks collection object. 11733 * @augments finesse.restservices.RestCollectionBase 11734 * @constructs 11735 * @see finesse.restservices.PhoneBook 11736 * @see finesse.restservices.Contacts 11737 * @see finesse.restservices.Contact 11738 * @example 11739 * _phoneBooks = _user.getPhoneBooks( { 11740 * onCollectionAdd : _handlePhoneBookAdd, 11741 * onCollectionDelete : _handlePhoneBookDelete, 11742 * onLoad : _handlePhoneBooksLoaded 11743 * }); 11744 * 11745 * _phoneBookCollection = _phoneBooks.getCollection(); 11746 * for (var phoneBookId in _phoneBookCollection) { 11747 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 11748 * _phoneBook = _phoneBookCollection[phoneBookId]; 11749 * etc... 11750 * } 11751 * } 11752 */ 11753 _fakeConstuctor: function () { 11754 /* This is here to hide the real init constructor from the public docs */ 11755 }, 11756 11757 /** 11758 * @private 11759 * 11760 * @param {Object} options 11761 * An object with the following properties:<ul> 11762 * <li><b>id:</b> The id of the object being constructed</li> 11763 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11764 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11765 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11766 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11767 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11768 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11769 * <li><b>content:</b> {String} Raw string of response</li> 11770 * <li><b>object:</b> {Object} Parsed object of response</li> 11771 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11772 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11773 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11774 * </ul></li> 11775 * </ul></li> 11776 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11777 **/ 11778 init: function (options) { 11779 // Keep the REST response for PhoneBooks to check for 206 Partial Content. 11780 this.keepRestResponse = true; 11781 // Add in the Range header which is required for PhoneBooks API. 11782 this.extraHeaders = { "Range": "objects=1-6000" }; 11783 if(options && options.teamId){ 11784 this.teamId = options.teamId; 11785 } 11786 this._super(options); 11787 }, 11788 11789 getRestUrl: function () { 11790 if(this.teamId){ 11791 return this.getBaseRestUrl() + "/TeamResource/" + this.teamId + "/"+ this.getRestType(); 11792 } 11793 return this._super(); 11794 }, 11795 11796 /** 11797 * @private 11798 * Gets the REST class for the current object - this is the PhoneBooks class. 11799 * @returns {Object} The PhoneBooks class. 11800 */ 11801 getRestClass: function () { 11802 return PhoneBooks; 11803 }, 11804 11805 /** 11806 * @private 11807 * Gets the REST class for the objects that make up the collection. - this is the PhoneBook class. 11808 * @returns {Object} The PhoneBook class 11809 */ 11810 getRestItemClass: function () { 11811 return PhoneBook; 11812 }, 11813 11814 /** 11815 * @private 11816 * Gets the REST type for the current object - this is a "PhoneBooks". 11817 * @returns {String} The PhoneBooks string. 11818 */ 11819 getRestType: function () { 11820 return "PhoneBooks"; 11821 }, 11822 11823 /** 11824 * @private 11825 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 11826 * @returns {String} The PhoneBook string. 11827 */ 11828 getRestItemType: function () { 11829 return "PhoneBook"; 11830 }, 11831 11832 /** 11833 * @private 11834 * Override default to indicates that the collection supports making 11835 * requests. 11836 */ 11837 supportsRequests: true, 11838 11839 /** 11840 * @private 11841 * Override default to indicates that the collection subscribes to its objects. 11842 */ 11843 supportsRestItemSubscriptions: false, 11844 11845 /** 11846 * @private 11847 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 11848 * 11849 * @returns {finesse.restservices.PhoneBooks} 11850 * This PhoneBooks object, to allow cascading. 11851 */ 11852 get: function () { 11853 // set loaded to false so it will rebuild the collection after the get 11854 this._loaded = false; 11855 // reset collection 11856 this._collection = {}; 11857 // perform get 11858 this._synchronize(); 11859 return this; 11860 } 11861 11862 }); 11863 11864 window.finesse = window.finesse || {}; 11865 window.finesse.restservices = window.finesse.restservices || {}; 11866 window.finesse.restservices.PhoneBooks = PhoneBooks; 11867 11868 return PhoneBooks; 11869 }); 11870 11871 /** 11872 * JavaScript representation of the Finesse WorkflowAction object. 11873 * 11874 * @requires finesse.clientservices.ClientServices 11875 * @requires Class 11876 * @requires finesse.FinesseBase 11877 * @requires finesse.restservices.RestBase 11878 */ 11879 11880 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 11881 /*global define,finesse */ 11882 11883 /** @private */ 11884 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 11885 11886 var WorkflowAction = RestBase.extend({ 11887 11888 _contacts: null, 11889 11890 actionTypes: [ 11891 { 11892 name: 'BROWSER_POP', 11893 params: [ 11894 { 11895 name: 'windowName', 11896 type: 'text' 11897 }, 11898 { 11899 name: 'path', 11900 type: 'systemVariableSingleLineEditor' 11901 } 11902 ] 11903 }, 11904 { 11905 name: 'HTTP_REQUEST', 11906 params: [ 11907 { 11908 name: 'method', 11909 type: 'dropdown', 11910 values: ['POST', 'PUT'] 11911 }, 11912 { 11913 name: 'location', 11914 type: 'dropdown', 11915 values: ['FINESSE', 'OTHER'] 11916 }, 11917 { 11918 name: 'contentType', 11919 type: 'text' 11920 }, 11921 { 11922 name: 'path', 11923 type: 'systemVariableSingleLineEditor' 11924 }, 11925 { 11926 name: 'body', 11927 type: 'systemVariableMultiLineEditor' 11928 } 11929 ] 11930 } 11931 // more action type definitions here 11932 ], 11933 11934 /** 11935 * @class 11936 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 11937 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 11938 * 11939 * @augments finesse.restservices.RestBase 11940 * @see finesse.restservices.Workflow 11941 * @constructs 11942 */ 11943 _fakeConstuctor: function () { 11944 /* This is here to hide the real init constructor from the public docs */ 11945 }, 11946 11947 /** 11948 * @private 11949 * JavaScript representation of a WorkflowAction object. Also exposes 11950 * methods to operate on the object against the server. 11951 * 11952 * @param {Object} options 11953 * An object with the following properties:<ul> 11954 * <li><b>id:</b> The id of the object being constructed</li> 11955 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11956 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11957 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11958 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11959 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11960 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11961 * <li><b>content:</b> {String} Raw string of response</li> 11962 * <li><b>object:</b> {Object} Parsed object of response</li> 11963 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11964 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11965 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11966 * </ul></li> 11967 * </ul></li> 11968 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11969 **/ 11970 init: function (options) { 11971 this._super(options); 11972 }, 11973 11974 /** 11975 * @private 11976 * Gets the REST class for the current object - this is the WorkflowAction class. 11977 * @returns {Object} The WorkflowAction class. 11978 */ 11979 getRestClass: function () { 11980 return finesse.restservices.WorkflowAction; 11981 }, 11982 11983 /** 11984 * @private 11985 * Gets the REST type for the current object - this is a "WorkflowAction". 11986 * @returns {String} The WorkflowAction string. 11987 */ 11988 getRestType: function () { 11989 return "WorkflowAction"; 11990 }, 11991 11992 /** 11993 * @private 11994 * Override default to indicate that this object doesn't support making 11995 * requests. 11996 */ 11997 supportsRequests: false, 11998 11999 /** 12000 * @private 12001 * Override default to indicate that this object doesn't support subscriptions. 12002 */ 12003 supportsSubscriptions: false, 12004 12005 /** 12006 * Getter for the name. 12007 * @returns {String} The name. 12008 */ 12009 getName: function () { 12010 this.isLoaded(); 12011 return this.getData().name; 12012 }, 12013 12014 /** 12015 * Getter for the type flag. 12016 * @returns {String} The type. 12017 */ 12018 getType: function () { 12019 this.isLoaded(); 12020 return this.getData().type; 12021 }, 12022 12023 /** 12024 * @private 12025 * Getter for the Uri value. 12026 * @returns {String} The Uri. 12027 */ 12028 getUri: function () { 12029 this.isLoaded(); 12030 return this.getData().uri; 12031 }, 12032 12033 /** 12034 * @private 12035 * Getter for the handledBy value. 12036 * @returns {String} handledBy. 12037 */ 12038 getHandledBy: function () { 12039 this.isLoaded(); 12040 return this.getData().handledBy; 12041 }, 12042 12043 /** 12044 * Getter for the parameters. 12045 * @returns {Object} key = param name, value = param value 12046 */ 12047 getParams: function () { 12048 var map = {}, 12049 params = this.getData().params.Param, 12050 i, 12051 param; 12052 12053 for(i=0; i<params.length; i+=1){ 12054 param = params[i]; 12055 map[param.name] = param.value || ""; 12056 } 12057 12058 return map; 12059 }, 12060 12061 /** 12062 * Getter for the ActionVariables 12063 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 12064 */ 12065 getActionVariables: function() { 12066 var map = {}, 12067 actionVariablesParent = this.getData().actionVariables, 12068 actionVariables, 12069 i, 12070 actionVariable; 12071 12072 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 12073 return map; 12074 } 12075 actionVariables = actionVariablesParent.ActionVariable; 12076 12077 if(actionVariables.length > 0){ 12078 for(i=0; i<actionVariables.length; i+=1){ 12079 actionVariable = actionVariables[i]; 12080 // escape nulls to empty string 12081 actionVariable.name = actionVariable.name || ""; 12082 actionVariable.type = actionVariable.type || ""; 12083 actionVariable.node = actionVariable.node || ""; 12084 actionVariable.testValue = actionVariable.testValue || ""; 12085 map[actionVariable.name] = actionVariable; 12086 } 12087 } else { 12088 map[actionVariables.name] = actionVariables; 12089 } 12090 12091 return map; 12092 }, 12093 12094 /** @private */ 12095 createPutSuccessHandler: function(action, contentBody, successHandler){ 12096 return function (rsp) { 12097 // Update internal structure based on response. Here we 12098 // inject the contentBody from the PUT request into the 12099 // rsp.object element to mimic a GET as a way to take 12100 // advantage of the existing _processResponse method. 12101 rsp.object = contentBody; 12102 action._processResponse(rsp); 12103 12104 //Remove the injected WorkflowAction object before cascading response 12105 rsp.object = {}; 12106 12107 //cascade response back to consumer's response handler 12108 successHandler(rsp); 12109 }; 12110 }, 12111 12112 /** @private */ 12113 createPostSuccessHandler: function (action, contentBody, successHandler) { 12114 return function (rsp) { 12115 rsp.object = contentBody; 12116 action._processResponse(rsp); 12117 12118 //Remove the injected WorkflowAction object before cascading response 12119 rsp.object = {}; 12120 12121 //cascade response back to consumer's response handler 12122 successHandler(rsp); 12123 }; 12124 }, 12125 12126 /** 12127 * @private 12128 * Build params array out of all the values coming into add or update methods 12129 * paramMap is a map of params.. we need to translate it into an array of Param objects 12130 * where path and windowName are params for the BROWSER_POP type 12131 */ 12132 buildParamsForRest: function(paramMap){ 12133 var params = {"Param": []}, 12134 i; 12135 for(i in paramMap){ 12136 if(paramMap.hasOwnProperty(i)){ 12137 params.Param.push({name: i, value: paramMap[i]}); 12138 } 12139 } 12140 return params; 12141 }, 12142 12143 /** 12144 * @private 12145 * Build actionVariables array out of all the values coming into add or update methods 12146 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 12147 * where path and windowName are params for the BROWSER_POP type 12148 */ 12149 buildActionVariablesForRest: function(actionVariableMap){ 12150 var actionVariables = {"ActionVariable": []}, 12151 i, 12152 actionVariable; 12153 for(i in actionVariableMap){ 12154 if(actionVariableMap.hasOwnProperty(i)){ 12155 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 12156 actionVariable = { 12157 "name": actionVariableMap[i].name, 12158 "type": actionVariableMap[i].type, 12159 "node": actionVariableMap[i].node, 12160 "testValue": actionVariableMap[i].testValue 12161 }; 12162 actionVariables.ActionVariable.push(actionVariable); 12163 } 12164 } 12165 return actionVariables; 12166 }, 12167 12168 /** 12169 * Add 12170 */ 12171 add: function (newValues, handlers) { 12172 var contentBody = {}; 12173 12174 contentBody[this.getRestType()] = { 12175 "name": newValues.name, 12176 "type": newValues.type, 12177 "handledBy": newValues.handledBy, 12178 "params": this.buildParamsForRest(newValues.params), 12179 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 12180 }; 12181 12182 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12183 handlers = handlers || {}; 12184 12185 this.restRequest(this.getRestUrl(), { 12186 method: 'POST', 12187 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 12188 error: handlers.error, 12189 content: contentBody 12190 }); 12191 12192 return this; // Allow cascading 12193 }, 12194 12195 /** 12196 * @private 12197 * Update 12198 */ 12199 update: function (newValues, handlers) { 12200 this.isLoaded(); 12201 var contentBody = {}; 12202 12203 contentBody[this.getRestType()] = { 12204 "uri": this.getId(), 12205 "name": newValues.name, 12206 "type": newValues.type, 12207 "handledBy": newValues.handledBy, 12208 "params": this.buildParamsForRest(newValues.params), 12209 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 12210 }; 12211 12212 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12213 handlers = handlers || {}; 12214 12215 this.restRequest(this.getRestUrl(), { 12216 method: 'PUT', 12217 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12218 error: handlers.error, 12219 content: contentBody 12220 }); 12221 12222 return this; // Allow cascading 12223 }, 12224 12225 12226 /** 12227 * @private 12228 * Delete 12229 */ 12230 "delete": function ( handlers) { 12231 this.isLoaded(); 12232 12233 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12234 handlers = handlers || {}; 12235 12236 this.restRequest(this.getRestUrl(), { 12237 method: 'DELETE', 12238 success: this.createPutSuccessHandler(this, {}, handlers.success), 12239 error: handlers.error, 12240 content: undefined 12241 }); 12242 12243 return this; // Allow cascading 12244 } 12245 12246 12247 12248 }); 12249 12250 window.finesse = window.finesse || {}; 12251 window.finesse.restservices = window.finesse.restservices || {}; 12252 window.finesse.restservices.WorkflowAction = WorkflowAction; 12253 12254 return WorkflowAction; 12255 }); 12256 12257 /** 12258 * JavaScript representation of the Finesse WorkflowActions collection 12259 * object which contains a list of WorkflowAction objects. 12260 * 12261 * @requires finesse.clientservices.ClientServices 12262 * @requires Class 12263 * @requires finesse.FinesseBase 12264 * @requires finesse.restservices.RestBase 12265 * @requires finesse.restservices.Dialog 12266 * @requires finesse.restservices.RestCollectionBase 12267 */ 12268 12269 /** @private */ 12270 define('restservices/WorkflowActions',[ 12271 'restservices/RestCollectionBase', 12272 'restservices/RestBase', 12273 'restservices/WorkflowAction' 12274 ], 12275 function (RestCollectionBase, RestBase, WorkflowAction) { 12276 12277 var WorkflowActions = RestCollectionBase.extend({ 12278 12279 /** 12280 * @class 12281 * JavaScript representation of a WorkflowActions collection object. 12282 * @augments finesse.restservices.RestCollectionBase 12283 * @constructs 12284 * @see finesse.restservices.WorkflowAction 12285 * @see finesse.restservices.Workflow 12286 * @see finesse.restservices.Workflows 12287 * @example 12288 * _workflowActions = _user.getWorkflowActions( { 12289 * onCollectionAdd : _handleWorkflowActionAdd, 12290 * onCollectionDelete : _handleWorkflowActionDelete, 12291 * onLoad : _handleWorkflowActionsLoaded 12292 * }); 12293 */ 12294 _fakeConstuctor: function () { 12295 /* This is here to hide the real init constructor from the public docs */ 12296 }, 12297 12298 /** 12299 * @private 12300 * JavaScript representation of a WorkflowActions collection object. Also exposes 12301 * methods to operate on the object against the server. 12302 * 12303 * @param {Object} options 12304 * An object with the following properties:<ul> 12305 * <li><b>id:</b> The id of the object being constructed</li> 12306 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12307 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12308 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12309 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12310 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12311 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12312 * <li><b>content:</b> {String} Raw string of response</li> 12313 * <li><b>object:</b> {Object} Parsed object of response</li> 12314 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12315 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12316 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12317 * </ul></li> 12318 * </ul></li> 12319 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12320 **/ 12321 init: function (options) { 12322 this._super(options); 12323 }, 12324 12325 /** 12326 * @private 12327 * Gets the REST class for the current object - this is the WorkflowActions class. 12328 */ 12329 getRestClass: function () { 12330 return WorkflowActions; 12331 }, 12332 12333 /** 12334 * @private 12335 * Gets the REST class for the objects that make up the collection. - this 12336 * is the WorkflowAction class. 12337 */ 12338 getRestItemClass: function () { 12339 return WorkflowAction; 12340 }, 12341 12342 /** 12343 * @private 12344 * Gets the REST type for the current object - this is a "WorkflowActions". 12345 */ 12346 getRestType: function () { 12347 return "WorkflowActions"; 12348 }, 12349 12350 /** 12351 * @private 12352 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 12353 */ 12354 getRestItemType: function () { 12355 return "WorkflowAction"; 12356 }, 12357 12358 /** 12359 * @private 12360 * Override default to indicates that the collection supports making 12361 * requests. 12362 */ 12363 supportsRequests: true, 12364 12365 /** 12366 * @private 12367 * Override default to indicates that the collection subscribes to its objects. 12368 */ 12369 supportsRestItemSubscriptions: false, 12370 12371 /** 12372 * @private 12373 * Retrieve the WorkflowActions. 12374 * 12375 * @returns {finesse.restservices.WorkflowActions} 12376 * This WorkflowActions object to allow cascading. 12377 */ 12378 get: function () { 12379 // set loaded to false so it will rebuild the collection after the get 12380 this._loaded = false; 12381 // reset collection 12382 this._collection = {}; 12383 // perform get 12384 this._synchronize(); 12385 return this; 12386 } 12387 }); 12388 12389 window.finesse = window.finesse || {}; 12390 window.finesse.restservices = window.finesse.restservices || {}; 12391 window.finesse.restservices.WorkflowActions = WorkflowActions; 12392 12393 return WorkflowActions; 12394 }); 12395 12396 /** 12397 * JavaScript representation of the Finesse Workflow object. 12398 * 12399 * @requires finesse.clientservices.ClientServices 12400 * @requires Class 12401 * @requires finesse.FinesseBase 12402 * @requires finesse.restservices.RestBase 12403 */ 12404 12405 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 12406 /*global define,finesse */ 12407 12408 /** @private */ 12409 define('restservices/Workflow',[ 12410 'restservices/RestBase', 12411 'restservices/WorkflowActions' 12412 ], 12413 function (RestBase, WorkflowActions) { 12414 12415 var Workflow = RestBase.extend({ 12416 12417 /** 12418 * @class 12419 * JavaScript representation of a Workflow object. Also exposes 12420 * methods to operate on the object against the server. 12421 * 12422 * @param {Object} options 12423 * An object with the following properties:<ul> 12424 * <li><b>id:</b> The id of the object being constructed</li> 12425 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12426 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12427 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12428 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12429 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12430 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12431 * <li><b>content:</b> {String} Raw string of response</li> 12432 * <li><b>object:</b> {Object} Parsed object of response</li> 12433 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12434 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12435 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12436 * </ul></li> 12437 * </ul></li> 12438 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12439 * @constructs 12440 **/ 12441 init: function (options) { 12442 this._super(options); 12443 }, 12444 12445 /** 12446 * @private 12447 * Gets the REST class for the current object - this is the Workflow class. 12448 * @returns {Object} The Workflow class. 12449 */ 12450 getRestClass: function () { 12451 return Workflow; 12452 }, 12453 12454 /** 12455 * @private 12456 * Gets the REST type for the current object - this is a "Workflow". 12457 * @returns {String} The Workflow string. 12458 */ 12459 getRestType: function () { 12460 return "Workflow"; 12461 }, 12462 12463 /** 12464 * @private 12465 * Override default to indicate that this object doesn't support making 12466 * requests. 12467 */ 12468 supportsRequests: false, 12469 12470 /** 12471 * @private 12472 * Override default to indicate that this object doesn't support subscriptions. 12473 */ 12474 supportsSubscriptions: false, 12475 12476 /** 12477 * @private 12478 * Getter for the Uri value. 12479 * @returns {String} The Uri. 12480 */ 12481 getUri: function () { 12482 this.isLoaded(); 12483 return this.getData().uri; 12484 }, 12485 12486 /** 12487 * Getter for the name. 12488 * @returns {String} The name. 12489 */ 12490 getName: function () { 12491 this.isLoaded(); 12492 return this.getData().name; 12493 }, 12494 12495 /** 12496 * Getter for the description. 12497 * @returns {String} The description. 12498 */ 12499 getDescription: function () { 12500 this.isLoaded(); 12501 return this.getData().description; 12502 }, 12503 12504 /** 12505 * Getter for the media. 12506 * @returns {String} The media. 12507 */ 12508 getMedia: function () { 12509 this.isLoaded(); 12510 return this.getData().media; 12511 }, 12512 12513 /** 12514 * Getter for the trigger set. 12515 * @returns {String} The trigger set. 12516 */ 12517 getTriggerSet: function () { 12518 this.isLoaded(); 12519 return this.getData().TriggerSet; 12520 }, 12521 12522 /** 12523 * Getter for the condition set. 12524 * @returns {String} The condition set. 12525 */ 12526 getConditionSet: function () { 12527 this.isLoaded(); 12528 return this.getData().ConditionSet; 12529 }, 12530 12531 /** 12532 * Getter for the assigned workflowActions. 12533 * @returns {String} The workflowActions object. 12534 */ 12535 getWorkflowActions: function () { 12536 this.isLoaded(); 12537 var workflowActions = this.getData().workflowActions; 12538 if (workflowActions === null) { 12539 workflowActions = ""; 12540 } 12541 return workflowActions; 12542 }, 12543 12544 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 12545 return function (rsp) { 12546 // Update internal structure based on response. Here we 12547 // inject the contentBody from the PUT request into the 12548 // rsp.object element to mimic a GET as a way to take 12549 // advantage of the existing _processResponse method. 12550 rsp.object = contentBody; 12551 workflow._processResponse(rsp); 12552 12553 //Remove the injected Workflow object before cascading response 12554 rsp.object = {}; 12555 12556 //cascade response back to consumer's response handler 12557 successHandler(rsp); 12558 }; 12559 }, 12560 12561 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 12562 return function (rsp) { 12563 rsp.object = contentBody; 12564 workflow._processResponse(rsp); 12565 12566 //Remove the injected Workflow object before cascading response 12567 rsp.object = {}; 12568 12569 //cascade response back to consumer's response handler 12570 successHandler(rsp); 12571 }; 12572 }, 12573 12574 /** 12575 * @private 12576 * Add 12577 */ 12578 add: function (newValues, handlers) { 12579 // this.isLoaded(); 12580 var contentBody = {}; 12581 12582 contentBody[this.getRestType()] = { 12583 "media": newValues.media, 12584 "name": newValues.name, 12585 "description": newValues.description, 12586 "TriggerSet" : newValues.TriggerSet, 12587 "ConditionSet" : newValues.ConditionSet, 12588 "workflowActions" : newValues.workflowActions 12589 }; 12590 12591 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12592 handlers = handlers || {}; 12593 12594 this.restRequest(this.getRestUrl(), { 12595 method: 'POST', 12596 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 12597 error: handlers.error, 12598 content: contentBody 12599 }); 12600 12601 return this; // Allow cascading 12602 }, 12603 12604 /** 12605 * @private 12606 * Update 12607 */ 12608 update: function (newValues, handlers) { 12609 this.isLoaded(); 12610 var contentBody = {}; 12611 12612 contentBody[this.getRestType()] = { 12613 "uri": this.getId(), 12614 "media": this.getMedia(), 12615 "name": newValues.name, 12616 "description": newValues.description, 12617 "TriggerSet" : newValues.TriggerSet, 12618 "ConditionSet" : newValues.ConditionSet, 12619 "workflowActions" : newValues.workflowActions 12620 }; 12621 12622 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12623 handlers = handlers || {}; 12624 12625 this.restRequest(this.getRestUrl(), { 12626 method: 'PUT', 12627 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12628 error: handlers.error, 12629 content: contentBody 12630 }); 12631 12632 return this; // Allow cascading 12633 }, 12634 12635 12636 /** 12637 * @private 12638 * Delete 12639 */ 12640 "delete": function (handlers) { 12641 this.isLoaded(); 12642 12643 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12644 handlers = handlers || {}; 12645 12646 this.restRequest(this.getRestUrl(), { 12647 method: 'DELETE', 12648 success: this.createPutSuccessHandler(this, {}, handlers.success), 12649 error: handlers.error, 12650 content: undefined 12651 }); 12652 12653 return this; // Allow cascading 12654 } 12655 12656 12657 12658 }); 12659 12660 window.finesse = window.finesse || {}; 12661 window.finesse.restservices = window.finesse.restservices || {}; 12662 window.finesse.restservices.Workflow = Workflow; 12663 12664 return Workflow; 12665 }); 12666 12667 /** 12668 * JavaScript representation of the Finesse workflows collection 12669 * object which contains a list of workflow objects. 12670 * 12671 * @requires finesse.clientservices.ClientServices 12672 * @requires Class 12673 * @requires finesse.FinesseBase 12674 * @requires finesse.restservices.RestBase 12675 * @requires finesse.restservices.Dialog 12676 * @requires finesse.restservices.RestCollectionBase 12677 */ 12678 12679 /** @private */ 12680 define('restservices/Workflows',[ 12681 'restservices/RestCollectionBase', 12682 'restservices/RestBase', 12683 'restservices/Workflow' 12684 ], 12685 function (RestCollectionBase, RestBase, Workflow) { 12686 12687 var Workflows = RestCollectionBase.extend({ 12688 12689 /** 12690 * @class 12691 * JavaScript representation of a workflows collection object. Also exposes 12692 * methods to operate on the object against the server. 12693 * 12694 * @param {Object} options 12695 * An object with the following properties:<ul> 12696 * <li><b>id:</b> The id of the object being constructed</li> 12697 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12698 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12699 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12700 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12701 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12702 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12703 * <li><b>content:</b> {String} Raw string of response</li> 12704 * <li><b>object:</b> {Object} Parsed object of response</li> 12705 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12706 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12707 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12708 * </ul></li> 12709 * </ul></li> 12710 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12711 * @constructs 12712 **/ 12713 init: function (options) { 12714 if(options && options.teamId){ 12715 this.teamId = options.teamId; 12716 } 12717 12718 this._super(options); 12719 }, 12720 12721 getRestUrl: function () { 12722 if(this.teamId){ 12723 return this.getBaseRestUrl() + "/TeamResource/" + this.teamId + "/"+ this.getRestType(); 12724 } 12725 return this._super(); 12726 }, 12727 12728 /** 12729 * @private 12730 * Gets the REST class for the current object - this is the workflows class. 12731 */ 12732 getRestClass: function () { 12733 return Workflows; 12734 }, 12735 12736 /** 12737 * @private 12738 * Gets the REST class for the objects that make up the collection. - this 12739 * is the workflow class. 12740 */ 12741 getRestItemClass: function () { 12742 return Workflow; 12743 }, 12744 12745 /** 12746 * @private 12747 * Gets the REST type for the current object - this is a "workflows". 12748 */ 12749 getRestType: function () { 12750 return "Workflows"; 12751 }, 12752 12753 /** 12754 * @private 12755 * Gets the REST type for the objects that make up the collection - this is "workflows". 12756 */ 12757 getRestItemType: function () { 12758 return "Workflow"; 12759 }, 12760 12761 /** 12762 * @private 12763 * Override default to indicates that the collection supports making requests. 12764 */ 12765 supportsRequests: true, 12766 12767 /** 12768 * @private 12769 * Override default to indicates that the collection does not subscribe to its objects. 12770 */ 12771 supportsRestItemSubscriptions: false, 12772 12773 /** 12774 * @private 12775 * Retrieve the workflows. This call will re-query the server and refresh the collection. 12776 * 12777 * @returns {finesse.restservices.workflows} 12778 * This workflows object to allow cascading. 12779 */ 12780 get: function () { 12781 // set loaded to false so it will rebuild the collection after the get 12782 this._loaded = false; 12783 // reset collection 12784 this._collection = {}; 12785 // perform get 12786 this._synchronize(); 12787 return this; 12788 } 12789 }); 12790 12791 window.finesse = window.finesse || {}; 12792 window.finesse.restservices = window.finesse.restservices || {}; 12793 window.finesse.restservices.Workflows = Workflows; 12794 12795 return Workflows; 12796 }); 12797 12798 /** 12799 * JavaScript representation of the Finesse MediaPropertiesLayout object for the Admin webapp. 12800 * @requires finesse.clientservices.ClientServices 12801 * @requires Class 12802 * @requires finesse.FinesseBase 12803 * @requires finesse.restservices.RestBase 12804 */ 12805 12806 /** The following comment is to prevent jslint errors about 12807 * using variables before they are defined. 12808 */ 12809 /*global finesse*/ 12810 12811 /** 12812 * @class 12813 * JavaScript representation of a MediaPropertiesLayout object for the Admin webapp. Also exposes 12814 * methods to operate on the object against the server. 12815 * 12816 * @constructor 12817 * @param {String} id 12818 * Not required... 12819 * @param {Object} callbacks 12820 * An object containing callbacks for instantiation and runtime 12821 * @param {Function} callbacks.onLoad(this) 12822 * Callback to invoke upon successful instantiation, passes in MediaPropertiesLayout object 12823 * @param {Function} callbacks.onLoadError(rsp) 12824 * Callback to invoke on instantiation REST request error 12825 * as passed by finesse.clientservices.ClientServices.ajax() 12826 * { 12827 * status: {Number} The HTTP status code returned 12828 * content: {String} Raw string of response 12829 * object: {Object} Parsed object of response 12830 * error: {Object} Wrapped exception that was caught 12831 * error.errorType: {String} Type of error that was caught 12832 * error.errorMessage: {String} Message associated with error 12833 * } 12834 * @param {Function} callbacks.onChange(this) 12835 * Callback to invoke upon successful update, passes in MediaPropertiesLayout object 12836 * @param {Function} callbacks.onError(rsp) 12837 * Callback to invoke on update error (refresh or event) 12838 * as passed by finesse.clientservices.ClientServices.ajax() 12839 * { 12840 * status: {Number} The HTTP status code returned 12841 * content: {String} Raw string of response 12842 * object: {Object} Parsed object of response 12843 * error: {Object} Wrapped exception that was caught 12844 * error.errorType: {String} Type of error that was caught 12845 * error.errorMessage: {String} Message associated with error 12846 * } 12847 */ 12848 12849 /** @private */ 12850 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 12851 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 12852 12853 /** 12854 * @class 12855 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 12856 * 12857 * @augments finesse.restservices.RestBase 12858 * @see finesse.restservices.Dialog#getMediaProperties 12859 * @see finesse.restservices.User#getMediaPropertiesLayout 12860 * @constructs 12861 */ 12862 _fakeConstuctor: function () { 12863 /* This is here to hide the real init constructor from the public docs */ 12864 }, 12865 12866 /** 12867 * @private 12868 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 12869 * methods to operate on the object against the server. 12870 * 12871 * @param {Object} options 12872 * An object with the following properties:<ul> 12873 * <li><b>id:</b> The id of the object being constructed</li> 12874 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12875 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12876 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12877 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12878 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12879 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12880 * <li><b>content:</b> {String} Raw string of response</li> 12881 * <li><b>object:</b> {Object} Parsed object of response</li> 12882 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12883 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12884 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12885 * </ul></li> 12886 * </ul></li> 12887 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12888 **/ 12889 init: function (options) { 12890 this._super(options); 12891 }, 12892 12893 /** 12894 * @private 12895 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 12896 * @returns {Object} The MediaPropertiesLayout constructor. 12897 */ 12898 getRestClass: function () { 12899 return MediaPropertiesLayout; 12900 }, 12901 12902 /** 12903 * @private 12904 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 12905 * @returns {String} The MediaPropertiesLayout string 12906 */ 12907 getRestType: function () { 12908 return "MediaPropertiesLayout"; 12909 }, 12910 12911 /** 12912 * @private 12913 * Returns whether this object supports subscriptions 12914 */ 12915 supportsSubscriptions: false, 12916 12917 /** 12918 * Getter for the name. 12919 * @returns {String} The name. 12920 */ 12921 getName: function () { 12922 this.isLoaded(); 12923 return this._data.name; 12924 }, 12925 12926 /** 12927 * Getter for the description. 12928 * @returns {String} The description. 12929 */ 12930 getDescription: function () { 12931 this.isLoaded(); 12932 return this._data.description || ""; 12933 }, 12934 12935 /** 12936 * Getter for the layout type (should be DEFAULT or CUSTOM). 12937 * @returns {String} The layout type. 12938 */ 12939 getType: function () { 12940 this.isLoaded(); 12941 return this._data.type || ""; 12942 }, 12943 12944 /** 12945 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 12946 * @returns {finesse.restservices.MediaPropertiesLayout} 12947 * This MediaPropertiesLayout object to allow cascading 12948 */ 12949 get: function () { 12950 this._synchronize(); 12951 12952 return this; //Allow cascading 12953 }, 12954 12955 /** 12956 * Gets the data for this object. 12957 * 12958 * Performs safe conversion from raw API data to ensure that the returned layout object 12959 * always has a header with correct entry fields, and exactly two columns with lists of entries. 12960 * 12961 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 12962 */ 12963 getData: function () { 12964 12965 var layout = this._data, result, _addColumnData; 12966 12967 result = this.getEmptyData(); 12968 result.name = layout.name; 12969 result.description = layout.description; 12970 result.type = layout.type; 12971 12972 /** 12973 * @private 12974 */ 12975 _addColumnData = function (entryData, colIndex) { 12976 12977 if (!entryData) { 12978 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 12979 entryData = {}; 12980 } else if (entryData.mediaProperty) { 12981 //If entryData contains the keys for a single entry rather than being a collection of entries, 12982 //rewrite it to be a collection containing a single entry 12983 entryData = { "": entryData }; 12984 } 12985 12986 //Add each of the entries in the list to the column 12987 jQuery.each(entryData, function (i, entryData) { 12988 12989 //If the entry has no displayName specified, explicitly set it to the empty string 12990 if (!entryData.displayName) { 12991 entryData.displayName = ""; 12992 } 12993 12994 result.columns[colIndex].push(entryData); 12995 12996 }); 12997 12998 }; 12999 13000 //The header should only contain a single entry 13001 if (layout.header && layout.header.entry) { 13002 13003 //If the entry has no displayName specified, explicitly set it to the empty string 13004 if (!layout.header.entry.displayName) { 13005 layout.header.entry.displayName = ""; 13006 } 13007 13008 result.header = layout.header.entry; 13009 13010 } else { 13011 13012 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 13013 13014 } 13015 13016 //If the column object contains an entry object that wasn't part of a list of entries, 13017 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 13018 //Force the entry object to be the 2nd element in an otherwise-empty list. 13019 if (layout.column && layout.column.entry) { 13020 layout.column = [ 13021 null, 13022 { "entry": layout.column.entry } 13023 ]; 13024 } 13025 13026 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 13027 13028 //Render left column entries 13029 if (layout.column[0] && layout.column[0].entry) { 13030 _addColumnData(layout.column[0].entry, 0); 13031 } 13032 13033 //Render right column entries 13034 if (layout.column[1] && layout.column[1].entry) { 13035 _addColumnData(layout.column[1].entry, 1); 13036 } 13037 13038 } 13039 13040 return result; 13041 13042 }, 13043 13044 /** 13045 * @private 13046 * Empty/template version of getData(). 13047 * 13048 * Used by getData(), and by callers of getData() in error cases. 13049 * @returns Empty/template version of getData() 13050 */ 13051 getEmptyData: function () { 13052 13053 return { 13054 header : { 13055 displayName: null, 13056 mediaProperty: null 13057 }, 13058 columns : [[], []] 13059 }; 13060 13061 }, 13062 13063 /** 13064 * Update the layout of this MediaPropertiesLayout 13065 * @param {Object} layout 13066 * The object representation of the layout you are setting 13067 * @param {finesse.interfaces.RequestHandlers} handlers 13068 * An object containing the handlers for the request 13069 * @returns {finesse.restservices.MediaPropertiesLayout} 13070 * This MediaPropertiesLayout object to allow cascading 13071 * @private 13072 */ 13073 update: function (newLayoutObject, handlers) { 13074 var contentBody = {}; 13075 13076 // Make sure type is kept the same 13077 newLayoutObject.type = this.getType(); 13078 13079 contentBody[this.getRestType()] = newLayoutObject; 13080 13081 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 13082 handlers = handlers || {}; 13083 13084 this.restRequest(this.getRestUrl(), { 13085 method: 'PUT', 13086 success: handlers.success, 13087 error: handlers.error, 13088 content: contentBody 13089 }); 13090 13091 return this; // Allow cascading 13092 }, 13093 13094 /** 13095 * Create a new MediaPropertiesLayout object with the layout passed in 13096 * @param {Object} layout 13097 * The object representation of the layout you are creating 13098 * @param {finesse.interfaces.RequestHandlers} handlers 13099 * An object containing the handlers for the request 13100 * @returns {finesse.restservices.MediaPropertiesLayout} 13101 * This MediaPropertiesLayout object to allow cascading 13102 * @private 13103 */ 13104 add: function (layout, handlers) { 13105 var contentBody = {}; 13106 13107 contentBody[this.getRestType()] = layout; 13108 13109 handlers = handlers || {}; 13110 13111 this.restRequest(this.getRestUrl(), { 13112 method: 'POST', 13113 success: handlers.success, 13114 error: handlers.error, 13115 content: contentBody 13116 }); 13117 13118 return this; // Allow cascading 13119 }, 13120 13121 /** 13122 * Delete this MediaPropertiesLayout 13123 * @param {finesse.interfaces.RequestHandlers} handlers 13124 * An object containing the handlers for the request 13125 * @returns {finesse.restservices.MediaPropertiesLayout} 13126 * This MediaPropertiesLayout object to allow cascading 13127 * @private 13128 */ 13129 "delete": function (handlers) { 13130 handlers = handlers || {}; 13131 13132 this.restRequest(this.getRestUrl(), { 13133 method: 'DELETE', 13134 success: handlers.success, 13135 error: handlers.error, 13136 content: undefined 13137 }); 13138 13139 return this; // Allow cascading 13140 } 13141 13142 }); 13143 13144 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 13145 /** 13146 * @class Format of MediaPropertiesLayout Object.<br> 13147 * Object { <ul> 13148 * <li>header : { <ul> 13149 * <li>dispayName {String} 13150 * <li>mediaProperty {String}</ul>} 13151 * <li>columns : { <ul> 13152 * <li>[ [] , [] ] 13153 * </ul> 13154 * where column arrays consists of the same Object format as header.<br> 13155 * }</ul> 13156 * }<br> 13157 * @constructs 13158 */ 13159 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13160 13161 }; 13162 13163 window.finesse = window.finesse || {}; 13164 window.finesse.restservices = window.finesse.restservices || {}; 13165 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 13166 13167 return MediaPropertiesLayout; 13168 }); 13169 13170 /** 13171 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 13172 * 13173 * @requires MediaPropertiesLayout 13174 * @requires ClientServices 13175 * @requires finesse.FinesseBase 13176 * @requires finesse.restservices.RestBase 13177 */ 13178 13179 /** The following comment is to prevent jslint errors about 13180 * using variables before they are defined. 13181 */ 13182 /*global finesse*/ 13183 13184 /** @private */ 13185 define('restservices/UserMediaPropertiesLayout',['restservices/MediaPropertiesLayout'], function (MediaPropertiesLayout) { 13186 var UserMediaPropertiesLayout = MediaPropertiesLayout.extend(/** @lends finesse.restservices.UserMediaPropertiesLayout.prototype */{ 13187 13188 /** 13189 * @class 13190 * JavaScript representation of a UserMediaPropertiesLayout collection object. Also exposes 13191 * methods to operate on the object against the server. 13192 * 13193 * @param {Object} options 13194 * An object with the following properties:<ul> 13195 * <li><b>id:</b> The id of the object being constructed</li> 13196 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13197 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13198 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13199 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13200 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13201 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13202 * <li><b>content:</b> {String} Raw string of response</li> 13203 * <li><b>object:</b> {Object} Parsed object of response</li> 13204 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13205 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13206 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13207 * </ul></li> 13208 * </ul></li> 13209 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13210 * @constructs 13211 **/ 13212 init: function (options) { 13213 this._super(options); 13214 }, 13215 13216 /** 13217 * @private 13218 * Gets the REST class for the current object - this is the UserMediaPropertiesLayout class. 13219 */ 13220 getRestClass: function () { 13221 return UserMediaPropertiesLayout; 13222 }, 13223 13224 /** 13225 * Overrides the parent class. Returns the url for the UserMediaPropertiesLayout resource 13226 */ 13227 getRestUrl: function () { 13228 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 13229 }, 13230 13231 /** 13232 * @private 13233 * Override to throw an error because we cannot do an update on the User's 13234 * MediaPropertiesLayout node 13235 */ 13236 update: function (layout, handlers) { 13237 throw new Error("update(): Cannot update layout for User's MediaPropertiesLayout"); 13238 }, 13239 13240 /** 13241 * @private 13242 * Override to throw an error because we cannot create a new layout on the User's 13243 * MediaPropertiesLayout node 13244 */ 13245 add: function (layout, handlers) { 13246 throw new Error("add(): Cannot create a new layout for User's MediaPropertiesLayout"); 13247 }, 13248 13249 /** 13250 * @private 13251 * Override to throw an error because we cannot delete the layout on the User's 13252 * MediaPropertiesLayout node 13253 */ 13254 "delete": function (layout, handlers) { 13255 throw new Error("delete(): Cannot delete the layout for User's MediaPropertiesLayout"); 13256 } 13257 13258 }); 13259 13260 window.finesse = window.finesse || {}; 13261 window.finesse.restservices = window.finesse.restservices || {}; 13262 window.finesse.restservices.UserMediaPropertiesLayout = UserMediaPropertiesLayout; 13263 13264 return UserMediaPropertiesLayout; 13265 }); 13266 13267 /** 13268 * JavaScript representation of the Finesse mediaPropertiesLayouts collection 13269 * object which contains a list of mediaPropertiesLayout objects. 13270 * 13271 * @requires finesse.clientservices.ClientServices 13272 * @requires Class 13273 * @requires finesse.FinesseBase 13274 * @requires finesse.restservices.RestBase 13275 * @requires finesse.restservices.Dialog 13276 * @requires finesse.restservices.RestCollectionBase 13277 */ 13278 13279 /** @private */ 13280 define('restservices/MediaPropertiesLayouts',[ 13281 'restservices/RestCollectionBase', 13282 'restservices/RestBase', 13283 'restservices/MediaPropertiesLayout' 13284 ], 13285 function (RestCollectionBase, RestBase, MediaPropertiesLayout) { 13286 13287 var MediaPropertiesLayouts = RestCollectionBase.extend({ 13288 13289 /** 13290 * @class 13291 * JavaScript representation of a mediaPropertiesLayouts collection object. Also exposes 13292 * methods to operate on the object against the server. 13293 * 13294 * @param {Object} options 13295 * An object with the following properties:<ul> 13296 * <li><b>id:</b> The id of the object being constructed</li> 13297 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13298 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13299 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13300 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13301 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13302 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13303 * <li><b>content:</b> {String} Raw string of response</li> 13304 * <li><b>object:</b> {Object} Parsed object of response</li> 13305 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13306 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13307 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13308 * </ul></li> 13309 * </ul></li> 13310 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13311 * @constructs 13312 **/ 13313 init: function (options) { 13314 if(options && options.teamId){ 13315 this.teamId = options.teamId; 13316 } 13317 this._super(options); 13318 }, 13319 13320 getRestUrl: function () { 13321 if(this.teamId){ 13322 return this.getBaseRestUrl() + "/TeamResource/" + this.teamId + "/"+ this.getRestType(); 13323 } 13324 return this._super(); 13325 }, 13326 13327 /** 13328 * @private 13329 * Gets the REST class for the current object - this is the mediaPropertiesLayouts class. 13330 */ 13331 getRestClass: function () { 13332 return MediaPropertiesLayouts; 13333 }, 13334 13335 /** 13336 * @private 13337 * Gets the REST class for the objects that make up the collection. - this 13338 * is the mediaPropertiesLayout class. 13339 */ 13340 getRestItemClass: function () { 13341 return MediaPropertiesLayout; 13342 }, 13343 13344 /** 13345 * @private 13346 * Gets the REST type for the current object - this is a "mediaPropertiesLayouts". 13347 */ 13348 getRestType: function () { 13349 return "MediaPropertiesLayouts"; 13350 }, 13351 13352 /** 13353 * @private 13354 * Gets the REST type for the objects that make up the collection - this is "mediaPropertiesLayouts". 13355 */ 13356 getRestItemType: function () { 13357 return "MediaPropertiesLayout"; 13358 }, 13359 13360 /** 13361 * @private 13362 * Override default to indicates that the collection supports making requests. 13363 */ 13364 supportsRequests: true, 13365 13366 /** 13367 * @private 13368 * Override default to indicates that the collection does not subscribe to its objects. 13369 */ 13370 supportsRestItemSubscriptions: false, 13371 13372 /** 13373 * @private 13374 * Retrieve the MediaPropertiesLayouts. This call will re-query the server and refresh the collection. 13375 * 13376 * @returns {finesse.restservices.MediaPropertiesLayouts} 13377 * This MediaPropertiesLayouts object to allow cascading. 13378 */ 13379 get: function () { 13380 // set loaded to false so it will rebuild the collection after the get 13381 this._loaded = false; 13382 // reset collection 13383 this._collection = {}; 13384 // perform get 13385 this._synchronize(); 13386 return this; 13387 } 13388 }); 13389 13390 window.finesse = window.finesse || {}; 13391 window.finesse.restservices = window.finesse.restservices || {}; 13392 window.finesse.restservices.MediaPropertiesLayouts = MediaPropertiesLayouts; 13393 13394 return MediaPropertiesLayouts; 13395 }); 13396 13397 /** 13398 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 13399 * 13400 * @requires MediaPropertiesLayout 13401 * @requires ClientServices 13402 * @requires finesse.FinesseBase 13403 * @requires finesse.restservices.RestBase 13404 */ 13405 13406 /** The following comment is to prevent jslint errors about 13407 * using variables before they are defined. 13408 */ 13409 /*global finesse*/ 13410 13411 /** @private */ 13412 define('restservices/UserMediaPropertiesLayouts',[ 13413 'restservices/MediaPropertiesLayouts', 13414 'restservices/UserMediaPropertiesLayout' 13415 ], 13416 function (MediaPropertiesLayouts, UserMediaPropertiesLayout) { 13417 var UserMediaPropertiesLayouts = MediaPropertiesLayouts.extend(/** @lends finesse.restservices.UserMediaPropertiesLayouts.prototype */{ 13418 13419 /** 13420 * @class 13421 * JavaScript representation of a UserMediaPropertiesLayouts collection object. Also exposes 13422 * methods to operate on the object against the server. 13423 * 13424 * @param {Object} options 13425 * An object with the following properties:<ul> 13426 * <li><b>id:</b> The id of the object being constructed</li> 13427 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13428 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13429 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13430 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13431 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13432 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13433 * <li><b>content:</b> {String} Raw string of response</li> 13434 * <li><b>object:</b> {Object} Parsed object of response</li> 13435 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13436 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13437 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13438 * </ul></li> 13439 * </ul></li> 13440 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13441 * @constructs 13442 **/ 13443 init: function (options) { 13444 this._super(options); 13445 }, 13446 13447 /** 13448 * @private 13449 * Gets the REST class for the current object - this is the UserMediaPropertiesLayouts class. 13450 */ 13451 getRestClass: function () { 13452 return UserMediaPropertiesLayouts; 13453 }, 13454 13455 /** 13456 * @private 13457 * Gets the REST class for the objects that make up the collection. - this 13458 * is the UserMediaPropertiesLayout class. 13459 */ 13460 getRestItemClass: function() { 13461 return UserMediaPropertiesLayout; 13462 } 13463 }); 13464 13465 window.finesse = window.finesse || {}; 13466 window.finesse.restservices = window.finesse.restservices || {}; 13467 window.finesse.restservices.UserMediaPropertiesLayouts = UserMediaPropertiesLayouts; 13468 13469 return UserMediaPropertiesLayouts; 13470 }); 13471 13472 /** 13473 * JavaScript representation of the Finesse Dialog object for non-voice media. 13474 * 13475 * @requires finesse.restservices.DialogBase 13476 */ 13477 13478 /** @private */ 13479 define('restservices/MediaDialog',[ 13480 'restservices/DialogBase' 13481 ], 13482 function (DialogBase) { 13483 var MediaDialog = DialogBase.extend(/** @lends finesse.restservices.MediaDialog.prototype */{ 13484 13485 /** 13486 * @private 13487 * 13488 * Support requests so that applications can refresh non-voice dialogs when the media channel that the 13489 * dialog belongs to is interrupted. An event is not sent to update a dialog's actions when the media is 13490 * interrupted so a refresh is required so that the application can get an updated set of actions. 13491 */ 13492 supportsRequests: true, 13493 13494 /** 13495 * @class 13496 * A MediaDialog is an attempted connection between or among multiple participants, 13497 * for example, a chat or email. 13498 * 13499 * @augments finesse.restservices.DialogBase 13500 * @constructs 13501 */ 13502 _fakeConstuctor: function () { 13503 /* This is here to hide the real init constructor from the public docs */ 13504 }, 13505 13506 /** 13507 * @private 13508 * 13509 * @param {Object} options 13510 * An object with the following properties:<ul> 13511 * <li><b>id:</b> The id of the object being constructed</li> 13512 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13513 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13514 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13515 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13516 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13517 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13518 * <li><b>content:</b> {String} Raw string of response</li> 13519 * <li><b>object:</b> {Object} Parsed object of response</li> 13520 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13521 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13522 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13523 * </ul></li> 13524 * </ul></li> 13525 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13526 **/ 13527 init: function (options) { 13528 this._super(options); 13529 }, 13530 13531 /** 13532 * @private 13533 * Gets the REST class for the current object - this is the MediaDialog class. 13534 * @returns {Object} The Dialog class. 13535 */ 13536 getRestClass: function () { 13537 return MediaDialog; 13538 }, 13539 13540 /** 13541 * Transfers a Media Dialog to the target specified 13542 * @param {String} target script selector 13543 * The script selector to transfer the dialog. 13544 * @param {finesse.interfaces.RequestHandlers} handlers 13545 * An object containing the handlers for the request 13546 */ 13547 transfer: function(target, handlers) { 13548 this.setTaskState(MediaDialog.TaskActions.TRANSFER, handlers, target); 13549 }, 13550 13551 /** 13552 * Set the state on a Media Dialog based on the action given. 13553 * @param {finesse.restservices.MediaDialog.TaskActions} action 13554 * The action string indicating the action to invoke on a Media dialog. 13555 * @param {finesse.interfaces.RequestHandlers} handlers 13556 * An object containing the handlers for the request 13557 * @param {String} target 13558 * The target to transfer the dialog. Pass null if not transfer 13559 * 13560 * @example 13561 * _mediaDialog.setTaskState(finesse.restservices.MediaDialog.TaskActions.ACCEPT, 13562 * { 13563 * success: _handleAcceptSuccess, 13564 * error: _handleAcceptError 13565 * }, 13566 * null); 13567 */ 13568 setTaskState: function (state,handlers,target) { 13569 this.isLoaded(); 13570 13571 var contentBody = {}; 13572 contentBody[this.getRestType()] = { 13573 "requestedAction": state, 13574 "target": target 13575 }; 13576 // (nonexistent) keys to be read as undefined 13577 handlers = handlers || {}; 13578 this.restRequest(this.getRestUrl(), { 13579 method: 'PUT', 13580 success: handlers.success, 13581 error: handlers.error, 13582 content: contentBody 13583 }); 13584 return this; // Allow cascading 13585 } 13586 13587 }); 13588 13589 MediaDialog.TaskActions = /** @lends finesse.restservices.MediaDialog.TaskActions.prototype */ { 13590 /** 13591 * Accept an incoming task. 13592 */ 13593 ACCEPT: "ACCEPT", 13594 /** 13595 * Start work on a task. 13596 */ 13597 START : "START", 13598 /** 13599 * Pause work on an active task. 13600 */ 13601 PAUSE: "PAUSE", 13602 /** 13603 * Resume work on a paused task. 13604 */ 13605 RESUME : "RESUME", 13606 /** 13607 * Wrap up work for a task. 13608 */ 13609 WRAP_UP : "WRAP_UP", 13610 /** 13611 * Transfer task to another target. 13612 */ 13613 TRANSFER : "TRANSFER", 13614 /** 13615 * End a task. 13616 */ 13617 CLOSE : "CLOSE", 13618 /** 13619 * @class Set of action constants for a Media Dialog. These should be used for 13620 * {@link finesse.restservices.MediaDialog#setTaskState}. 13621 * @constructs 13622 */ 13623 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13624 }; 13625 13626 13627 13628 MediaDialog.States = /** @lends finesse.restservices.MediaDialog.States.prototype */ { 13629 /** 13630 * Indicates that the task has been offered to an agent. 13631 */ 13632 OFFERED: "OFFERED", 13633 /** 13634 * Indicates that the user has started work on the task. 13635 */ 13636 ACTIVE: "ACTIVE", 13637 /** 13638 * Indicates that the user has paused work on the task. 13639 */ 13640 PAUSED: "PAUSED", 13641 /** 13642 * Indicates that the user is wrapping up the task. 13643 */ 13644 WRAPPING_UP: "WRAPPING_UP", 13645 /** 13646 * Indicates that the task was interrupted. 13647 */ 13648 INTERRUPTED: "INTERRUPTED", 13649 /** 13650 * Indicates that the task has ended. 13651 */ 13652 CLOSED: "CLOSED", 13653 /** 13654 * Indicates that the user has accepted the task. 13655 */ 13656 ACCEPTED: "ACCEPTED", 13657 /** 13658 * Finesse has recovered a task after a failure. It does not have enough information to build a complete set 13659 * of actions for the task so it only allows the user to end the task. 13660 */ 13661 UNKNOWN: "UNKNOWN", 13662 /** 13663 * @class Possible Dialog State constants. 13664 * The State flow of a typical in-bound Dialog is as follows: OFFERED, ACCEPTED, ACTIVE, CLOSED. 13665 * @constructs 13666 */ 13667 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13668 }; 13669 13670 MediaDialog.ParticipantStates = MediaDialog.States; 13671 13672 window.finesse = window.finesse || {}; 13673 window.finesse.restservices = window.finesse.restservices || {}; 13674 window.finesse.restservices.MediaDialog = MediaDialog; 13675 13676 13677 return MediaDialog; 13678 }); 13679 13680 /* Simple JavaScript Inheritance 13681 * By John Resig http://ejohn.org/ 13682 * MIT Licensed. 13683 */ 13684 // Inspired by base2 and Prototype 13685 define('restservices/../../thirdparty/Class',[], function () { 13686 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 13687 // The base Class implementation (does nothing) 13688 /** @private */ 13689 Class = function(){}; 13690 13691 // Create a new Class that inherits from this class 13692 /** @private */ 13693 Class.extend = function(prop) { 13694 var _super = this.prototype; 13695 13696 // Instantiate a base class (but only create the instance, 13697 // don't run the init constructor) 13698 initializing = true; 13699 var prototype = new this(); 13700 initializing = false; 13701 13702 // Copy the properties over onto the new prototype 13703 for (var name in prop) { 13704 // Check if we're overwriting an existing function 13705 prototype[name] = typeof prop[name] == "function" && 13706 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 13707 (function(name, fn){ 13708 return function() { 13709 var tmp = this._super; 13710 13711 // Add a new ._super() method that is the same method 13712 // but on the super-class 13713 this._super = _super[name]; 13714 13715 // The method only need to be bound temporarily, so we 13716 // remove it when we're done executing 13717 var ret = fn.apply(this, arguments); 13718 this._super = tmp; 13719 13720 return ret; 13721 }; 13722 })(name, prop[name]) : 13723 prop[name]; 13724 } 13725 13726 // The dummy class constructor 13727 /** @private */ 13728 function Class() { 13729 // All construction is actually done in the init method 13730 if ( !initializing && this.init ) 13731 this.init.apply(this, arguments); 13732 } 13733 13734 // Populate our constructed prototype object 13735 Class.prototype = prototype; 13736 13737 // Enforce the constructor to be what we expect 13738 Class.prototype.constructor = Class; 13739 13740 // And make this class extendable 13741 Class.extend = arguments.callee; 13742 13743 return Class; 13744 }; 13745 return Class; 13746 }); 13747 13748 /** 13749 * Class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects for non-voice 13750 * dialog events. 13751 * 13752 * @requires Class 13753 * @requires finesse.clientservices.ClientServices 13754 * @requires finesse.clientservices.Topics 13755 */ 13756 /** @private */ 13757 define('restservices/MediaDialogsSubscriptionManager',[ 13758 "../../thirdparty/Class", 13759 "clientservices/ClientServices", 13760 "clientservices/Topics" 13761 ], 13762 function (Class, ClientServices, Topics) { 13763 var MediaDialogsSubscriptionManager = Class.extend(/** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */{ 13764 13765 /** 13766 * Map used to track the MediaDialogs objects managed by this object. 13767 * @private 13768 */ 13769 _mediaDialogsMap: {}, 13770 13771 /** 13772 * The regex used to match the source of BOSH/XMPP events. If an event matches this source, the event will 13773 * be processed by the subscription manager. 13774 * @private 13775 */ 13776 _sourceRegEx: null, 13777 13778 /** 13779 * The subscription ID/handle for Media/Dialogs events. 13780 * @private 13781 */ 13782 _subscriptionId: null, 13783 13784 _fakeConstuctor: function () 13785 { 13786 /* This is here to hide the real init constructor from the public docs */ 13787 }, 13788 13789 /** 13790 * Create the regex used to match the source of BOSH/XMPP events. If an event matches this source, the event 13791 * will be processed by the subscription manager. 13792 * 13793 * @param {Object} restObj 13794 * The restObj whose REST URL will be used as the base of the regex. 13795 * 13796 * @returns {RegExp} 13797 * The regex used to match the source of XMPP events. 13798 * @private 13799 */ 13800 _makeSourceRegEx: function(restObj) 13801 { 13802 return new RegExp("^" + restObj.getRestUrl() + "/Media/[0-9]+/Dialogs$"); 13803 }, 13804 13805 /** 13806 * Return the media ID associated with the update. 13807 * 13808 * @param {Object} update 13809 * The content of the update event. 13810 * 13811 * @returns {String} 13812 * The media ID associated with the update. 13813 * @private 13814 */ 13815 _getMediaIdFromEventUpdate: function(update) 13816 { 13817 var parts = update.object.Update.source.split("/"); 13818 return parts[MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE]; 13819 }, 13820 13821 /** 13822 * Handler for update events. This handler forwards the update to the MediaDialogs object associated with 13823 * the media ID carried in the update event. 13824 * 13825 * @param {Object} update 13826 * The content of the update event. 13827 * 13828 * @private 13829 */ 13830 _updateEventHandler: function(update) 13831 { 13832 var mediaId = this._getMediaIdFromEventUpdate(update), 13833 mediaDialogs = this._mediaDialogsMap[mediaId]; 13834 13835 if ( mediaDialogs ) 13836 { 13837 mediaDialogs._updateEventHandler(mediaDialogs, update); 13838 } 13839 }, 13840 13841 /** 13842 * Return the media ID associated with the REST update. 13843 * 13844 * @param {Object} update 13845 * The content of the REST update. 13846 * 13847 * @returns {String} 13848 * The media ID associated with the update. 13849 * @private 13850 */ 13851 _getMediaIdFromRestUpdate: function(update) 13852 { 13853 return update.object.Update.data.dialog.mediaProperties.mediaId; 13854 }, 13855 13856 /** 13857 * Handler for REST updates. This handler forwards the update to the MediaDialogs object associated with 13858 * the media ID carried in the REST update. 13859 * 13860 * @param {Object} update 13861 * The content of the REST update. 13862 * 13863 * @private 13864 */ 13865 _processRestItemUpdate: function(update) 13866 { 13867 var mediaId = this._getMediaIdFromRestUpdate(update), 13868 mediaDialogs = this._mediaDialogsMap[mediaId]; 13869 13870 if ( mediaDialogs ) 13871 { 13872 mediaDialogs._processRestItemUpdate(update); 13873 } 13874 }, 13875 13876 /** 13877 * Utility method to create a callback to be given to OpenAjax to invoke when a message 13878 * is published on the topic of our REST URL (also XEP-0060 node). 13879 * This needs to be its own defined method so that subclasses can have their own implementation. 13880 * @returns {Function} callback(update) 13881 * The callback to be invoked when an update event is received. This callback will 13882 * process the update by notifying the MediaDialogs object associated with the media ID in the update. 13883 * 13884 * @private 13885 */ 13886 _createPubsubCallback: function () 13887 { 13888 var _this = this; 13889 return function (update) { 13890 //If the source of the update is our REST URL, this means the collection itself is modified 13891 if (update.object.Update.source.match(_this._sourceRegEx)) { 13892 _this._updateEventHandler(update); 13893 } else { 13894 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 13895 //rest item update of one of our children that was published on our node (OpenAjax topic) 13896 _this._processRestItemUpdate(update); 13897 } 13898 }; 13899 }, 13900 13901 /** 13902 * Track the MediaDialogs object so that events and REST updates signalled to this subscription manager 13903 * can be forwarded to the given MediaDialogs object. 13904 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to be tracked by the 13905 * subscription manager. 13906 * @private 13907 */ 13908 _manage: function(mediaDialogs) 13909 { 13910 this._mediaDialogsMap[mediaDialogs.getMedia().getMediaId()] = mediaDialogs; 13911 }, 13912 13913 /** 13914 * Stop tracking the MediaDialogs object. Events and REST updates signalled to this subscription manager 13915 * will no longer be forwarded to the given MediaDialogs object. 13916 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to no longer track. 13917 * @private 13918 */ 13919 _unManage: function(mediaDialogs) 13920 { 13921 var mediaId = mediaDialogs.getMedia().getMediaId(); 13922 if ( this._callbackMap[mediaId] ) 13923 { 13924 delete this._callbackMap[mediaId]; 13925 } 13926 }, 13927 13928 /** 13929 * @class 13930 * An internal class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects 13931 * for non-voice dialog events. 13932 * 13933 * @constructor 13934 * @param {RestBase} restObj 13935 * A RestBase object used to build the user portion of XMPP and REST paths. 13936 * @constructs 13937 * @private 13938 */ 13939 init: function (restObj) 13940 { 13941 var _this; 13942 13943 this._sourceRegEx = this._makeSourceRegEx(restObj); 13944 }, 13945 13946 /** 13947 * Create the BOSH/XMPP subscription used for non-voice dialog events. Additionally, store the given 13948 * MediaDialogs object so that events for the object can be forwarded to it. 13949 * 13950 * @param {finesse.restservices.MediaDialogs} mediaDialogs a MediaDialogs object to manage (forward events) 13951 * @param {Object} callbacks an object containing success and error callbacks used to signal the result of 13952 * the subscription. 13953 * @returns {MediaDialogsSubscriptionManager} 13954 * @private 13955 */ 13956 subscribe: function (mediaDialogs, callbacks) 13957 { 13958 var topic = Topics.getTopic(mediaDialogs.getXMPPNodePath()), 13959 _this = mediaDialogs, 13960 handlers, 13961 successful; 13962 13963 callbacks = callbacks || {}; 13964 13965 handlers = { 13966 /** @private */ 13967 success: function () { 13968 // Add item to the refresh list in ClientServices to refresh if 13969 // we recover due to our resilient connection. 13970 ClientServices.addToRefreshList(_this); 13971 if (typeof callbacks.success === "function") { 13972 callbacks.success(); 13973 } 13974 }, 13975 /** @private */ 13976 error: function (err) { 13977 if (successful) { 13978 _this._unManage(mediaDialogs); 13979 ClientServices.unsubscribe(topic); 13980 } 13981 13982 if (typeof callbacks.error === "function") { 13983 callbacks.error(err); 13984 } 13985 } 13986 }; 13987 13988 this._manage(mediaDialogs); 13989 if ( this._subscriptionId ) 13990 { 13991 successful = true; 13992 } 13993 else 13994 { 13995 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 13996 if ( successful ) 13997 { 13998 this._subscriptionId = "OpenAjaxOnly"; 13999 } 14000 } 14001 14002 if (successful) { 14003 handlers.success(); 14004 } else { 14005 handlers.error(); 14006 } 14007 14008 return this; 14009 } 14010 }); 14011 14012 MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE = 6; 14013 14014 window.finesse = window.finesse || {}; 14015 window.finesse.restservices = window.finesse.restservices || {}; 14016 window.finesse.restservices.MediaDialogsSubscriptionManager = MediaDialogsSubscriptionManager; 14017 14018 return MediaDialogsSubscriptionManager; 14019 }); 14020 14021 /** 14022 * JavaScript representation of the Finesse MediaDialogs collection 14023 * object which contains a list of Dialog objects. 14024 * 14025 * @requires finesse.clientservices.ClientServices 14026 * @requires Class 14027 * @requires finesse.FinesseBase 14028 * @requires finesse.restservices.RestBase 14029 * @requires finesse.restservices.Dialogs 14030 * @requires finesse.restservices.MediaDialogsSubscriptionManager 14031 */ 14032 /** @private */ 14033 define('restservices/MediaDialogs',[ 14034 'restservices/RestCollectionBase', 14035 'restservices/RestBase', 14036 'restservices/Dialogs', 14037 'restservices/MediaDialog', 14038 'restservices/MediaDialogsSubscriptionManager' 14039 ], 14040 function (RestCollectionBase, RestBase, Dialogs, MediaDialog, MediaDialogsSubscriptionManager) { 14041 var MediaDialogs = Dialogs.extend(/** @lends finesse.restservices.MediaDialogs.prototype */{ 14042 14043 /** 14044 * @class 14045 * JavaScript representation of a collection of Dialogs for a specific non-voice Media. 14046 * @augments finesse.restservices.Dialogs 14047 * @constructs 14048 * @see finesse.restservices.Dialog 14049 * @example 14050 * _MediaDialogs = _media.getMediaDialogs( { 14051 * onCollectionAdd : _handleDialogAdd, 14052 * onCollectionDelete : _handleDialogDelete, 14053 * onLoad : _handleMediaDialogsLoaded 14054 * }); 14055 * 14056 * _dialogCollection = _MediaDialogs.getCollection(); 14057 * for (var dialogId in _dialogCollection) { 14058 * if (_dialogCollection.hasOwnProperty(dialogId)) { 14059 * _dialog = _dialogCollection[dialogId]; 14060 * etc... 14061 * } 14062 * } 14063 */ 14064 _fakeConstuctor: function () { 14065 /* This is here to hide the real init constructor from the public docs */ 14066 }, 14067 14068 /** 14069 * @private 14070 * @param {Object} options 14071 * An object with the following properties:<ul> 14072 * <li><b>id:</b> The id of the object being constructed</li> 14073 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14074 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14075 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14076 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14077 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14078 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14079 * <li><b>content:</b> {String} Raw string of response</li> 14080 * <li><b>object:</b> {Object} Parsed object of response</li> 14081 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14082 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14083 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14084 * </ul></li> 14085 * </ul></li> 14086 * <li><b>parentObj:</b> The parent object</li></ul> 14087 * <li><b>mediaObj:</b> The media object</li></ul> 14088 **/ 14089 init: function (options) { 14090 this._mediaObj = options.mediaObj; 14091 this._super(options); 14092 }, 14093 14094 /** 14095 * Returns the associated Media object. 14096 * @returns {finesse.restservices.Media} the associated media object. 14097 */ 14098 getMedia: function() { 14099 return this._mediaObj; 14100 }, 14101 14102 /** 14103 * @private 14104 * Gets the REST class for the objects that make up the collection. - this 14105 * is the Dialog class. 14106 */ 14107 getRestItemClass: function () { 14108 return MediaDialog; 14109 }, 14110 14111 /** 14112 * @private 14113 * Gets the node path for the current object - this is the media node 14114 * @returns {String} The node path 14115 */ 14116 getXMPPNodePath: function () { 14117 var 14118 restObj = this._restObj, 14119 nodePath = ""; 14120 14121 //Prepend the base REST object if one was provided. 14122 if (restObj instanceof RestBase) { 14123 nodePath += restObj.getRestUrl(); 14124 } 14125 //Otherwise prepend with the default webapp name. 14126 else { 14127 nodePath += "/finesse/api"; 14128 } 14129 14130 //Append the REST type. 14131 nodePath += "/" + this.getRestType() + "/Media"; 14132 return nodePath; 14133 }, 14134 14135 /** 14136 * The REST URL in which this object can be referenced. 14137 * @return {String} 14138 * The REST URI for this object. 14139 * @private 14140 */ 14141 getRestUrl: function () { 14142 var 14143 restObj = this._mediaObj, 14144 restUrl = ""; 14145 14146 //Prepend the base REST object if one was provided. 14147 if (restObj instanceof RestBase) { 14148 restUrl += restObj.getRestUrl(); 14149 } 14150 //Otherwise prepend with the default webapp name. 14151 else { 14152 restUrl += "/finesse/api"; 14153 } 14154 14155 //Append the REST type. 14156 restUrl += "/" + this.getRestType(); 14157 14158 //Append ID if it is not undefined, null, or empty. 14159 if (this._id) { 14160 restUrl += "/" + this._id; 14161 } 14162 return restUrl; 14163 }, 14164 14165 /** 14166 * Overridden so that MediaDialogsSubscriptionManager can be used to share events across media dialogs. 14167 * 14168 * @param {Object} callbacks 14169 * An object containing success and error handlers for the subscription request. 14170 * @private 14171 */ 14172 subscribe: function (callbacks) 14173 { 14174 if ( !MediaDialogs.subscriptionManager ) 14175 { 14176 MediaDialogs.subscriptionManager = new MediaDialogsSubscriptionManager(this._restObj); 14177 } 14178 14179 MediaDialogs.subscriptionManager.subscribe(this, callbacks); 14180 14181 return this; 14182 } 14183 }); 14184 14185 MediaDialogs.subscriptionManager = /** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */ null; 14186 14187 window.finesse = window.finesse || {}; 14188 window.finesse.restservices = window.finesse.restservices || {}; 14189 window.finesse.restservices.MediaDialogs = MediaDialogs; 14190 14191 return MediaDialogs; 14192 }); 14193 14194 /** 14195 * Utility class used to recover a media object after recovering from a connection or system failure. 14196 * 14197 * @requires Class 14198 * @requires finesse.restservices.Notifier 14199 * @requires finesse.clientservices.ClientServices 14200 * @requires finesse.restservices.Media 14201 */ 14202 14203 /** @private */ 14204 define('restservices/MediaOptionsHelper',['../../thirdparty/Class', 14205 '../utilities/Utilities', 14206 '../clientservices/ClientServices', 14207 '../cslogger/ClientLogger' 14208 ], 14209 function (Class, Utilities, ClientServices, ClientLogger) 14210 { 14211 /** 14212 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 14213 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 14214 * interruptAction, and dialogLogoutAction as the previous Finesse server. 14215 */ 14216 var MediaOptionsHelper = Class.extend(/** @lends finesse.restservices.MediaOptionsHelper.prototype */ 14217 { 14218 /** 14219 * @private 14220 * 14221 * The media that this helper is responsible for recovering in case of failover. 14222 */ 14223 _media: null, 14224 14225 /** 14226 * @private 14227 * 14228 * The media options that this helper will ensure are set properly across failures. 14229 */ 14230 _mediaOptions: null, 14231 14232 /** 14233 * @private 14234 * 14235 * The current state of the failover recovery. 14236 */ 14237 _state: null, 14238 14239 /** 14240 * @class 14241 * 14242 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 14243 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 14244 * interruptAction, and dialogLogoutAction as the previous Finesse server. 14245 * 14246 * @constructs 14247 */ 14248 _fakeConstuctor: function () 14249 { 14250 /* This is here to hide the real init constructor from the public docs */ 14251 }, 14252 14253 /** 14254 * Utility method to format a message logged by an instance of this class. 14255 * 14256 * @param {string} message the message to format 14257 * @returns {string} the given message prefixed with the name of this class and the ID of the Media object 14258 * associated with this class. 14259 * @private 14260 */ 14261 _formatLogMessage: function(message) 14262 { 14263 return "MediaOptionsHelper[" + this.media.getMediaId() + "]: " + message; 14264 }, 14265 14266 /** 14267 * Utility method to log an informational message. 14268 * 14269 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 14270 * logger, this class will not log. 14271 * 14272 * @param {string} message the message to log 14273 * @private 14274 */ 14275 _log: function(message) 14276 { 14277 ClientLogger.log(this._formatLogMessage(message)); 14278 }, 14279 14280 /** 14281 * Utility method to log an error message. 14282 * 14283 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 14284 * logger, this class will not log. 14285 * 14286 * @param {string} message the message to log 14287 * @private 14288 */ 14289 _error: function(message) 14290 { 14291 ClientLogger.error(this._formatLogMessage(message)); 14292 }, 14293 14294 /** 14295 * @private 14296 * 14297 * Set the running state of this failover helper. 14298 * 14299 * @param {String} newState the new state of the failover helper. 14300 */ 14301 _setState: function(newState) 14302 { 14303 this._state = newState; 14304 this._log("changed state to " + this._state); 14305 }, 14306 14307 /** 14308 * Check the given media object to see if the maxDialogLimit, interruptAction, and dialogLogoutAction options 14309 * need to be reset. These options need to be reset if the application specified login options and any of the 14310 * following conditions are true:<ul> 14311 * <li>the dialogLogoutAction in the given media object does not match the action set by the application</li> 14312 * <li>the interruptAction in the given media object does not match the action set by the application</li> 14313 * <li>the maxDialogLimit in the given media object does not match the limit set by the application</li></ul> 14314 * 14315 * @param {Object} media the media object to evaluate 14316 * @returns {*|{}|boolean} true if a login request should be sent to correct the media options 14317 * @private 14318 */ 14319 _shouldLoginToFixOptions: function(media) 14320 { 14321 return this._mediaOptions 14322 && media.isLoggedIn() 14323 && (media.getDialogLogoutAction() !== this._mediaOptions.dialogLogoutAction 14324 || media.getInterruptAction() !== this._mediaOptions.interruptAction 14325 || media.getMaxDialogLimit() !== this._mediaOptions.maxDialogLimit); 14326 }, 14327 14328 /** 14329 * @private 14330 * 14331 * Determine if the given response is an "agent already logged in" error. 14332 * 14333 * @param {Object} response the response to evaluate 14334 * 14335 * @returns {boolean} true if 14336 */ 14337 _agentIsAlreadyLoggedIn: function(response) 14338 { 14339 return response 14340 && response.object 14341 && response.object.ApiErrors 14342 && response.object.ApiErrors.ApiError 14343 && response.object.ApiErrors.ApiError.ErrorMessage === "E_ARM_STAT_AGENT_ALREADY_LOGGED_IN"; 14344 }, 14345 14346 /** 14347 * Determine if the given response to a media login request is successful. A response is successful under these 14348 * conditions:<ul> 14349 * <li>the response has a 202 status</li> 14350 * <li>the response has a 400 status and the error indicates the agent is already logged in</li> 14351 * </ul> 14352 * 14353 * @param {Object} loginResponse the response to evaluate 14354 * 14355 * @returns {*|boolean} true if the response status is 202 or if the response status is 400 and the error states 14356 * that the agent is already logged in. 14357 * @private 14358 */ 14359 _isSuccessfulLoginResponse: function(loginResponse) 14360 { 14361 return loginResponse && ((loginResponse.status === 202) || this._agentIsAlreadyLoggedIn(loginResponse)); 14362 }, 14363 14364 /** 14365 * Process a media load or change while in the connected state. This involves checking the media options to 14366 * ensure they are the same as those set by the application. 14367 * 14368 * @param {Object} media the media object that was loaded or changed. 14369 * @private 14370 */ 14371 _processConnectedState: function(media) 14372 { 14373 var _this = this, processResponse; 14374 14375 if ( this._shouldLoginToFixOptions(media) ) 14376 { 14377 processResponse = function(response) 14378 { 14379 _this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 14380 14381 if ( !_this._isSuccessfulLoginResponse(response) ) 14382 { 14383 _this._error("failed to reset options: " + response.status + ": " + response.content); 14384 } 14385 }; 14386 14387 this._setState(MediaOptionsHelper.States.SETTING_OPTIONS); 14388 14389 this._log("logging in to fix options"); 14390 14391 this.media.login({ 14392 dialogLogoutAction: _this._mediaOptions.dialogLogoutAction, 14393 interruptAction: _this._mediaOptions.interruptAction, 14394 maxDialogLimit: _this._mediaOptions.maxDialogLimit, 14395 handlers: { 14396 success: processResponse, 14397 error: processResponse 14398 } 14399 }); 14400 } 14401 }, 14402 14403 /** 14404 * Process a media load or change while in the resetting options state. All that is done in this state is log a 14405 * message that a reset is already in progress. 14406 * 14407 * @param {Object} media the media object that was loaded or changed. 14408 * @private 14409 */ 14410 _processResettingOptionsState: function(media) 14411 { 14412 this._log("Resetting options is in progress"); 14413 }, 14414 14415 /** 14416 * Initialize a helper class used to recover media objects following connectivity or component failures related 14417 * to Finesse and/or CCE services. 14418 * 14419 * Initialize the failover helper to manage the recovery of the given media object. 14420 * 14421 * @param {Object} media the media object to recover 14422 * @param {Object} mediaOptions an object containing the media options used by the application:<ul> 14423 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 14424 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14425 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14426 */ 14427 init: function (media, mediaOptions) 14428 { 14429 var _this = this, processMediaStateChange = function(media) 14430 { 14431 switch ( _this._state ) 14432 { 14433 case MediaOptionsHelper.States.MONITORING_OPTIONS: 14434 _this._processConnectedState(media); 14435 break; 14436 case MediaOptionsHelper.States.SETTING_OPTIONS: 14437 _this._processResettingOptionsState(media); 14438 break; 14439 default: 14440 _this._error("unexpected state: " + _this.state); 14441 break; 14442 } 14443 }; 14444 14445 this.media = media; 14446 14447 this._mediaOptions = mediaOptions || {}; 14448 14449 // The maxDialogLimit is a string in media events. Ensure _mediaOptions.maxDialogLimit is a string to 14450 // make sure it can be compared to the maxDialogLimit field in media events. 14451 // 14452 if ( this._mediaOptions.maxDialogLimit ) 14453 { 14454 this._mediaOptions.maxDialogLimit = this._mediaOptions.maxDialogLimit.toString(); 14455 } 14456 14457 // Add the media object to the refresh list so that ClientServices calls refresh on the media object when 14458 // the connection is reestablished. This will trigger processMediaStateChange() which will trigger a login 14459 // to restore media options if media options are different. 14460 // 14461 ClientServices.addToRefreshList(this.media); 14462 14463 this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 14464 14465 this.media.addHandler('load', processMediaStateChange); 14466 this.media.addHandler('change', processMediaStateChange); 14467 14468 this._log("initialized"); 14469 } 14470 }); 14471 14472 /** 14473 * @private 14474 * 14475 * The states that a running MediaOptionsHelper executes. 14476 * 14477 * @type {{CONNECTED: string, RECOVERING: string, RECOVERY_LOGIN: string, _fakeConstructor: _fakeConstructor}} 14478 */ 14479 MediaOptionsHelper.States = /** @lends finesse.restservices.MediaOptionsHelper.States.prototype */ { 14480 14481 /** 14482 * The media is synchronized with the Finesse server. Media options are being monitored for changes. 14483 */ 14484 MONITORING_OPTIONS: "MONITORING_OPTIONS", 14485 14486 /** 14487 * A media login request has been sent to Finesse to set the correct media options. 14488 */ 14489 SETTING_OPTIONS: "SETTING_OPTIONS", 14490 14491 /** 14492 * @class Possible MediaOptionsHelper state values. 14493 * @constructs 14494 */ 14495 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 14496 }; 14497 14498 window.finesse = window.finesse || {}; 14499 window.finesse.restservices = window.finesse.restservices || {}; 14500 window.finesse.restservices.MediaOptionsHelper = MediaOptionsHelper; 14501 14502 return MediaOptionsHelper; 14503 }); 14504 /** 14505 * JavaScript representation of the Finesse Media object 14506 * 14507 * @requires finesse.clientservices.ClientServices 14508 * @requires Class 14509 * @requires finesse.FinesseBase 14510 * @requires finesse.restservices.RestBase 14511 * @requires finesse.restservices.MediaDialogs 14512 * @requires finesse.restservices.MediaOptionsHelper 14513 */ 14514 14515 /** @private */ 14516 define('restservices/Media',[ 14517 'restservices/RestBase', 14518 'restservices/MediaDialogs', 14519 'restservices/MediaOptionsHelper' 14520 ], 14521 function (RestBase, MediaDialogs, MediaOptionsHelper) { 14522 var Media = RestBase.extend(/** @lends finesse.restservices.Media.prototype */{ 14523 14524 /** 14525 * @private 14526 * Media objects support GET REST requests. 14527 */ 14528 supportsRequests: true, 14529 14530 /** 14531 * @private 14532 * The list of dialogs associated with this media. 14533 */ 14534 _mdialogs : null, 14535 14536 /** 14537 * @private 14538 * The options used to log into this media. 14539 */ 14540 _mediaOptions: null, 14541 14542 /** 14543 * @private 14544 * Object used to keep the maxDialogLimit, interruptAction, and dialogLogoutAction in-synch across fail-overs. 14545 */ 14546 _optionsHelper: null, 14547 14548 /** 14549 * @class 14550 * A Media represents a non-voice channel, 14551 * for example, a chat or a email. 14552 * 14553 * @augments finesse.restservices.RestBase 14554 * @constructs 14555 */ 14556 _fakeConstuctor: function () { 14557 /* This is here to hide the real init constructor from the public docs */ 14558 }, 14559 14560 /** 14561 * @private 14562 * 14563 * @param {Object} options 14564 * An object with the following properties:<ul> 14565 * <li><b>id:</b> The id of the object being constructed</li> 14566 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14567 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14568 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14569 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14570 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14571 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14572 * <li><b>content:</b> {String} Raw string of response</li> 14573 * <li><b>object:</b> {Object} Parsed object of response</li> 14574 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14575 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14576 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14577 * </ul></li> 14578 * </ul></li> 14579 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14580 **/ 14581 init: function (options) { 14582 this._super(options); 14583 }, 14584 14585 /** 14586 * @private 14587 * Utility method used to retrieve an attribute from this media object's underlying data. 14588 * 14589 * @param {String} attributeName the name of the attribute to retrieve 14590 * @returns {String} the value of the attribute or undefined if the attribute is not found 14591 */ 14592 _getDataAttribute: function(attributeName) { 14593 this.isLoaded(); 14594 return this.getData()[attributeName]; 14595 }, 14596 14597 /** 14598 * @private 14599 * Gets the REST class for the current object - this is the Media class. 14600 * @returns {Object} The Media class. 14601 */ 14602 getRestClass: function () { 14603 return Media; 14604 }, 14605 14606 /** 14607 * @private 14608 * Gets the REST type for the current object - this is a "Media". 14609 * @returns {String} The Media string. 14610 */ 14611 getRestType: function () { 14612 return "Media"; 14613 }, 14614 14615 /** 14616 * @private 14617 * Getter for the uri. 14618 * @returns {String} The uri. 14619 */ 14620 getMediaUri: function () { 14621 return this._getDataAttribute('uri'); 14622 }, 14623 14624 /** 14625 * Returns the id. 14626 * @returns {String} The id. 14627 */ 14628 getId: function () { 14629 return this._getDataAttribute('id'); 14630 }, 14631 14632 /** 14633 * Returns the name. 14634 * @returns {String} The name. 14635 */ 14636 getName: function () { 14637 return this._getDataAttribute('name'); 14638 }, 14639 14640 /** 14641 * Returns the reason code id. 14642 * @returns {String} The reason code id. 14643 */ 14644 getReasonCodeId: function () { 14645 return this._getDataAttribute('reasonCodeId'); 14646 }, 14647 14648 /** 14649 * Returns the reason code label. 14650 * @returns {String} The reason code label. 14651 */ 14652 getReasonCodeLabel: function() { 14653 this.isLoaded(); 14654 if (this.getData().reasonCode) { 14655 return this.getData.reasonCode.label; 14656 } 14657 return ""; 14658 }, 14659 14660 /** 14661 * Returns the action to be taken in the event this media is interrupted. The action will be one of the 14662 * following:<ul> 14663 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on tasks in this media 14664 * until the media is no longer interrupted.</li> 14665 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on the task while the 14666 * media is interrupted.</li></ul> 14667 * @returns {*|Object} 14668 */ 14669 getInterruptAction: function() { 14670 return this._getDataAttribute('interruptAction'); 14671 }, 14672 14673 /** 14674 * Returns the action to be taken in the event the agent logs out with dialogs associated with this media. 14675 * The action will be one of the following:<ul> 14676 * <li><b>CLOSE:</b> the dialog will be closed.</li> 14677 * <li><b>TRANSFER:</b> the dialog will be transferred to another agent.</li></ul> 14678 * @returns {*|Object} 14679 */ 14680 getDialogLogoutAction: function() { 14681 return this._getDataAttribute('dialogLogoutAction'); 14682 }, 14683 14684 /** 14685 * Returns the state of the User on this Media. 14686 * @returns {String} 14687 * The current (or last fetched) state of the User on this Media 14688 * @see finesse.restservices.Media.States 14689 */ 14690 getState: function() { 14691 return this._getDataAttribute('state'); 14692 }, 14693 14694 /** 14695 * Returns the Media id 14696 * @returns {String} The Media id 14697 */ 14698 getMediaId: function() { 14699 return this._getDataAttribute('id'); 14700 }, 14701 14702 /** 14703 * Returns maximum number of dialogs allowed on this Media 14704 * @returns {String} The maximum number of Dialogs on this Media 14705 */ 14706 getMaxDialogLimit: function() { 14707 return this._getDataAttribute('maxDialogLimit'); 14708 }, 14709 14710 /** 14711 * Returns true if this media is interruptible 14712 * @returns {Boolean} true if interruptible; false otherwise 14713 */ 14714 getInterruptible: function() { 14715 var interruptible = this._getDataAttribute('interruptible'); 14716 return interruptible === 'true'; 14717 }, 14718 14719 /** 14720 * Returns true if the user interruptible on this Media. 14721 * @returns {Boolean} true if interruptible; false otherwise 14722 */ 14723 isInterruptible: function() { 14724 return this.getInterruptible(); 14725 }, 14726 14727 /** 14728 * Returns true if the user is routable on this Media 14729 * @returns {Boolean} true if routable, false otherwise 14730 */ 14731 getRoutable: function() { 14732 var routable = this._getDataAttribute('routable'); 14733 return routable === 'true'; 14734 }, 14735 14736 /** 14737 * Returns true if the user is routable on this Media. 14738 * @returns {Boolean} true if routable, false otherwise 14739 */ 14740 isRoutable: function() { 14741 return this.getRoutable(); 14742 }, 14743 14744 /** 14745 * Sets the routable status of this media 14746 * . 14747 * @param {Object} options 14748 * An object with the following properties:<ul> 14749 * <li><b>routable:</b> true if the agent is routable, false otherwise</li> 14750 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14751 * @returns {finesse.restservices.Media} 14752 * This Media object, to allow cascading 14753 */ 14754 setRoutable: function(params) { 14755 var handlers, contentBody = {}, 14756 restType = this.getRestType(), 14757 url = this.getRestUrl(); 14758 params = params || {}; 14759 14760 contentBody[restType] = { 14761 "routable": params.routable 14762 }; 14763 14764 handlers = params.handlers || {}; 14765 14766 this._makeRequest(contentBody, url, handlers); 14767 14768 return this; 14769 }, 14770 14771 /** 14772 * @private 14773 * Invoke a request to the server given a content body and handlers. 14774 * 14775 * @param {Object} contentBody 14776 * A JS object containing the body of the action request. 14777 * @param {finesse.interfaces.RequestHandlers} handlers 14778 * An object containing the handlers for the request 14779 */ 14780 _makeRequest: function (contentBody, url, handlers) { 14781 // Protect against null dereferencing of options allowing its 14782 // (nonexistent) keys to be read as undefined 14783 handlers = handlers || {}; 14784 14785 this.restRequest(url, { 14786 method: 'PUT', 14787 success: handlers.success, 14788 error: handlers.error, 14789 content: contentBody 14790 }); 14791 }, 14792 14793 /** 14794 * Return true if the params object contains one of the following:<ul> 14795 * <li>maxDialogLimit</li> 14796 * <li>interruptAction</li> 14797 * <li>dialogLogoutAction</li></ul> 14798 * 14799 * @param {Object} params the parameters to evaluate 14800 * @returns {*|Boolean} 14801 * @private 14802 */ 14803 _containsLoginOptions: function(params) { 14804 return params.maxDialogLimit || params.interruptAction || params.dialogLogoutAction; 14805 }, 14806 14807 /** 14808 * Create login parameters using the given algorithm:<ul> 14809 * <li>if loginOptions have not be set in the call to MediaList.getMedia(), use the params given by the application</li> 14810 * <li>if no params were set by the application, use the loginOptions set in the call to MediaList.getMedia()</li> 14811 * <li>if the parameters given by the application contains login options, use the parameters given by the application</li> 14812 * <li>if login options were given by the application but callbacks were given, create new login params with the 14813 * loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login params</li></ul> 14814 * 14815 * @param params the parameters specified by the application in the call to Media.login() 14816 * 14817 * @see finesse.restservices.Media#login 14818 * @see finesse.restservices.MediaList#getMedia 14819 * 14820 * @returns {Object} login parameters built based on the algorithm listed above. 14821 * @private 14822 */ 14823 _makeLoginOptions: function(params) { 14824 if ( !this._mediaOptions ) { 14825 // If loginOptions have not be set, use the params given by the application. 14826 // 14827 return params; 14828 } 14829 14830 if ( !params ) { 14831 // If there were no params given by the application, use the loginOptions set in the call to 14832 // MediaList.getMedia(). 14833 // 14834 return this._mediaOptions; 14835 } 14836 14837 if ( this._containsLoginOptions(params) ) { 14838 // if the parameters given by the application contains login options, use the parameters given by the 14839 // application. 14840 // 14841 return params; 14842 } 14843 14844 // If login options were given by the application but callbacks were given, create new login params with the 14845 // loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login 14846 // params. 14847 // 14848 return { 14849 maxDialogLimit: this._mediaOptions.maxDialogLimit, 14850 interruptAction: this._mediaOptions.interruptAction, 14851 dialogLogoutAction: this._mediaOptions.dialogLogoutAction, 14852 handlers: params.handlers 14853 }; 14854 }, 14855 14856 /** 14857 * Ensure that the maxDialogLimit, interruptAction, and dialogLogoutAction options are kept in synch across 14858 * fail-overs for this media object. 14859 * @private 14860 */ 14861 _ensureOptionsAreInSynch: function() { 14862 if ( !this._optionsHelper && this._mediaOptions ) { 14863 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 14864 } 14865 }, 14866 14867 /** 14868 * Log the agent into this media. 14869 * 14870 * @param {Object} params 14871 * An object with the following properties:<ul> 14872 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 14873 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14874 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 14875 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14876 * 14877 * If maxDialogLimit, interruptAction, and dialogLogoutAction are not present, loginOptions specified in the 14878 * call to finesse.restservices.MediaList.getMedia() will be used. 14879 * 14880 * @see finesse.restservices.MediaList#getMedia 14881 * 14882 * @returns {finesse.restservices.Media} 14883 * This Media object, to allow cascading 14884 */ 14885 login: function(params) { 14886 this.setState(Media.States.LOGIN, null, this._makeLoginOptions(params)); 14887 return this; // Allow cascading 14888 }, 14889 14890 /** 14891 * Perform a logout for a user on this media. 14892 * 14893 * @param {String} reasonCode 14894 * The reason code for this user to logging out of this media. Pass null for no reason. 14895 * @param {Object} params 14896 * An object with the following properties:<ul> 14897 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14898 * 14899 * @returns {finesse.restservices.Media} 14900 * This Media object, to allow cascading 14901 */ 14902 logout: function(reasonCode, params) { 14903 var state = Media.States.LOGOUT; 14904 return this.setState(state, reasonCode, params); 14905 }, 14906 14907 /** 14908 * Set the state of the user on this Media. 14909 * 14910 * @param {String} newState 14911 * The new state to be set. 14912 * @param {ReasonCode} reasonCode 14913 * The reason code for the state change for this media. Pass null for no reason. 14914 * @param {Object} params 14915 * An object with the following properties:<ul> 14916 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 14917 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14918 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 14919 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14920 * 14921 * @see finesse.restservices.User.States 14922 * @returns {finesse.restservices.Media} 14923 * This Media object, to allow cascading 14924 * @example 14925 * _media.setState(finesse.restservices.Media.States.NOT_READY, 14926 * { 14927 * id: _reasonCodeId 14928 * }, 14929 * { 14930 * handlers: { 14931 * success: _handleStateChangeSuccess, 14932 * error : _handleStateChangeError 14933 * } 14934 * }); 14935 */ 14936 setState: function(state, reasonCode, params) { 14937 var handlers, reasonCodeId, contentBody = {}, 14938 restType = this.getRestType(), 14939 url = this.getRestUrl(); 14940 params = params || {}; 14941 14942 if(reasonCode) { 14943 reasonCodeId = reasonCode.id; 14944 } 14945 14946 contentBody[restType] = { 14947 "state": state, 14948 "maxDialogLimit": params.maxDialogLimit, 14949 "interruptAction": params.interruptAction, 14950 "dialogLogoutAction": params.dialogLogoutAction, 14951 "reasonCodeId": reasonCodeId 14952 }; 14953 14954 handlers = params.handlers || {}; 14955 14956 this._makeRequest(contentBody, url, handlers); 14957 14958 return this; 14959 }, 14960 14961 /** 14962 * Returns a MediaDialogs collection object that is associated with User on this Media. 14963 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14964 * applicable when Object has not been previously created). 14965 * @returns {finesse.restservices.MediaDialogs} 14966 * A MediaDialogs collection object. 14967 * @example 14968 * First call: 14969 * _mediaDialogs = _media.getMediaDialogs({ 14970 * onLoad : _handleMediaDialogsLoad, 14971 * onChange : _handleTeamChange, 14972 * onAdd: _handleMediaDialogAdd, 14973 * onDelete: _handleMediaDialogDelete, 14974 * onError: _errorHandler 14975 * }); 14976 * Subsequent calls on the same object, after the media dialogs are loaded: 14977 * ... 14978 * _mediaDialogsNew = _media.getMediaDialogs(); 14979 * _dialogsCollection = _mediaDialogsNew.getCollection(); 14980 * ... 14981 */ 14982 getMediaDialogs: function (callbacks) { 14983 var options = callbacks || {}; 14984 options.parentObj = this._restObj; 14985 options.mediaObj = this; 14986 this.isLoaded(); 14987 14988 if (this._mdialogs === null) { 14989 this._mdialogs = new MediaDialogs(options); 14990 } 14991 14992 return this._mdialogs; 14993 }, 14994 14995 /** 14996 * Refresh the dialog collection associated with this media. 14997 * The refresh will happen only if the media dialogs have been initialized. 14998 */ 14999 refreshMediaDialogs: function() { 15000 if ( this._mdialogs ) { 15001 this._mdialogs.refresh(); 15002 } 15003 }, 15004 15005 /** 15006 * Set the maxDialogLimit, interruptAction, and dialogLogoutAction settings that the application will use for 15007 * this media. In the event of a failure, these options will be set on the new Finesse server. 15008 * 15009 * @param {Object} mediaOptions an object with the following properties:<ul> 15010 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 15011 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 15012 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 15013 */ 15014 setMediaOptions: function(mediaOptions) { 15015 if ( mediaOptions ) { 15016 this._mediaOptions = mediaOptions; 15017 if ( !this._optionsHelper ) { 15018 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 15019 } 15020 } 15021 }, 15022 15023 /** 15024 * Refresh this media object and optionally refresh the list of media dialogs associated with this object. 15025 * 15026 * @param {Integer} retries the number of times to retry synchronizing this media object. 15027 */ 15028 refresh: function(retries) { 15029 retries = retries || 1; 15030 this._synchronize(retries); 15031 this.refreshMediaDialogs(); 15032 }, 15033 15034 /** 15035 * Returns true if the user in work state on this Media. 15036 * @returns {boolean} true if the media is in work state; false otherwise 15037 */ 15038 isInWorkState: function() { 15039 return this.getState() === Media.States.WORK; 15040 }, 15041 15042 /** 15043 * Returns true if the user in any state except LOGOUT on this Media. 15044 * @returns {boolean} returns true if the agent is in any state except LOGOUT in this media 15045 */ 15046 isLoggedIn: function() { 15047 var state = this.getState(); 15048 return state && state !== Media.States.LOGOUT; 15049 } 15050 }); 15051 15052 Media.States = /** @lends finesse.restservices.Media.States.prototype */ { 15053 /** 15054 * User Login on a non-voice Media. Note that while this is an action, is not technically a state, since a 15055 * logged-in User will always be in a specific state (READY, NOT_READY, etc.). 15056 */ 15057 LOGIN: "LOGIN", 15058 /** 15059 * User is logged out of this Media. 15060 */ 15061 LOGOUT: "LOGOUT", 15062 /** 15063 * User is not ready on this Media. 15064 */ 15065 NOT_READY: "NOT_READY", 15066 /** 15067 * User is ready for activity on this Media. 15068 */ 15069 READY: "READY", 15070 /** 15071 * User has a dialog coming in, but has not accepted it. 15072 */ 15073 RESERVED: "RESERVED", 15074 /** 15075 * The dialogs in this media have been interrupted by a dialog in a non-interruptible media. 15076 */ 15077 INTERRUPTED: "INTERRUPTED", 15078 /** 15079 * User enters this state when failing over from one Finesse to the other or when failing over from one 15080 * PG side to the other. 15081 */ 15082 WORK: "WORK", 15083 /** 15084 * @class Possible Media state values. Used in finesse.restservices.Media#setState. 15085 * @constructs 15086 */ 15087 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15088 15089 }; 15090 15091 window.finesse = window.finesse || {}; 15092 window.finesse.restservices = window.finesse.restservices || {}; 15093 window.finesse.restservices.Media = Media; 15094 15095 return Media; 15096 }); 15097 /** 15098 * JavaScript representation of the Finesse Media collection 15099 * object which contains a list of Media objects. 15100 * 15101 * @requires finesse.clientservices.ClientServices 15102 * @requires Class 15103 * @requires finesse.FinesseBase 15104 * @requires finesse.restservices.RestBase 15105 * @requires finesse.restservices.Media 15106 */ 15107 /** @private */ 15108 define('restservices/MediaList',[ 15109 'restservices/RestCollectionBase', 15110 'restservices/Media', 15111 'restservices/RestBase', 15112 'utilities/Utilities' 15113 ], 15114 function (RestCollectionBase, Media, RestBase, Utilities) { 15115 var MediaList = RestCollectionBase.extend(/** @lends finesse.restservices.MediaList.prototype */{ 15116 15117 /** 15118 * @class 15119 * JavaScript representation of a MediaList collection object. 15120 * @augments finesse.restservices.RestCollectionBase 15121 * @constructs 15122 * @see finesse.restservices.Media 15123 * @example 15124 * mediaList = _user.getMediaList( { 15125 * onCollectionAdd : _handleMediaAdd, 15126 * onCollectionDelete : _handleMediaDelete, 15127 * onLoad : _handleMediaListLoaded 15128 * }); 15129 * 15130 * _mediaCollection = mediaList.getCollection(); 15131 * for (var mediaId in _mediaCollection) { 15132 * if (_mediaCollection.hasOwnProperty(mediaId)) { 15133 * media = _mediaCollection[mediaId]; 15134 * etc... 15135 * } 15136 * } 15137 */ 15138 _fakeConstuctor: function () { 15139 /* This is here to hide the real init constructor from the public docs */ 15140 }, 15141 15142 /** 15143 * @private 15144 * @param {Object} options 15145 * An object with the following properties:<ul> 15146 * <li><b>id:</b> The id of the object being constructed</li> 15147 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15148 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15149 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15150 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15151 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15152 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15153 * <li><b>content:</b> {String} Raw string of response</li> 15154 * <li><b>object:</b> {Object} Parsed object of response</li> 15155 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15156 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15157 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15158 * </ul></li> 15159 * </ul></li> 15160 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15161 **/ 15162 init: function (options) { 15163 this._super(options); 15164 }, 15165 15166 /** 15167 * @private 15168 * Gets the REST class for the current object - this is the MediaList class. 15169 */ 15170 getRestClass: function () { 15171 return MediaList; 15172 }, 15173 15174 /** 15175 * @private 15176 * Gets the REST class for the objects that make up the collection. - this 15177 * is the Media class. 15178 */ 15179 getRestItemClass: function () { 15180 return Media; 15181 }, 15182 15183 /** 15184 * @private 15185 * Gets the REST type for the current object - this is a "MediaList". 15186 */ 15187 getRestType: function () { 15188 return "MediaList"; 15189 }, 15190 15191 /** 15192 * @private 15193 * Gets the REST type for the objects that make up the collection - this is "Media". 15194 */ 15195 getRestItemType: function () { 15196 return "Media"; 15197 }, 15198 15199 /** 15200 * @private 15201 * Override default to indicates that the collection doesn't support making 15202 * requests. 15203 */ 15204 supportsRequests: true, 15205 15206 /** 15207 * @private 15208 * Override default to indicates that the collection subscribes to its objects. 15209 */ 15210 supportsRestItemSubscriptions: true, 15211 15212 /** 15213 * The REST URL in which this object can be referenced. 15214 * @return {String} 15215 * The REST URI for this object. 15216 * @private 15217 */ 15218 getRestUrl: function () { 15219 var 15220 restObj = this._restObj, 15221 restUrl = ""; 15222 15223 //Prepend the base REST object if one was provided. 15224 if (restObj instanceof RestBase) { 15225 restUrl += restObj.getRestUrl(); 15226 } 15227 //Otherwise prepend with the default webapp name. 15228 else { 15229 restUrl += "/finesse/api"; 15230 } 15231 15232 //Append the REST type. (Media not MediaList) 15233 restUrl += "/" + this.getRestItemType(); 15234 15235 //Append ID if it is not undefined, null, or empty. 15236 if (this._id) { 15237 restUrl += "/" + this._id; 15238 } 15239 15240 return restUrl; 15241 }, 15242 15243 /** 15244 * Returns a specific Media with the id passed, from the MediaList collection. 15245 * * @param {Object} options 15246 * An object with the following properties:<ul> 15247 * <li><b>id:</b> The id of the media to fetch</li> 15248 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 15249 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 15250 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 15251 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 15252 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 15253 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15254 * <li><b>content:</b> {String} Raw string of response</li> 15255 * <li><b>object:</b> {Object} Parsed object of response</li> 15256 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15257 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15258 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15259 * </ul></li> 15260 * </ul></li> 15261 * <li><b>mediaOptions:</b> {Object} An object with the following properties:<ul> 15262 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 15263 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 15264 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 15265 * </li></ul> 15266 * 15267 * @returns {finesse.restservices.Media} 15268 * A Media object. 15269 */ 15270 getMedia: function (options) { 15271 this.isLoaded(); 15272 options = options || {}; 15273 var objectId = options.id, 15274 media = this._collection[objectId]; 15275 15276 //throw error if media not found 15277 if(!media) { 15278 throw new Error("No media found with id: " + objectId); 15279 } 15280 15281 media.addHandler('load', options.onLoad); 15282 media.addHandler('change', options.onChange); 15283 media.addHandler('add', options.onAdd); 15284 media.addHandler('delete', options.onDelete); 15285 media.addHandler('error', options.onError); 15286 15287 media.setMediaOptions(options.mediaOptions); 15288 15289 return media; 15290 }, 15291 15292 /** 15293 * Utility method to build the internal collection data structure (object) based on provided data 15294 * @param {Object} data 15295 * The data to build the internal collection from 15296 * @private 15297 */ 15298 _buildCollection: function (data) { 15299 //overriding this from RestBaseCollection because we need to pass in parentObj 15300 //because restUrl is finesse/api/User/<useri>/Media/<mediaId> 15301 //other objects like dialog have finesse/api/Dialog/<dialogId> 15302 15303 var i, object, objectId, dataArray; 15304 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 15305 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 15306 for (i = 0; i < dataArray.length; i += 1) { 15307 15308 object = {}; 15309 object[this.getRestItemType()] = dataArray[i]; 15310 15311 //get id from object.id instead of uri, since uri is not there for some reason 15312 objectId = object[this.getRestItemType()].id;//this._extractId(object); 15313 15314 //create the Media Object 15315 this._collection[objectId] = new (this.getRestItemClass())({ 15316 id: objectId, 15317 data: object, 15318 parentObj: this._restObj 15319 }); 15320 this.length += 1; 15321 } 15322 } 15323 } 15324 }); 15325 15326 window.finesse = window.finesse || {}; 15327 window.finesse.restservices = window.finesse.restservices || {}; 15328 window.finesse.restservices.MediaList = MediaList; 15329 15330 return MediaList; 15331 }); 15332 15333 /** 15334 * JavaScript representation of the Finesse ReasonCodes collection 15335 * object which contains a list of ReasonCodes objects. 15336 * 15337 * @requires Class 15338 */ 15339 15340 /** @private */ 15341 define('restservices/ReasonCodes',[], function () { 15342 15343 var ReasonCodes = Class.extend(/** @lends finesse.restservices.ReasonCodes.prototype */{ 15344 15345 /** 15346 * @class 15347 * JavaScript representation of a ReasonCodes collection object. 15348 * @augments Class 15349 * @constructs 15350 * @example 15351 * _user.getNotReadyReasonCodes({ 15352 * success: handleNotReadyReasonCodesSuccess, 15353 * error: handleNotReadyReasonCodesError 15354 * }); 15355 * 15356 * handleNotReadyReasonCodesSuccess = function(reasonCodes) { 15357 * for(var i = 0; i < reasonCodes.length; i++) { 15358 * var reasonCode = reasonCodes[i]; 15359 * var reasonCodeId = reasonCode.id; 15360 * var reasonCodeLabel = reasonCode.label; 15361 * } 15362 * } 15363 * } 15364 */ 15365 15366 _fakeConstuctor: function () { 15367 /* This is here to hide the real init constructor from the public docs */ 15368 } 15369 15370 }); 15371 15372 window.finesse = window.finesse || {}; 15373 window.finesse.restservices = window.finesse.restservices || {}; 15374 window.finesse.restservices.ReasonCodes = ReasonCodes; 15375 15376 return ReasonCodes; 15377 }); 15378 15379 /** 15380 * Utility class for fetch all the reason codes on Team. Reason codes result will include all global and system reason codes as well. 15381 * 15382 */ 15383 15384 /** @private */ 15385 define('restservices/TeamResource',['restservices/RestBase', 'utilities/Utilities', "clientservices/Topics", "utilities/RestCachingService"], function (RestBase, Utilities, Topics, RestCachingService) { 15386 15387 var TeamResource = RestBase.extend(/** @lends finesse.restservices.TeamResource.prototype */{ 15388 /** 15389 * @private 15390 * Returns whether this object supports subscriptions 15391 */ 15392 supportsSubscriptions: false, 15393 15394 doNotRefresh: true, 15395 15396 autoSubscribe: false, 15397 15398 supportsRequests: false, 15399 15400 teamId: null, 15401 15402 rcs: RestCachingService, 15403 15404 /** 15405 * References to ClientServices logger methods 15406 * @private 15407 */ 15408 _logger: { 15409 log: finesse.clientservices.ClientServices.log 15410 }, 15411 15412 /** 15413 * JavaScript representation of a TeamResource object. Also exposes methods to operate 15414 * on the object against the server. 15415 * 15416 * @param {Object} options 15417 * An object with the following properties:<ul> 15418 * <li><b>teamId:</b> teamId of the user</li> 15419 * </ul> 15420 * @constructs 15421 **/ 15422 init: function (options){ 15423 this._super(options); 15424 if(!options.teamId){ 15425 throw Error("Team Id is mandatory") 15426 } 15427 15428 this.teamId = options.teamId; 15429 }, 15430 15431 /** 15432 * @private 15433 * Do get disabled. 15434 */ 15435 doGet: function(handlers) { 15436 return; 15437 }, 15438 15439 /** 15440 * @private 15441 * Gets the REST class for the current object - this is the TeamResource object. 15442 */ 15443 getRestClass: function () { 15444 return TeamResource; 15445 }, 15446 15447 /** 15448 * @private 15449 * Gets the REST type for the current object - this is a "TeamResource". 15450 */ 15451 getRestType: function () { 15452 return "TeamResource"; 15453 }, 15454 15455 /** 15456 * @private 15457 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 15458 */ 15459 getRestItemType: function () { 15460 return "ReasonCodes"; 15461 }, 15462 15463 getRestUrl: function () { 15464 return this.getBaseRestUrl() + "/" + this.getRestType() + "/" + this.teamId + "/" 15465 }, 15466 15467 /** 15468 * @private 15469 * Parses a uriString to retrieve the id portion 15470 * @param {String} uriString 15471 * @return {String} id 15472 */ 15473 _parseIdFromUriString : function (uriString) { 15474 return Utilities.getId(uriString); 15475 }, 15476 15477 /** 15478 * Get reason codes cache from cache. If reason code does not exists in the reason codes cache, then fetch it from Finesse server, for looking up the reason code 15479 * with its reason code value, and category. 15480 * Note that there is no return value; use the success handler to process a 15481 * valid return. 15482 * 15483 * @param {finesse.interfaces.RequestHandlers} handlers 15484 * An object containing the handlers for the request 15485 * @param {String} reasonCodeValue The code for the reason code to lookup 15486 * @param {String} reasonCodeCategory The category for the reason code to lookup. 15487 * The possible values are "NOT_READY" and "LOGOUT". 15488 * 15489 * @example 15490 * new finesse.restservices.TeamResource().lookupReasonCode({ 15491 * success: _handleReasonCodeGet, 15492 * error: _handleReasonCodeGetError 15493 * }, '32762', 'NOT_READY'); 15494 * _handleReasonCodeGet(_reasonCode) { 15495 * var id = _reasonCode.id; 15496 * var uri = _reasonCode.uri; 15497 * var label = _reasonCode.label; 15498 * ... 15499 * } 15500 * 15501 */ 15502 lookupReasonCode : function (handlers, reasonCodeValue, reasonCodeCategory) { 15503 15504 /** 15505 * Lookup the reason code from cache if already cached. 15506 * if not cached fetch the reson codes and cache it. 15507 */ 15508 this.getReasonCodesByType(reasonCodeCategory, { 15509 success: function(reasonCodes) { 15510 handlers.success(reasonCodes.filter(function(rc){ 15511 return rc.code === reasonCodeValue; 15512 })[0]) 15513 }, 15514 error: function (rsp) { 15515 handlers.error(rsp); 15516 }, 15517 }, true); 15518 }, 15519 15520 /** 15521 * Get the reason code by category from reason code cache. reason code cache is empty, then fetch all the reason codes Not_READY, LOGOUT and System Reason codes 15522 * from server adn cache it in the local memory. 15523 * * @param {String} type, The reason code type "NOT_READY" and "LOGOUT" 15524 * @param {finesse.interfaces.RequestHandlers} handlers 15525 * An object containing the handlers for the request 15526 * @param {boolean} includeSystemCode should return system code also. 15527 */ 15528 getReasonCodesByType : function (type, handlers, includeSystemCode) 15529 { 15530 var restType = 'ReasonCodes'; 15531 var self = this, url = this.getRestUrl() + restType, reasonCodes, i, reasonCodeArray; 15532 var includeSystemCode = includeSystemCode ? true: false; 15533 15534 if(this.rcs.isRestCacheServiceReachable() && this.rcs.isCacheable(restType)) { 15535 var options = { 15536 key: Topics.getTopic(url), 15537 onsuccess: function (result) { 15538 // check if reason code already exists in the cache. 15539 if(result){ 15540 self._logger.log('Getting Reason Codes from cache for category=' + type + ' and Team='+this.teamId); 15541 self._reasonCodesSuccessHandler(type, url, result.data.text, handlers, includeSystemCode); 15542 return; 15543 } else { 15544 self._makeRestRequest(self, url, this.teamId, type, handlers, includeSystemCode, restType) 15545 } 15546 }, 15547 onerror: function (error) { 15548 self._logger.log("Error while fetching reason codes from cache", error); 15549 } 15550 } 15551 finesse.idb.RestCacheDataContainer.fetchData(options); 15552 return; 15553 } else { 15554 this._makeRestRequest(self, url, this.teamId, type, handlers, includeSystemCode, restType); 15555 } 15556 }, 15557 15558 _makeRestRequest: function(self,url, teamId, type, handlers, includeSystemCode, restType){ 15559 var contentBody = {}; 15560 self._logger.log('Fetching Reason Codes category=ALL'); 15561 // if reason code does not exists in the cache, fetch from server. 15562 self.restRequest(url + "?category=ALL", { 15563 method: 'GET', 15564 success: function (rsp) { 15565 self._reasonCodesSuccessHandler(type, url, rsp.content, handlers, includeSystemCode); 15566 }, 15567 error: function (rsp) { 15568 handlers.error(rsp); 15569 }, 15570 content: contentBody, 15571 restType: restType 15572 }); 15573 }, 15574 15575 _reasonCodesSuccessHandler: function(type, url, reasonCodesXml, handlers, includeSystemCode) { 15576 var reasonCodesRsp = []; 15577 var systemReasonCodes = []; 15578 var reasonCodesObj = this._util.xml2js(reasonCodesXml); 15579 var reasonCodes = []; 15580 if(reasonCodesObj && reasonCodesObj.ReasonCodes.ReasonCode){ 15581 reasonCodes = reasonCodesObj.ReasonCodes.ReasonCode; 15582 } 15583 // if there more than one reason codes 15584 if (reasonCodes[0] !== undefined) { 15585 15586 for (i = 0; i < reasonCodes.length; i = i + 1) { 15587 if(type === reasonCodes[i].category){ 15588 if(reasonCodes[i].systemCode === 'true'){ 15589 systemReasonCodes[i] = this._getResonCodeObject(reasonCodes[i]); 15590 } else { 15591 reasonCodesRsp[i] = this._getResonCodeObject(reasonCodes[i]); 15592 } 15593 } 15594 } 15595 } else { 15596 if(type === reasonCodes.category){ 15597 if(reasonCodes[i].systemCode === 'true'){ 15598 systemReasonCodes[i] = _getResonCodeObject(reasonCodes); 15599 }else{ 15600 reasonCodesRsp[i] = _getResonCodeObject(reasonCodes); 15601 } 15602 } 15603 } 15604 // invoke call back with reason codes. includeSystemCode is true then include system reason codes also 15605 handlers.success(includeSystemCode? reasonCodesRsp.concat(systemReasonCodes): reasonCodesRsp); 15606 }, 15607 _getResonCodeObject: function(reasonCode){ 15608 return { 15609 label: reasonCode.label, 15610 id: Utilities.getId(reasonCode.uri), 15611 uri: reasonCode.uri, 15612 code: reasonCode.code 15613 }; 15614 } 15615 }); 15616 15617 15618 window.finesse = window.finesse || {}; 15619 window.finesse.restservices = window.finesse.restservices || {}; 15620 window.finesse.restservices.TeamResource = TeamResource; 15621 15622 return TeamResource; 15623 }); 15624 15625 /** 15626 * JavaScript representation of the Finesse User object 15627 * 15628 * @requires finesse.clientservices.ClientServices 15629 * @requires Class 15630 * @requires finesse.FinesseBase 15631 * @requires finesse.restservices.RestBase 15632 */ 15633 15634 /** @private */ 15635 define('restservices/User',[ 15636 'restservices/RestBase', 15637 'restservices/Dialogs', 15638 'restservices/ClientLog', 15639 'restservices/Queues', 15640 'restservices/WrapUpReasons', 15641 'restservices/PhoneBooks', 15642 'restservices/Workflows', 15643 'restservices/UserMediaPropertiesLayout', 15644 'restservices/UserMediaPropertiesLayouts', 15645 'restservices/Media', 15646 'restservices/MediaList', 15647 'restservices/ReasonCodes', 15648 'utilities/Utilities', 15649 "restservices/TeamResource" 15650 ], 15651 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, UserMediaPropertiesLayout, UserMediaPropertiesLayouts, Media, MediaList, ReasonCodes, Utilities, TeamResource) { 15652 15653 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 15654 15655 _dialogs : null, 15656 _clientLogObj : null, 15657 _wrapUpReasons : null, 15658 _phoneBooks : null, 15659 _workflows : null, 15660 _mediaPropertiesLayout : null, 15661 _mediaPropertiesLayouts : null, 15662 _queues : null, 15663 media : null, 15664 mediaList : null, 15665 _TeamResource: null, 15666 15667 /** 15668 * @class 15669 * The User represents a Finesse Agent or Supervisor. 15670 * 15671 * @param {Object} options 15672 * An object with the following properties:<ul> 15673 * <li><b>id:</b> The id of the object being constructed</li> 15674 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 15675 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 15676 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 15677 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 15678 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 15679 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15680 * <li><b>content:</b> {String} Raw string of response</li> 15681 * <li><b>object:</b> {Object} Parsed object of response</li> 15682 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15683 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15684 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15685 * </ul></li> 15686 * </ul></li> 15687 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15688 * @augments finesse.restservices.RestBase 15689 * @constructs 15690 * @example 15691 * _user = new finesse.restservices.User({ 15692 * id: _id, 15693 * onLoad : _handleUserLoad, 15694 * onChange : _handleUserChange 15695 * }); 15696 **/ 15697 init: function (options) { 15698 this._super(options); 15699 }, 15700 15701 Callbacks: {}, 15702 15703 /** 15704 * @private 15705 * Gets the REST class for the current object - this is the User object. 15706 * @returns {Object} 15707 * The User constructor. 15708 */ 15709 getRestClass: function () { 15710 return User; 15711 }, 15712 15713 /** 15714 * @private 15715 * Gets the REST type for the current object - this is a "User". 15716 * @returns {String} 15717 * The User String 15718 */ 15719 getRestType: function () { 15720 return "User"; 15721 }, 15722 /** 15723 * @private 15724 * overloading this to return URI 15725 * @returns {String} 15726 * The REST URI for this object. 15727 */ 15728 getXMPPNodePath: function () { 15729 return this.getRestUrl(); 15730 }, 15731 /** 15732 * @private 15733 * Returns whether this object supports subscriptions 15734 * @returns {Boolean} True 15735 */ 15736 supportsSubscriptions: function () { 15737 return true; 15738 }, 15739 15740 /** 15741 * Getter for the firstName of this User. 15742 * @returns {String} 15743 * The firstName for this User 15744 */ 15745 getFirstName: function () { 15746 this.isLoaded(); 15747 return Utilities.convertNullToEmptyString(this.getData().firstName); 15748 }, 15749 15750 15751 /** 15752 * Getter for the reasonCode of this User. 15753 * 15754 * @returns {Object} The reasonCode for the state of the User<br> 15755 * The contents may include the following:<ul> 15756 * <li>uri: The URI for the reason code object. 15757 * <li>id: The unique ID for the reason code. 15758 * <li>category: The category. Can be either NOT_READY or LOGOUT. 15759 * <li>code: The numeric reason code value. 15760 * <li>label: The label for the reason code. 15761 * <li>forAll: Boolean flag that denotes the global status for the reason code. 15762 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 15763 * </ul> 15764 * 15765 */ 15766 getReasonCode : function() { 15767 this.isLoaded(); 15768 return this.getData().reasonCode; 15769 }, 15770 15771 /** 15772 * Getter for the pending state reasonCode of this User. 15773 * 15774 * @returns {Object} The reasonCode for the pending state of the User.<br> 15775 * The contents may include the following:<ul> 15776 * <li>uri: The URI for the reason code object. 15777 * <li>id: The unique ID for the reason code. 15778 * <li>category: The category. Can be either NOT_READY or LOGOUT. 15779 * <li>code: The numeric reason code value. 15780 * <li>label: The label for the reason code. 15781 * <li>forAll: Boolean flag that denotes the global status for the reason code. 15782 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 15783 * </ul> 15784 */ 15785 getPendingStateReasonCode : function() { 15786 this.isLoaded(); 15787 return this.getData().pendingStateReasonCode; 15788 }, 15789 15790 15791 /** 15792 * Getter for the reasonCode of this User. 15793 * 15794 * @returns {Boolean} True if the reason code for the state of the user 15795 * is a system generated reason code. 15796 */ 15797 isReasonCodeReserved : function() { 15798 var resonCode = this.getReasonCode(); 15799 if (resonCode) { 15800 return resonCode.systemCode === "true" ? true 15801 : false; 15802 } 15803 return false; 15804 }, 15805 15806 15807 /** 15808 * Getter for the lastName of this User. 15809 * @returns {String} 15810 * The lastName for this User 15811 */ 15812 getLastName: function () { 15813 this.isLoaded(); 15814 return Utilities.convertNullToEmptyString(this.getData().lastName); 15815 }, 15816 15817 /** 15818 * Getter for the full name of this User. 15819 * The full name is of the format "FirstName LastName" (for example: "John Doe") 15820 * 15821 * @returns {String} 15822 * The full name of this User. 15823 */ 15824 getFullName : function() { 15825 this.isLoaded(); 15826 15827 /* 15828 * Currently, the expected format is "FirstName LastName", but can differ later 15829 * to something "LastName, FirstName". 15830 * To accommodate such, we use formatString utility method so that, if required, 15831 * the same can be achieved just by flipping the format place holders as follows: 15832 * "{1}, {0}" 15833 * Also, the function could be enhanced to take the format parameter. 15834 */ 15835 var fmt = "{0} {1}"; 15836 return Utilities.formatString(fmt, 15837 Utilities.convertNullToEmptyString(this.getData().firstName), 15838 Utilities.convertNullToEmptyString(this.getData().lastName)); 15839 }, 15840 15841 /** 15842 * Getter for the extension of this User. 15843 * @returns {String} 15844 * The extension, if any, of this User 15845 */ 15846 getExtension: function () { 15847 this.isLoaded(); 15848 return Utilities.convertNullToEmptyString(this.getData().extension); 15849 }, 15850 15851 /** 15852 * Getter for the id of the Team of this User 15853 * @returns {String} 15854 * The current (or last fetched) id of the Team of this User 15855 */ 15856 getTeamId: function () { 15857 this.isLoaded(); 15858 return this.getData().teamId; 15859 }, 15860 15861 /** 15862 * Getter for the name of the Team of this User 15863 * @returns {String} 15864 * The current (or last fetched) name of the Team of this User 15865 */ 15866 getTeamName: function () { 15867 this.isLoaded(); 15868 return this.getData().teamName; 15869 }, 15870 15871 /** 15872 * Is user an agent? 15873 * @returns {Boolean} True if user has role of agent, else false. 15874 */ 15875 hasAgentRole: function () { 15876 this.isLoaded(); 15877 return this.hasRole("Agent"); 15878 }, 15879 15880 /** 15881 * Is user a supervisor? 15882 * @returns {Boolean} True if user has role of supervisor, else false. 15883 */ 15884 hasSupervisorRole: function () { 15885 this.isLoaded(); 15886 return this.hasRole("Supervisor"); 15887 }, 15888 15889 /** 15890 * @private 15891 * Checks to see if user has "theRole" 15892 * @returns {Boolean} True if "theRole" has the role of supervisor or agent, else false. 15893 */ 15894 hasRole: function (theRole) { 15895 this.isLoaded(); 15896 var result = false, i, roles, len; 15897 15898 roles = this.getData().roles.role; 15899 len = roles.length; 15900 if (typeof roles === 'string') { 15901 if (roles === theRole) { 15902 result = true; 15903 } 15904 } else { 15905 for (i = 0; i < len ; i = i + 1) { 15906 if (roles[i] === theRole) { 15907 result = true; 15908 break; 15909 } 15910 } 15911 } 15912 15913 return result; 15914 }, 15915 15916 /** 15917 * Getter for the pending state of this User. 15918 * @returns {String} 15919 * The pending state of this User 15920 * @see finesse.restservices.User.States 15921 */ 15922 getPendingState: function () { 15923 this.isLoaded(); 15924 return Utilities.convertNullToEmptyString(this.getData().pendingState); 15925 }, 15926 15927 15928 /** 15929 * Getter for the work mode timer for the user 15930 * @returns {String} 15931 * The WrapUpTimer for the user 15932 * @since 12.0.1 15933 */ 15934 getWrapUpTimer: function () { 15935 this.isLoaded(); 15936 return this.getData().wrapUpTimer; 15937 }, 15938 15939 /** 15940 * Getter for the state of this User. 15941 * @returns {String} 15942 * The current (or last fetched) state of this User 15943 * @see finesse.restservices.User.States 15944 */ 15945 getState: function () { 15946 this.isLoaded(); 15947 return this.getData().state; 15948 }, 15949 15950 /** 15951 * Getter for the media state of this User. 15952 * @returns {String} 15953 * The current (or last fetched) media state of this User 15954 * Will be applicable only in CCX deployments 15955 * When the agent is talking on a manual outbound call, it returns busy 15956 * The value will not be present in other cases. 15957 */ 15958 getMediaState: function () { 15959 this.isLoaded(); 15960 /* There is an assertion that the value should not be undefined while setting the value in datastore. Hence setting it to null*/ 15961 if(this.getData().mediaState === undefined) { 15962 this.getData().mediaState = null; 15963 } 15964 15965 return this.getData().mediaState; 15966 }, 15967 15968 /** 15969 * Getter for the state change time of this User. 15970 * @returns {String} 15971 * The state change time of this User 15972 */ 15973 getStateChangeTime: function () { 15974 this.isLoaded(); 15975 return this.getData().stateChangeTime; 15976 }, 15977 15978 /** 15979 * Getter for the wrap-up mode of this User for incoming calls. 15980 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 15981 * @see finesse.restservices.User.WrapUpMode 15982 */ 15983 getWrapUpOnIncoming: function () { 15984 this.isLoaded(); 15985 return this.getData().settings.wrapUpOnIncoming; 15986 }, 15987 15988 /** 15989 * Getter for the wrap-up mode of this User for outgoing calls. 15990 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 15991 * @see finesse.restservices.User.WrapUpMode 15992 */ 15993 getWrapUpOnOutgoing: function () { 15994 this.isLoaded(); 15995 return this.getData().settings.wrapUpOnOutgoing; 15996 }, 15997 15998 15999 /** 16000 * Is User required to go into wrap-up? 16001 * @return {Boolean} 16002 * True if this agent is required to go into wrap-up. 16003 * @see finesse.restservices.User.WrapUpMode 16004 */ 16005 isWrapUpRequired: function () { 16006 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 16007 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 16008 }, 16009 16010 /** 16011 * Checks to see if the user is considered a mobile agent by checking for 16012 * the existence of the mobileAgent node. 16013 * @returns {Boolean} 16014 * True if this agent is a mobile agent. 16015 */ 16016 isMobileAgent: function () { 16017 this.isLoaded(); 16018 var ma = this.getData().mobileAgent; 16019 return ma !== null && typeof ma === "object"; 16020 }, 16021 16022 /** 16023 * Getter for the mobile agent work mode. 16024 * @returns {finesse.restservices.User.WorkMode} 16025 * If available, return the mobile agent work mode, otherwise null. 16026 * @see finesse.restservices.User.WorkMode 16027 */ 16028 getMobileAgentMode: function () { 16029 this.isLoaded(); 16030 if (this.isMobileAgent()) { 16031 return this.getData().mobileAgent.mode; 16032 } 16033 return null; 16034 }, 16035 16036 /** 16037 * Getter for the mobile agent dial number. 16038 * @returns {String} 16039 * If available, return the mobile agent dial number, otherwise null. 16040 */ 16041 getMobileAgentDialNumber: function () { 16042 this.isLoaded(); 16043 if (this.isMobileAgent()) { 16044 return this.getData().mobileAgent.dialNumber; 16045 } 16046 return null; 16047 }, 16048 16049 /** 16050 * Getter for a Dialogs collection object that is associated with User. 16051 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16052 * applicable when Object has not been previously created). 16053 * @returns {finesse.restservices.Dialogs} 16054 * A Dialogs collection object. 16055 * @see finesse.restservices.Dialogs 16056 */ 16057 getDialogs: function (callbacks) { 16058 var options = callbacks || {}; 16059 options.parentObj = this; 16060 this.isLoaded(); 16061 16062 if (this._dialogs === null) { 16063 this._dialogs = new Dialogs(options); 16064 } 16065 16066 return this._dialogs; 16067 }, 16068 16069 /** 16070 * Getter for a Dialogs collection object that is associated with User.This will always query from server 16071 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers 16072 * @returns {finesse.restservices.Dialogs} 16073 * A Dialogs collection object. 16074 * @see finesse.restservices.Dialogs 16075 */ 16076 getDialogsNoCache: function (callbacks) { 16077 var options = callbacks || {}; 16078 options.parentObj = this; 16079 this.isLoaded(); 16080 this._dialogs = new Dialogs(options); 16081 16082 return this._dialogs; 16083 }, 16084 16085 /** 16086 * Getter for Media collection object that is associated with User. 16087 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16088 * applicable when Object has not been previously created). 16089 * @returns {finesse.restservices.MediaList} 16090 * A Media Dialogs collection object. 16091 * @see finesse.restservices.MediaList 16092 */ 16093 getMediaList: function (callbacks) { 16094 var options = callbacks || {}; 16095 options.parentObj = this; 16096 this.isLoaded(); 16097 16098 if (this.mediaList === null) { 16099 this.mediaList = new MediaList(options); 16100 } 16101 16102 return this.mediaList; 16103 }, 16104 16105 /** 16106 * @private 16107 * Getter for a ClientLog object that is associated with User. 16108 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16109 * applicable when Object has not been previously created). 16110 * @returns {finesse.restservices.ClientLog} 16111 * A ClientLog collection object. 16112 * @see finesse.restservices.ClientLog 16113 */ 16114 getClientLog: function (callbacks) { 16115 var options = callbacks || {}; 16116 options.parentObj = this; 16117 this.isLoaded(); 16118 16119 if (this._clientLogObj === null) { 16120 this._clientLogObj = new ClientLog(options); 16121 } 16122 else { 16123 if(options.onLoad && typeof options.onLoad === "function") { 16124 options.onLoad(this._clientLogObj); 16125 } 16126 } 16127 return this._clientLogObj; 16128 }, 16129 16130 /** 16131 * Getter for a Queues collection object that is associated with User. 16132 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16133 * applicable when Object has not been previously created). 16134 * @returns {finesse.restservices.Queues} 16135 * A Queues collection object. 16136 * @see finesse.restservices.Queues 16137 */ 16138 getQueues: function (callbacks) { 16139 var options = callbacks || {}; 16140 options.parentObj = this; 16141 this.isLoaded(); 16142 16143 if (this._queues === null) { 16144 this._queues = new Queues(options); 16145 } 16146 16147 return this._queues; 16148 }, 16149 16150 /** 16151 * Getter for a WrapUpReasons collection object that is associated with User. 16152 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16153 * applicable when Object has not been previously created). 16154 * @returns {finesse.restservices.WrapUpReasons} 16155 * A WrapUpReasons collection object. 16156 * @see finesse.restservices.WrapUpReasons 16157 */ 16158 getWrapUpReasons: function (callbacks) { 16159 var options = callbacks || {}; 16160 options.parentObj = this; 16161 options.teamId = this.getTeamId(); 16162 this.isLoaded(); 16163 16164 if (this._wrapUpReasons === null) { 16165 this._wrapUpReasons = new WrapUpReasons(options); 16166 } 16167 16168 return this._wrapUpReasons; 16169 }, 16170 16171 /** 16172 * Getter for a PhoneBooks collection object that is associated with User. 16173 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16174 * applicable when Object has not been previously created). 16175 * @returns {finesse.restservices.PhoneBooks} 16176 * A PhoneBooks collection object. 16177 * @see finesse.restservices.PhoneBooks 16178 */ 16179 getPhoneBooks: function (callbacks) { 16180 var options = callbacks || {}; 16181 options.parentObj = this; 16182 options.teamId = this.getTeamId(); 16183 this.isLoaded(); 16184 16185 if (this._phoneBooks === null) { 16186 this._phoneBooks = new PhoneBooks(options); 16187 } 16188 16189 return this._phoneBooks; 16190 }, 16191 16192 /** 16193 * @private 16194 * Loads the Workflows collection object that is associated with User and 16195 * 'returns' them to the caller via the handlers. 16196 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16197 * applicable when Object has not been previously created). 16198 * @see finesse.restservices.Workflow 16199 * @see finesse.restservices.Workflows 16200 * @see finesse.restservices.RestCollectionBase 16201 */ 16202 loadWorkflows: function (callbacks) { 16203 var options = callbacks || {}; 16204 options.parentObj = this; 16205 options.teamId = this.getTeamId(); 16206 this.isLoaded(); 16207 16208 if (this._workflows === null) { 16209 this._workflows = new Workflows(options); 16210 } else { 16211 this._workflows.refresh(); 16212 } 16213 16214 }, 16215 16216 /** 16217 * Getter for a UserMediaPropertiesLayout object that is associated with User. 16218 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16219 * applicable when Object has not been previously created). 16220 * @returns {finesse.restservices.UserMediaPropertiesLayout} 16221 * The UserMediaPropertiesLayout object associated with this user 16222 * @see finesse.restservices.UserMediaPropertiesLayout 16223 */ 16224 getMediaPropertiesLayout: function (callbacks) { 16225 var options = callbacks || {}; 16226 options.parentObj = this; 16227 options.id = this._id; 16228 16229 this.isLoaded(); 16230 if (this._mediaPropertiesLayout === null) { 16231 this._mediaPropertiesLayout = new UserMediaPropertiesLayout(options); 16232 } 16233 return this._mediaPropertiesLayout; 16234 }, 16235 16236 /** 16237 * Getter for a UserMediaPropertiesLayouts object that is associated with User. 16238 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 16239 * applicable when Object has not been previously created). 16240 * @returns {finesse.restservices.UserMediaPropertiesLayout} 16241 * The UserMediaPropertiesLayout object associated with this user 16242 * @see finesse.restservices.UserMediaPropertiesLayout 16243 */ 16244 getMediaPropertiesLayouts: function (callbacks) { 16245 var options = callbacks || {}; 16246 options.parentObj = this; 16247 options.teamId = this.getTeamId(); 16248 this.isLoaded(); 16249 if (this._mediaPropertiesLayouts === null) { 16250 this._mediaPropertiesLayouts = new UserMediaPropertiesLayouts(options); 16251 } 16252 return this._mediaPropertiesLayouts; 16253 }, 16254 16255 /** 16256 * Getter for the Teams managed by this User(Supervisor), if any. 16257 * 16258 * @returns {Array} of objects containing id, name, uri of the Teams managed by this User(Supervisor).<br> 16259 * The object content includes the following:<ul> 16260 * <li>id: The unique ID for the team. 16261 * <li>name: The team name for the team. 16262 * <li>uri: The URI for the team. 16263 * </ul> 16264 * 16265 */ 16266 getSupervisedTeams: function () { 16267 this.isLoaded(); 16268 16269 try { 16270 return Utilities.getArray(this.getData().teams.Team); 16271 } catch (e) { 16272 return []; 16273 } 16274 16275 }, 16276 16277 /** 16278 * Perform an agent login for this user, associating him with the 16279 * specified extension. 16280 * @param {Object} params 16281 * An object containing properties for agent login. 16282 * @param {String} params.reasonCodeId 16283 * The reason code id associated with this login 16284 * @param {String} params.extension 16285 * The extension to associate with this user 16286 * @param {Object} [params.mobileAgent] 16287 * A mobile agent object containing the mode and dial number properties. 16288 * @param {finesse.interfaces.RequestHandlers} params.handlers 16289 * @see finesse.interfaces.RequestHandlers 16290 * @returns {finesse.restservices.User} 16291 * This User object, to allow cascading 16292 * @private 16293 */ 16294 _login: function (params) { 16295 var handlers, contentBody = {}, 16296 restType = this.getRestType(); 16297 16298 // Protect against null dereferencing. 16299 params = params || {}; 16300 16301 contentBody[restType] = { 16302 "state": User.States.LOGIN, 16303 "extension": params.extension 16304 }; 16305 16306 if(params.reasonCodeId){ 16307 contentBody[restType].reasonCodeId = params.reasonCodeId 16308 } 16309 16310 // Create mobile agent node if available. 16311 if (typeof params.mobileAgent === "object") { 16312 contentBody[restType].mobileAgent = { 16313 "mode": params.mobileAgent.mode, 16314 "dialNumber": params.mobileAgent.dialNumber 16315 }; 16316 } 16317 16318 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16319 handlers = params.handlers || {}; 16320 16321 this.restRequest(this.getRestUrl(), { 16322 method: 'PUT', 16323 success: handlers.success, 16324 error: handlers.error, 16325 content: contentBody 16326 }); 16327 16328 return this; // Allow cascading 16329 }, 16330 16331 /** 16332 * Perform an agent login for this user, associating him with the 16333 * specified extension. 16334 * @param {String} extension 16335 * The extension to associate with this user 16336 * @param {finesse.interfaces.RequestHandlers} handlers 16337 * An object containing the handlers for the request 16338 * @returns {finesse.restservices.User} 16339 * This User object, to allow cascading 16340 */ 16341 login: function (extension, handlers) { 16342 this.isLoaded(); 16343 var params = { 16344 "extension": extension, 16345 "handlers": handlers 16346 }; 16347 return this._login(params); 16348 }, 16349 16350 16351 16352 16353 /** 16354 * Perform an agent login for this user, associating him with the 16355 * specified extension. 16356 * @param {String} extension 16357 * The extension to associate with this user 16358 * @param {String} mode 16359 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 16360 * @param {String} extension 16361 * The external dial number desired to be used by the mobile agent. 16362 * @param {finesse.interfaces.RequestHandlers} handlers 16363 * An object containing the handlers for the request 16364 * @param {Object} reasonCode 16365 * An object containing the reasonCode for the login request 16366 * @returns {finesse.restservices.User} 16367 * This User object, to allow cascading 16368 */ 16369 loginMobileAgent: function (extension, mode, dialNumber, handlers, reasonCode) { 16370 this.isLoaded(); 16371 16372 var params = { 16373 "extension": extension, 16374 "mobileAgent": { 16375 "mode": mode, 16376 "dialNumber": dialNumber 16377 }, 16378 "handlers": handlers 16379 }; 16380 //US303866 - reasonCode added for restoring MobileAgent after CTI client disconnect 16381 if(reasonCode) { 16382 params.reasonCodeId = reasonCode.id; 16383 } 16384 return this._login(params); 16385 }, 16386 16387 16388 _updateMobileAgent: function (params) { 16389 var handlers, contentBody = {}, 16390 restType = this.getRestType(); 16391 16392 params = params || {}; 16393 16394 contentBody[restType] = { 16395 }; 16396 16397 if (typeof params.mobileAgent === "object") { 16398 contentBody[restType].mobileAgent = { 16399 "mode": params.mobileAgent.mode, 16400 "dialNumber": params.mobileAgent.dialNumber 16401 }; 16402 } 16403 16404 handlers = params.handlers || {}; 16405 16406 this.restRequest(this.getRestUrl(), { 16407 method: 'PUT', 16408 success: handlers.success, 16409 error: handlers.error, 16410 content: contentBody 16411 }); 16412 16413 return this; 16414 }, 16415 16416 /** 16417 * Update user object in Finesse with agent's mobile login information (Refer defect CSCvc35407) 16418 * @param {String} mode 16419 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 16420 * @param {String} dialNumber 16421 * The external dial number desired to be used by the mobile agent. 16422 * @param {finesse.interfaces.RequestHandlers} handlers 16423 * An object containing the handlers for the request 16424 * @returns {finesse.restservices.User} 16425 * This User object, to allow cascading 16426 */ 16427 updateToMobileAgent: function (mode, dialNumber, handlers) { 16428 this.isLoaded(); 16429 var params = { 16430 "mobileAgent": { 16431 "mode": mode, 16432 "dialNumber": dialNumber 16433 }, 16434 "handlers": handlers 16435 }; 16436 return this._updateMobileAgent(params); 16437 }, 16438 16439 /** 16440 * Perform an agent logout for this user. 16441 * @param {String} reasonCode 16442 * The reason this user is logging out. Pass null for no reason. 16443 * @param {finesse.interfaces.RequestHandlers} handlers 16444 * An object containing the handlers for the request 16445 * @returns {finesse.restservices.User} 16446 * This User object, to allow cascading 16447 */ 16448 logout: function (reasonCode, handlers) { 16449 return this.setState("LOGOUT", reasonCode, handlers); 16450 }, 16451 16452 /** 16453 * Set the state of the user. 16454 * @param {String} newState 16455 * The state you are setting 16456 * @param {ReasonCode} reasonCode 16457 * The reason this user is logging out. Pass null for no reason. 16458 * @param {finesse.interfaces.RequestHandlers} handlers 16459 * An object containing the handlers for the request 16460 * @see finesse.restservices.User.States 16461 * @returns {finesse.restservices.User} 16462 * This User object, to allow cascading 16463 */ 16464 setState: function (newState, reasonCode, handlers) { 16465 this.isLoaded(); 16466 16467 var options, contentBody = {}; 16468 16469 if (!reasonCode) { 16470 contentBody[this.getRestType()] = { 16471 "state": newState 16472 }; 16473 16474 } else { 16475 contentBody[this.getRestType()] = { 16476 "state": newState, 16477 "reasonCodeId": reasonCode.id 16478 }; 16479 16480 } 16481 16482 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16483 handlers = handlers || {}; 16484 16485 options = { 16486 method: 'PUT', 16487 success: handlers.success, 16488 error: handlers.error, 16489 content: contentBody 16490 }; 16491 16492 // After removing the selective 202 handling, we should be able to just use restRequest 16493 this.restRequest(this.getRestUrl(), options); 16494 16495 return this; // Allow cascading 16496 }, 16497 16498 /** 16499 * Make call to a particular phone number. 16500 * 16501 * @param {String} 16502 * The number to call 16503 * @param {finesse.interfaces.RequestHandlers} handlers 16504 * An object containing the handlers for the request 16505 * @returns {finesse.restservices.User} 16506 * This User object, to allow cascading 16507 */ 16508 makeCall: function (number, handlers) { 16509 this.isLoaded(); 16510 16511 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 16512 16513 return this; // Allow cascading 16514 }, 16515 16516 /** 16517 * Make a silent monitor call to a particular agent's phone number. 16518 * 16519 * @param {String} 16520 * The number to call 16521 * @param {finesse.interfaces.RequestHandlers} handlers 16522 * An object containing the handlers for the request 16523 * @returns {finesse.restservices.User} 16524 * This User object, to allow cascading 16525 */ 16526 makeSMCall: function (number, handlers) { 16527 this.isLoaded(); 16528 16529 var actionType = "SILENT_MONITOR"; 16530 16531 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 16532 16533 return this; // Allow cascading 16534 }, 16535 16536 16537 /** 16538 * Make a silent monitor call to a particular agent's phone number. 16539 * 16540 * @param {String} 16541 * The number to call 16542 * @param {String} dialogUri 16543 * The associated dialog uri of SUPERVISOR_MONITOR call 16544 * @param {finesse.interfaces.RequestHandlers} handlers 16545 * An object containing the handlers for the request 16546 * @see finesse.restservices.dialog 16547 * @returns {finesse.restservices.User} 16548 * This User object, to allow cascading 16549 */ 16550 makeBargeCall:function (number, dialogURI, handlers) { 16551 this.isLoaded(); 16552 var actionType = "BARGE_CALL"; 16553 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 16554 16555 return this; // Allow cascading 16556 }, 16557 16558 /** 16559 * Returns true if the user's current state will result in a pending state change. A pending state 16560 * change is a request to change state that does not result in an immediate state change. For 16561 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 16562 * agent will not change state until the call ends. 16563 * 16564 * The current set of states that result in pending state changes is as follows: 16565 * TALKING 16566 * HOLD 16567 * RESERVED_OUTBOUND_PREVIEW 16568 * @returns {Boolean} True if there is a pending state change. 16569 * @see finesse.restservices.User.States 16570 */ 16571 isPendingStateChange: function () { 16572 var state = this.getState(); 16573 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 16574 }, 16575 16576 /** 16577 * Returns true if the user's current state is WORK or WORK_READY. This is used so 16578 * that a pending state is not cleared when moving into wrap up (work) mode. 16579 * Note that we don't add this as a pending state, since changes while in wrap up 16580 * occur immediately (and we don't want any "pending state" to flash on screen. 16581 * 16582 * @see finesse.restservices.User.States 16583 * @returns {Boolean} True if user is in wrap-up mode. 16584 */ 16585 isWrapUp: function () { 16586 var state = this.getState(); 16587 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 16588 }, 16589 16590 /** 16591 * @private 16592 * Parses a uriString to retrieve the id portion 16593 * @param {String} uriString 16594 * @return {String} id 16595 */ 16596 _parseIdFromUriString : function (uriString) { 16597 return Utilities.getId(uriString); 16598 }, 16599 16600 /** 16601 * Gets the user's Reason Code label. Works for both Not Ready and 16602 * Logout reason codes 16603 * 16604 * @return {String} the reason code label, or empty string if none 16605 */ 16606 getReasonCodeLabel : function() { 16607 this.isLoaded(); 16608 16609 if (this.getData().reasonCode) { 16610 return this.getData().reasonCode.label; 16611 } else { 16612 return ""; 16613 } 16614 }, 16615 16616 /** 16617 * Gets the user's Not Ready reason code. 16618 * 16619 * @return {String} Reason Code Id, or undefined if not set or 16620 * indeterminate 16621 */ 16622 getNotReadyReasonCodeId : function () { 16623 this.isLoaded(); 16624 16625 var reasoncodeIdResult, finesseServerReasonCodeId; 16626 finesseServerReasonCodeId = this.getData().reasonCodeId; 16627 16628 //FinesseServer will give "-l" => we will set to undefined (for convenience) 16629 if (finesseServerReasonCodeId !== "-1") { 16630 reasoncodeIdResult = finesseServerReasonCodeId; 16631 } 16632 16633 return reasoncodeIdResult; 16634 }, 16635 16636 /** 16637 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 16638 * Note that there is no return value; use the success handler to process a 16639 * valid return. 16640 * @param {finesse.interfaces.RequestHandlers} handlers 16641 * An object containing the handlers for the request 16642 * @param {String} reasonCodeId The id for the reason code to lookup 16643 * 16644 */ 16645 getReasonCodeById : function (handlers, reasonCodeId) 16646 { 16647 var self = this, contentBody, reasonCode, url; 16648 contentBody = {}; 16649 16650 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 16651 this.restRequest(url, { 16652 method: 'GET', 16653 success: function (rsp) { 16654 reasonCode = { 16655 uri: rsp.object.ReasonCode.uri, 16656 label: rsp.object.ReasonCode.label, 16657 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 16658 }; 16659 handlers.success(reasonCode); 16660 }, 16661 error: function (rsp) { 16662 handlers.error(rsp); 16663 }, 16664 content: contentBody 16665 }); 16666 }, 16667 16668 /** 16669 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 16670 * @param {String} type (LOGOUT or NOT_READY) 16671 * @param {finesse.interfaces.RequestHandlers} handlers 16672 * An object containing the handlers for the request 16673 */ 16674 _getReasonCodesByType : function (type, handlers) 16675 { 16676 if(!this._TeamResource){ 16677 this._TeamResource = new TeamResource({teamId: this.getTeamId()}); 16678 } 16679 16680 this._TeamResource.getReasonCodesByType(type, handlers); 16681 }, 16682 16683 /** 16684 * Performs a GET against the Finesse server and retrieves all of the Not Ready 16685 * reason codes. Note that there is no return value; use the success handler to 16686 * process a valid return. 16687 * 16688 * <pre class="code"> 16689 * _user.getSignoutReasonCodes({ 16690 * success: handleSignoutReasonCodesSuccess, 16691 * error: handleSignoutReasonCodesError 16692 * }); 16693 * </pre> 16694 * 16695 * @see finesse.restservices.ReasonCodes 16696 * 16697 * @param {finesse.interfaces.RequestHandlers} handlers 16698 * An object containing the handlers for the request 16699 */ 16700 getSignoutReasonCodes : function (handlers) 16701 { 16702 this._getReasonCodesByType("LOGOUT", handlers); 16703 }, 16704 16705 /** 16706 * Performs a GET against the Finesse server and retrieves all of the Not Ready 16707 * reason codes. Note that there is no return value; use the success handler to 16708 * process a valid return. 16709 * 16710 * <pre class="code"> 16711 * _user.getNotReadyReasonCodes({ 16712 * success: handleNotReadyReasonCodesSuccess, 16713 * error: handleNotReadyReasonCodesError 16714 * }); 16715 * </pre> 16716 * 16717 * @see finesse.restservices.ReasonCodes 16718 * 16719 * @param {finesse.interfaces.RequestHandlers} handlers 16720 * An object containing the handlers for the request 16721 */ 16722 getNotReadyReasonCodes : function (handlers) 16723 { 16724 this._getReasonCodesByType("NOT_READY", handlers); 16725 } 16726 }); 16727 User.MediaStates = /** @lends finesse.restservices.User.MediaStates.prototype */ { 16728 /** 16729 * Will be applicable only in CCX deployments 16730 * When the agent is talking on a manual outbound call 16731 */ 16732 BUSY: "BUSY", 16733 /** 16734 * @class Possible Agent Media States. 16735 * @constructs 16736 */ 16737 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16738 16739 }; 16740 User.States = /** @lends finesse.restservices.User.States.prototype */ { 16741 /** 16742 * User Login. Note that while this is an action, is not technically a state, since a 16743 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 16744 */ 16745 LOGIN: "LOGIN", 16746 /** 16747 * User is logged out. 16748 */ 16749 LOGOUT: "LOGOUT", 16750 /** 16751 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 16752 */ 16753 NOT_READY: "NOT_READY", 16754 /** 16755 * User is ready for calls. 16756 */ 16757 READY: "READY", 16758 /** 16759 * User has a call coming in, but has not answered it. 16760 */ 16761 RESERVED: "RESERVED", 16762 /** 16763 * User has an outbound call being made, but has not been connected to it. 16764 */ 16765 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 16766 /** 16767 * User has an outbound call's preview information being displayed, but has not acted on it. 16768 */ 16769 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 16770 /** 16771 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 16772 */ 16773 TALKING: "TALKING", 16774 /** 16775 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 16776 */ 16777 HOLD: "HOLD", 16778 /** 16779 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 16780 */ 16781 WORK: "WORK", 16782 /** 16783 * This is the same as WORK, except that after time out user becomes READY. 16784 */ 16785 WORK_READY: "WORK_READY", 16786 /** 16787 * @class Possible User state values. 16788 * @constructs 16789 */ 16790 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16791 16792 }; 16793 16794 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 16795 /** 16796 * Mobile agent is connected (dialed) for each incoming call received. 16797 */ 16798 CALL_BY_CALL: "CALL_BY_CALL", 16799 /** 16800 * Mobile agent is connected (dialed) at login. 16801 */ 16802 NAILED_CONNECTION: "NAILED_CONNECTION", 16803 /** 16804 * @class Possible Mobile Agent Work Mode Types. 16805 * @constructs 16806 */ 16807 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16808 16809 }; 16810 16811 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 16812 /** 16813 * Agent must go into wrap-up when call ends 16814 */ 16815 REQUIRED: "REQUIRED", 16816 /** 16817 * Agent must go into wrap-up when call ends and must enter wrap-up data 16818 */ 16819 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 16820 /** 16821 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 16822 */ 16823 OPTIONAL: "OPTIONAL", 16824 /** 16825 * Agent is not allowed to go into wrap-up when call ends. 16826 */ 16827 NOT_ALLOWED: "NOT_ALLOWED", 16828 /** 16829 * @class Possible Wrap-up Mode Types. 16830 * @constructs 16831 */ 16832 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 16833 16834 }; 16835 16836 window.finesse = window.finesse || {}; 16837 window.finesse.restservices = window.finesse.restservices || {}; 16838 window.finesse.restservices.User = User; 16839 16840 return User; 16841 }); 16842 16843 /** 16844 * JavaScript representation of the Finesse Users collection 16845 * object which contains a list of Users objects. 16846 * 16847 * @requires finesse.clientservices.ClientServices 16848 * @requires Class 16849 * @requires finesse.FinesseBase 16850 * @requires finesse.restservices.RestBase 16851 * @requires finesse.restservices.RestCollectionBase 16852 * @requires finesse.restservices.User 16853 */ 16854 16855 /** @private */ 16856 define('restservices/Users',[ 16857 'restservices/RestCollectionBase', 16858 'restservices/RestBase', 16859 'restservices/User' 16860 ], 16861 function (RestCollectionBase, RestBase, User) { 16862 16863 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 16864 16865 /** 16866 * @class 16867 * JavaScript representation of a Users collection object. 16868 * While there is no method provided to retrieve all Users, this collection is 16869 * used to return the Users in a supervised Team. 16870 * @augments finesse.restservices.RestCollectionBase 16871 * @constructs 16872 * @see finesse.restservices.Team 16873 * @see finesse.restservices.User 16874 * @see finesse.restservices.User#getSupervisedTeams 16875 * @example 16876 * // Note: The following method gets an Array of Teams, not a Collection. 16877 * _teams = _user.getSupervisedTeams(); 16878 * if (_teams.length > 0) { 16879 * _team0Users = _teams[0].getUsers(); 16880 * } 16881 */ 16882 _fakeConstuctor: function () { 16883 /* This is here to hide the real init constructor from the public docs */ 16884 }, 16885 16886 /** 16887 * @private 16888 * JavaScript representation of the Finesse Users collection 16889 * object which contains a list of Users objects. 16890 * 16891 * @param {Object} options 16892 * An object with the following properties:<ul> 16893 * <li><b>id:</b> The id of the object being constructed</li> 16894 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16895 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16896 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16897 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16898 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16899 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16900 * <li><b>content:</b> {String} Raw string of response</li> 16901 * <li><b>object:</b> {Object} Parsed object of response</li> 16902 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16903 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16904 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16905 * </ul></li> 16906 * </ul></li> 16907 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16908 **/ 16909 init: function (options) { 16910 this._super(options); 16911 }, 16912 16913 /** 16914 * @private 16915 * Gets the REST class for the current object - this is the Users class. 16916 * @returns {Object} 16917 * The User constructor. 16918 */ 16919 getRestClass: function () { 16920 return Users; 16921 }, 16922 16923 /** 16924 * @private 16925 * Gets the REST class for the objects that make up the collection. - this 16926 * is the User class. 16927 * @returns {finesse.restservices.User} 16928 * This User object 16929 * @see finesse.restservices.User 16930 */ 16931 getRestItemClass: function () { 16932 return User; 16933 }, 16934 16935 /** 16936 * @private 16937 * Gets the REST type for the current object - this is a "Users". 16938 * @returns {String} The Users String 16939 */ 16940 getRestType: function () { 16941 return "Users"; 16942 }, 16943 16944 /** 16945 * @private 16946 * Gets the REST type for the objects that make up the collection - this is "User". 16947 * @returns {String} The User String 16948 */ 16949 getRestItemType: function () { 16950 return "User"; 16951 }, 16952 16953 /** 16954 * @private 16955 * Gets the node path for the current object - this is the team Users node 16956 * @returns {String} The node path 16957 */ 16958 getXMPPNodePath: function () { 16959 return this.getRestUrl(); 16960 }, 16961 16962 /** 16963 * @private 16964 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 16965 * This needs to be done because the GET /Team/id/Users API is missing 16966 * @returns {Users} This Users (collection) object to allow cascading 16967 */ 16968 _doGET: function (handlers) { 16969 var _this = this; 16970 handlers = handlers || {}; 16971 // Only do this for /Team/id/Users 16972 if (this._restObj && this._restObj.getRestType() === "Team") { 16973 this._restObj._doGET({ 16974 success: function (rspObj) { 16975 // Making sure the response was a valid Team 16976 if (_this._restObj._validate(rspObj.object)) { 16977 // Shimmying the response to look like a Users collection by extracting it from the Team response 16978 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 16979 handlers.success(rspObj); 16980 } else { 16981 handlers.error(rspObj); 16982 } 16983 }, 16984 error: handlers.error 16985 }); 16986 return this; // Allow cascading 16987 } else { 16988 return this._super(handlers); 16989 } 16990 }, 16991 16992 /** 16993 * @private 16994 * Override default to indicates that the collection doesn't support making 16995 * requests. 16996 */ 16997 supportsRequests: false, 16998 16999 /** 17000 * @private 17001 * Indicates that this collection handles the subscription for its items 17002 */ 17003 handlesItemSubscription: true, 17004 17005 /** 17006 * @private 17007 * Override default to indicate that we need to subscribe explicitly 17008 */ 17009 explicitSubscription: true 17010 17011 }); 17012 17013 window.finesse = window.finesse || {}; 17014 window.finesse.restservices = window.finesse.restservices || {}; 17015 window.finesse.restservices.Users = Users; 17016 17017 return Users; 17018 }); 17019 17020 /** 17021 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 17022 * 17023 * @requires finesse.clientservices.ClientServices 17024 * @requires Class 17025 * @requires finesse.FinesseBase 17026 * @requires finesse.restservices.RestBase 17027 */ 17028 17029 /** @private */ 17030 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 17031 17032 var TeamNotReadyReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamNotReadyReasonCode.prototype */{ 17033 17034 /** 17035 * @class 17036 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 17037 * methods to operate on the object against the server. 17038 * 17039 * @param {Object} options 17040 * An object with the following properties:<ul> 17041 * <li><b>id:</b> The id of the object being constructed</li> 17042 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17043 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17044 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17045 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17046 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17047 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17048 * <li><b>content:</b> {String} Raw string of response</li> 17049 * <li><b>object:</b> {Object} Parsed object of response</li> 17050 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17051 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17052 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17053 * </ul></li> 17054 * </ul></li> 17055 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17056 * @constructs 17057 **/ 17058 init: function (options) { 17059 this._super(options); 17060 }, 17061 17062 /** 17063 * @private 17064 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 17065 * @returns {Object} The TeamNotReadyReasonCode class. 17066 */ 17067 getRestClass: function () { 17068 return TeamNotReadyReasonCode; 17069 }, 17070 17071 /** 17072 * @private 17073 * Gets the REST type for the current object - this is a "ReasonCode". 17074 * @returns {String} The ReasonCode string. 17075 */ 17076 getRestType: function () { 17077 return "ReasonCode"; 17078 }, 17079 17080 /** 17081 * @private 17082 * Override default to indicate that this object doesn't support making 17083 * requests. 17084 */ 17085 supportsRequests: false, 17086 17087 /** 17088 * @private 17089 * Override default to indicate that this object doesn't support subscriptions. 17090 */ 17091 supportsSubscriptions: false, 17092 17093 /** 17094 * Getter for the category. 17095 * @returns {String} The category. 17096 */ 17097 getCategory: function () { 17098 this.isLoaded(); 17099 return this.getData().category; 17100 }, 17101 17102 /** 17103 * Getter for the code. 17104 * @returns {String} The code. 17105 */ 17106 getCode: function () { 17107 this.isLoaded(); 17108 return this.getData().code; 17109 }, 17110 17111 /** 17112 * Getter for the label. 17113 * @returns {String} The label. 17114 */ 17115 getLabel: function () { 17116 this.isLoaded(); 17117 return this.getData().label; 17118 }, 17119 17120 /** 17121 * Getter for the forAll value. 17122 * @returns {String} The forAll. 17123 */ 17124 getForAll: function () { 17125 this.isLoaded(); 17126 return this.getData().forAll; 17127 }, 17128 17129 /** 17130 * Getter for the Uri value. 17131 * @returns {String} The Uri. 17132 */ 17133 getUri: function () { 17134 this.isLoaded(); 17135 return this.getData().uri; 17136 }, 17137 /** 17138 * Getter for the systemCode value. 17139 * @returns {String} The value for systemCode. 17140 * @since 11.6(1)-ES1 onwards 17141 */ 17142 getSystemCode: function () { 17143 this.isLoaded(); 17144 return this.getData().systemCode; 17145 } 17146 17147 }); 17148 17149 window.finesse = window.finesse || {}; 17150 window.finesse.restservices = window.finesse.restservices || {}; 17151 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 17152 17153 return TeamNotReadyReasonCode; 17154 }); 17155 17156 /** 17157 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 17158 * object which contains a list of TeamNotReadyReasonCode objects. 17159 * 17160 * @requires finesse.clientservices.ClientServices 17161 * @requires Class 17162 * @requires finesse.FinesseBase 17163 * @requires finesse.restservices.RestBase 17164 * @requires finesse.restservices.Dialog 17165 * @requires finesse.restservices.RestCollectionBase 17166 */ 17167 17168 /** @private */ 17169 define('restservices/TeamNotReadyReasonCodes',[ 17170 'restservices/RestCollectionBase', 17171 'restservices/RestBase', 17172 'restservices/TeamNotReadyReasonCode' 17173 ], 17174 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 17175 17176 var TeamNotReadyReasonCodes = RestCollectionBase.extend(/** @lends finesse.restservices.TeamNotReadyReasonCodes.prototype */{ 17177 17178 /** 17179 * @class 17180 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 17181 * methods to operate on the object against the server. 17182 * 17183 * @param {Object} options 17184 * An object with the following properties:<ul> 17185 * <li><b>id:</b> The id of the object being constructed</li> 17186 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17187 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17188 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17189 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17190 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17191 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17192 * <li><b>content:</b> {String} Raw string of response</li> 17193 * <li><b>object:</b> {Object} Parsed object of response</li> 17194 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17195 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17196 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17197 * </ul></li> 17198 * </ul></li> 17199 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17200 * @augments finesse.restservices.RestCollectionBase 17201 * @constructs 17202 **/ 17203 init: function (options) { 17204 this._super(options); 17205 }, 17206 17207 /** 17208 * @private 17209 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 17210 * @returns {Object} 17211 * The TeamNotReadyReasonCodes constructor. 17212 */ 17213 getRestClass: function () { 17214 return TeamNotReadyReasonCodes; 17215 }, 17216 17217 /** 17218 * @private 17219 * Gets the REST class for the objects that make up the collection. - this 17220 * is the TeamNotReadyReasonCode class. 17221 * @returns {finesse.restservices.TeamNotReadyReasonCode} 17222 * The TeamNotReadyReasonCode Object 17223 * @see finesse.restservices.TeamNotReadyReasonCode 17224 */ 17225 getRestItemClass: function () { 17226 return TeamNotReadyReasonCode; 17227 }, 17228 17229 /** 17230 * @private 17231 * Gets the REST type for the current object - this is a "ReasonCodes". 17232 * @returns {String} The ReasonCodes String 17233 */ 17234 getRestType: function () { 17235 return "ReasonCodes"; 17236 }, 17237 17238 /** 17239 * @private 17240 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 17241 */ 17242 getRestUrl: function () { 17243 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 17244 var restObj = this._restObj, 17245 restUrl = ""; 17246 //Prepend the base REST object if one was provided. 17247 //Otherwise prepend with the default webapp name. 17248 if (restObj instanceof RestBase) { 17249 restUrl += restObj.getRestUrl(); 17250 } 17251 else { 17252 restUrl += "/finesse/api"; 17253 } 17254 //Append the REST type. 17255 restUrl += "/ReasonCodes?category=NOT_READY"; 17256 //Append ID if it is not undefined, null, or empty. 17257 if (this._id) { 17258 restUrl += "/" + this._id; 17259 } 17260 return restUrl; 17261 }, 17262 17263 /** 17264 * @private 17265 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 17266 */ 17267 getRestItemType: function () { 17268 return "ReasonCode"; 17269 }, 17270 17271 /** 17272 * @private 17273 * Override default to indicates that the collection supports making 17274 * requests. 17275 */ 17276 supportsRequests: true, 17277 17278 /** 17279 * @private 17280 * Override default to indicate that this object doesn't support subscriptions. 17281 */ 17282 supportsRestItemSubscriptions: false, 17283 17284 /** 17285 * @private 17286 * Retrieve the Not Ready Reason Codes. 17287 * 17288 * @returns {TeamNotReadyReasonCodes} 17289 * This TeamNotReadyReasonCodes object to allow cascading. 17290 */ 17291 get: function () { 17292 // set loaded to false so it will rebuild the collection after the get 17293 this._loaded = false; 17294 // reset collection 17295 this._collection = {}; 17296 // perform get 17297 this._synchronize(); 17298 return this; 17299 }, 17300 17301 /** 17302 * @private 17303 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 17304 * @param {Object} reasonCodes 17305 * @param {String} contentBody 17306 * @param successHandler 17307 * @return {function} 17308 */ 17309 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 17310 return function (rsp) { 17311 // Update internal structure based on response. Here we 17312 // inject the contentBody from the PUT request into the 17313 // rsp.object element to mimic a GET as a way to take 17314 // advantage of the existing _processResponse method. 17315 rsp.object = contentBody; 17316 reasonCodes._processResponse(rsp); 17317 17318 //Remove the injected contentBody object before cascading response 17319 rsp.object = {}; 17320 17321 //cascade response back to consumer's response handler 17322 successHandler(rsp); 17323 }; 17324 }, 17325 17326 /** 17327 * @private 17328 * Perform the REST API PUT call to update the reason code assignments for the team 17329 * @param {string[]} newValues 17330 * @param handlers 17331 */ 17332 update: function (newValues, handlers) { 17333 this.isLoaded(); 17334 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17335 17336 contentBody[this.getRestType()] = { 17337 }; 17338 17339 for (i in newValues) { 17340 if (newValues.hasOwnProperty(i)) { 17341 innerObject = { 17342 "uri": newValues[i] 17343 }; 17344 contentBodyInner.push(innerObject); 17345 } 17346 } 17347 17348 contentBody[this.getRestType()] = { 17349 "ReasonCode" : contentBodyInner 17350 }; 17351 17352 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17353 handlers = handlers || {}; 17354 17355 this.restRequest(this.getRestUrl(), { 17356 method: 'PUT', 17357 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17358 error: handlers.error, 17359 content: contentBody 17360 }); 17361 17362 return this; // Allow cascading 17363 } 17364 }); 17365 17366 window.finesse = window.finesse || {}; 17367 window.finesse.restservices = window.finesse.restservices || {}; 17368 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 17369 17370 return TeamNotReadyReasonCodes; 17371 }); 17372 17373 /** 17374 * JavaScript representation of the Finesse Team Wrap Up Reason object. 17375 * 17376 * @requires finesse.clientservices.ClientServices 17377 * @requires Class 17378 * @requires finesse.FinesseBase 17379 * @requires finesse.restservices.RestBase 17380 */ 17381 /** @private */ 17382 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 17383 17384 var TeamWrapUpReason = RestBase.extend({ 17385 17386 /** 17387 * @class 17388 * JavaScript representation of a TeamWrapUpReason object. Also exposes 17389 * methods to operate on the object against the server. 17390 * 17391 * @param {Object} options 17392 * An object with the following properties:<ul> 17393 * <li><b>id:</b> The id of the object being constructed</li> 17394 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17395 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17396 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17397 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17398 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17399 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17400 * <li><b>content:</b> {String} Raw string of response</li> 17401 * <li><b>object:</b> {Object} Parsed object of response</li> 17402 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17403 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17404 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17405 * </ul></li> 17406 * </ul></li> 17407 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17408 * @constructs 17409 **/ 17410 init: function (options) { 17411 this._super(options); 17412 }, 17413 17414 /** 17415 * @private 17416 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 17417 * @returns {Object} The TeamWrapUpReason class. 17418 */ 17419 getRestClass: function () { 17420 return TeamWrapUpReason; 17421 }, 17422 17423 /** 17424 * @private 17425 * Gets the REST type for the current object - this is a "WrapUpReason". 17426 * @returns {String} The WrapUpReason string. 17427 */ 17428 getRestType: function () { 17429 return "WrapUpReason"; 17430 }, 17431 17432 /** 17433 * @private 17434 * Override default to indicate that this object doesn't support making 17435 * requests. 17436 */ 17437 supportsRequests: false, 17438 17439 /** 17440 * @private 17441 * Override default to indicate that this object doesn't support subscriptions. 17442 */ 17443 supportsSubscriptions: false, 17444 17445 /** 17446 * Getter for the label. 17447 * @returns {String} The label. 17448 */ 17449 getLabel: function () { 17450 this.isLoaded(); 17451 return this.getData().label; 17452 }, 17453 17454 /** 17455 * @private 17456 * Getter for the forAll value. 17457 * @returns {Boolean} True if global 17458 */ 17459 getForAll: function () { 17460 this.isLoaded(); 17461 return this.getData().forAll; 17462 }, 17463 17464 /** 17465 * @private 17466 * Getter for the Uri value. 17467 * @returns {String} The Uri. 17468 */ 17469 getUri: function () { 17470 this.isLoaded(); 17471 return this.getData().uri; 17472 } 17473 }); 17474 17475 window.finesse = window.finesse || {}; 17476 window.finesse.restservices = window.finesse.restservices || {}; 17477 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 17478 17479 return TeamWrapUpReason; 17480 }); 17481 17482 /** 17483 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 17484 * object which contains a list of Wrap-Up Reasons objects. 17485 * 17486 * @requires finesse.clientservices.ClientServices 17487 * @requires Class 17488 * @requires finesse.FinesseBase 17489 * @requires finesse.restservices.RestBase 17490 * @requires finesse.restservices.Dialog 17491 * @requires finesse.restservices.RestCollectionBase 17492 */ 17493 /** @private */ 17494 define('restservices/TeamWrapUpReasons',[ 17495 'restservices/RestCollectionBase', 17496 'restservices/RestBase', 17497 'restservices/TeamWrapUpReason' 17498 ], 17499 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 17500 17501 var TeamWrapUpReasons = RestCollectionBase.extend({ 17502 17503 /** 17504 * @class 17505 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 17506 * methods to operate on the object against the server. 17507 * 17508 * @param {Object} options 17509 * An object with the following properties:<ul> 17510 * <li><b>id:</b> The id of the object being constructed</li> 17511 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17512 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17513 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17514 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17515 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17516 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17517 * <li><b>content:</b> {String} Raw string of response</li> 17518 * <li><b>object:</b> {Object} Parsed object of response</li> 17519 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17520 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17521 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17522 * </ul></li> 17523 * </ul></li> 17524 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17525 * @constructs 17526 **/ 17527 init: function (options) { 17528 this._super(options); 17529 }, 17530 17531 /** 17532 * @private 17533 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 17534 */ 17535 getRestClass: function () { 17536 return TeamWrapUpReasons; 17537 }, 17538 17539 /** 17540 * @private 17541 * Gets the REST class for the objects that make up the collection. - this 17542 * is the TeamWrapUpReason class. 17543 */ 17544 getRestItemClass: function () { 17545 return TeamWrapUpReason; 17546 }, 17547 17548 /** 17549 * @private 17550 * Gets the REST type for the current object - this is a "WrapUpReasons". 17551 */ 17552 getRestType: function () { 17553 return "WrapUpReasons"; 17554 }, 17555 17556 /** 17557 * @private 17558 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 17559 */ 17560 getRestItemType: function () { 17561 return "WrapUpReason"; 17562 }, 17563 17564 /** 17565 * @private 17566 * Override default to indicates that the collection supports making 17567 * requests. 17568 */ 17569 supportsRequests: true, 17570 17571 /** 17572 * @private 17573 * Override default to indicate that this object doesn't support subscriptions. 17574 */ 17575 supportsRestItemSubscriptions: false, 17576 17577 /** 17578 * Retrieve the Team Wrap Up Reasons. 17579 * 17580 * @returns {finesse.restservices.TeamWrapUpReasons} 17581 * This TeamWrapUpReasons object to allow cascading. 17582 */ 17583 get: function () { 17584 // set loaded to false so it will rebuild the collection after the get 17585 this._loaded = false; 17586 // reset collection 17587 this._collection = {}; 17588 // perform get 17589 this._synchronize(); 17590 return this; 17591 }, 17592 17593 /** 17594 * Set up the PutSuccessHandler for TeamWrapUpReasons 17595 * @param {Object} wrapUpReasons 17596 * @param {Object} contentBody 17597 * @param successHandler 17598 * @returns response 17599 */ 17600 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 17601 return function (rsp) { 17602 // Update internal structure based on response. Here we 17603 // inject the contentBody from the PUT request into the 17604 // rsp.object element to mimic a GET as a way to take 17605 // advantage of the existing _processResponse method. 17606 rsp.object = contentBody; 17607 17608 wrapUpReasons._processResponse(rsp); 17609 17610 //Remove the injected contentBody object before cascading response 17611 rsp.object = {}; 17612 17613 //cascade response back to consumer's response handler 17614 successHandler(rsp); 17615 }; 17616 }, 17617 17618 /** 17619 * Perform the REST API PUT call to update the reason code assignments for the team 17620 * @param {String Array} newValues 17621 * @param handlers 17622 * @returns {Object} this 17623 */ 17624 update: function (newValues, handlers) { 17625 this.isLoaded(); 17626 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17627 17628 contentBody[this.getRestType()] = { 17629 }; 17630 17631 for (i in newValues) { 17632 if (newValues.hasOwnProperty(i)) { 17633 innerObject = { 17634 "uri": newValues[i] 17635 }; 17636 contentBodyInner.push(innerObject); 17637 } 17638 } 17639 17640 contentBody[this.getRestType()] = { 17641 "WrapUpReason" : contentBodyInner 17642 }; 17643 17644 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17645 handlers = handlers || {}; 17646 17647 this.restRequest(this.getRestUrl(), { 17648 method: 'PUT', 17649 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17650 error: handlers.error, 17651 content: contentBody 17652 }); 17653 17654 return this; // Allow cascading 17655 } 17656 }); 17657 17658 window.finesse = window.finesse || {}; 17659 window.finesse.restservices = window.finesse.restservices || {}; 17660 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 17661 17662 return TeamWrapUpReasons; 17663 }); 17664 17665 /** 17666 * JavaScript representation of a TeamSignOutReasonCode. 17667 * 17668 * @requires finesse.clientservices.ClientServices 17669 * @requires Class 17670 * @requires finesse.FinesseBase 17671 * @requires finesse.restservices.RestBase 17672 */ 17673 17674 /** @private */ 17675 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 17676 var TeamSignOutReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamSignOutReasonCode.prototype */{ 17677 17678 /** 17679 * @class 17680 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 17681 * methods to operate on the object against the server. 17682 * 17683 * @param {Object} options 17684 * An object with the following properties:<ul> 17685 * <li><b>id:</b> The id of the object being constructed</li> 17686 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17687 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17688 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17689 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17690 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17691 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17692 * <li><b>content:</b> {String} Raw string of response</li> 17693 * <li><b>object:</b> {Object} Parsed object of response</li> 17694 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17695 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17696 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17697 * </ul></li> 17698 * </ul></li> 17699 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17700 * @constructs 17701 * @ignore 17702 **/ 17703 init: function (options) { 17704 this._super(options); 17705 }, 17706 17707 /** 17708 * @private 17709 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 17710 * @returns {Object} The TeamSignOutReasonCode class. 17711 */ 17712 getRestClass: function () { 17713 return TeamSignOutReasonCode; 17714 }, 17715 17716 /** 17717 * @private 17718 * Gets the REST type for the current object - this is a "ReasonCode". 17719 * @returns {String} The ReasonCode string. 17720 */ 17721 getRestType: function () { 17722 return "ReasonCode"; 17723 }, 17724 17725 /** 17726 * @private 17727 * Override default to indicate that this object doesn't support making 17728 * requests. 17729 */ 17730 supportsRequests: false, 17731 17732 /** 17733 * @private 17734 * Override default to indicate that this object doesn't support subscriptions. 17735 */ 17736 supportsSubscriptions: false, 17737 17738 /** 17739 * Getter for the category. 17740 * @returns {String} The category. 17741 */ 17742 getCategory: function () { 17743 this.isLoaded(); 17744 return this.getData().category; 17745 }, 17746 17747 /** 17748 * Getter for the code. 17749 * @returns {String} The code. 17750 */ 17751 getCode: function () { 17752 this.isLoaded(); 17753 return this.getData().code; 17754 }, 17755 17756 /** 17757 * Getter for the label. 17758 * @returns {String} The label. 17759 */ 17760 getLabel: function () { 17761 this.isLoaded(); 17762 return this.getData().label; 17763 }, 17764 17765 /** 17766 * Getter for the forAll value. 17767 * @returns {String} The forAll. 17768 */ 17769 getForAll: function () { 17770 this.isLoaded(); 17771 return this.getData().forAll; 17772 }, 17773 17774 /** 17775 * Getter for the Uri value. 17776 * @returns {String} The Uri. 17777 */ 17778 getUri: function () { 17779 this.isLoaded(); 17780 return this.getData().uri; 17781 }, 17782 /** 17783 * Getter for the systemCode value. 17784 * @returns {String} The value for systemCode. 17785 * @since 11.6(1)-ES1 onwards 17786 */ 17787 getSystemCode: function () { 17788 this.isLoaded(); 17789 return this.getData().systemCode; 17790 } 17791 17792 }); 17793 17794 window.finesse = window.finesse || {}; 17795 window.finesse.restservices = window.finesse.restservices || {}; 17796 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 17797 17798 return TeamSignOutReasonCode; 17799 }); 17800 17801 /** 17802 * JavaScript representation of the TeamSignOutReasonCodes collection 17803 * object which contains a list of TeamSignOutReasonCode objects. 17804 * 17805 * @requires finesse.clientservices.ClientServices 17806 * @requires Class 17807 * @requires finesse.FinesseBase 17808 * @requires finesse.restservices.RestBase 17809 * @requires finesse.restservices.Dialog 17810 * @requires finesse.restservices.RestCollectionBase 17811 */ 17812 17813 /** @private */ 17814 define('restservices/TeamSignOutReasonCodes',[ 17815 'restservices/RestCollectionBase', 17816 'restservices/RestBase', 17817 'restservices/TeamSignOutReasonCode' 17818 ], 17819 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 17820 17821 var TeamSignOutReasonCodes = RestCollectionBase.extend(/** @lends finesse.restservices.TeamSignOutReasonCodes.prototype */{ 17822 /** 17823 * @class 17824 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 17825 * methods to operate on the object against the server. 17826 * 17827 * @param {Object} options 17828 * An object with the following properties:<ul> 17829 * <li><b>id:</b> The id of the object being constructed</li> 17830 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17831 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17832 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17833 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17834 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17835 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17836 * <li><b>content:</b> {String} Raw string of response</li> 17837 * <li><b>object:</b> {Object} Parsed object of response</li> 17838 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17839 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17840 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17841 * </ul></li> 17842 * </ul></li> 17843 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17844 * @constructs 17845 **/ 17846 init: function (options) { 17847 this._super(options); 17848 }, 17849 17850 /** 17851 * @private 17852 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 17853 * @returns {Object} 17854 * The TeamSignOutReasonCodes constructor. 17855 */ 17856 getRestClass: function () { 17857 return TeamSignOutReasonCodes; 17858 }, 17859 17860 /** 17861 * @private 17862 * Gets the REST class for the objects that make up the collection. - this 17863 * is the TeamSignOutReasonCode class. 17864 * @returns {finesse.restservices.TeamSignOutReasonCode} 17865 * The TeamSignOutReasonCode Object 17866 * @see finesse.restservices.TeamSignOutReasonCode 17867 */ 17868 getRestItemClass: function () { 17869 return TeamSignOutReasonCode; 17870 }, 17871 17872 /** 17873 * @private 17874 * Gets the REST type for the current object - this is a "ReasonCodes". 17875 * @returns {String} The ReasonCodes String 17876 */ 17877 getRestType: function () { 17878 return "ReasonCodes"; 17879 }, 17880 17881 /** 17882 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 17883 */ 17884 getRestUrl: function () { 17885 var restObj = this._restObj, restUrl = ""; 17886 17887 //Prepend the base REST object if one was provided. 17888 //Otherwise prepend with the default webapp name. 17889 if (restObj instanceof RestBase) { 17890 restUrl += restObj.getRestUrl(); 17891 } else { 17892 restUrl += "/finesse/api"; 17893 } 17894 //Append the REST type. 17895 restUrl += "/ReasonCodes?category=LOGOUT"; 17896 //Append ID if it is not undefined, null, or empty. 17897 if (this._id) { 17898 restUrl += "/" + this._id; 17899 } 17900 return restUrl; 17901 }, 17902 17903 /** 17904 * @private 17905 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 17906 */ 17907 getRestItemType: function () { 17908 return "ReasonCode"; 17909 }, 17910 17911 /** 17912 * @private 17913 * Override default to indicates that the collection supports making requests. 17914 */ 17915 supportsRequests: true, 17916 17917 /** 17918 * @private 17919 * Override default to indicates that the collection does not subscribe to its objects. 17920 */ 17921 supportsRestItemSubscriptions: false, 17922 17923 /** 17924 * Retrieve the Sign Out Reason Codes. 17925 * 17926 * @returns {finesse.restservices.TeamSignOutReasonCodes} 17927 * This TeamSignOutReasonCodes object to allow cascading. 17928 */ 17929 get: function () { 17930 // set loaded to false so it will rebuild the collection after the get 17931 this._loaded = false; 17932 // reset collection 17933 this._collection = {}; 17934 // perform get 17935 this._synchronize(); 17936 return this; 17937 }, 17938 17939 /* We only use PUT and GET on Reason Code team assignments 17940 * @param {Object} contact 17941 * @param {Object} contentBody 17942 * @param {Function} successHandler 17943 */ 17944 createPutSuccessHandler: function (contact, contentBody, successHandler) { 17945 return function (rsp) { 17946 // Update internal structure based on response. Here we 17947 // inject the contentBody from the PUT request into the 17948 // rsp.object element to mimic a GET as a way to take 17949 // advantage of the existing _processResponse method. 17950 rsp.object = contentBody; 17951 contact._processResponse(rsp); 17952 17953 //Remove the injected contentBody object before cascading response 17954 rsp.object = {}; 17955 17956 //cascade response back to consumer's response handler 17957 successHandler(rsp); 17958 }; 17959 }, 17960 17961 /** 17962 * Update - This should be all that is needed. 17963 * @param {Object} newValues 17964 * @param {Object} handlers 17965 * @returns {finesse.restservices.TeamSignOutReasonCodes} 17966 * This TeamSignOutReasonCodes object to allow cascading. 17967 */ 17968 update: function (newValues, handlers) { 17969 this.isLoaded(); 17970 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17971 17972 contentBody[this.getRestType()] = { 17973 }; 17974 17975 for (i in newValues) { 17976 if (newValues.hasOwnProperty(i)) { 17977 innerObject = { 17978 "uri": newValues[i] 17979 }; 17980 contentBodyInner.push(innerObject); 17981 } 17982 } 17983 17984 contentBody[this.getRestType()] = { 17985 "ReasonCode" : contentBodyInner 17986 }; 17987 17988 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17989 handlers = handlers || {}; 17990 17991 this.restRequest(this.getRestUrl(), { 17992 method: 'PUT', 17993 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17994 error: handlers.error, 17995 content: contentBody 17996 }); 17997 17998 return this; // Allow cascading 17999 } 18000 18001 }); 18002 18003 window.finesse = window.finesse || {}; 18004 window.finesse.restservices = window.finesse.restservices || {}; 18005 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 18006 18007 return TeamSignOutReasonCodes; 18008 }); 18009 18010 /** 18011 * JavaScript representation of the Finesse PhoneBook Assignment object. 18012 * 18013 * @requires finesse.clientservices.ClientServices 18014 * @requires Class 18015 * @requires finesse.FinesseBase 18016 * @requires finesse.restservices.RestBase 18017 */ 18018 18019 /** 18020 * The following comment prevents JSLint errors concerning undefined global variables. 18021 * It tells JSLint that these identifiers are defined elsewhere. 18022 */ 18023 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 18024 18025 /** The following comment is to prevent jslint errors about 18026 * using variables before they are defined. 18027 */ 18028 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 18029 18030 /** @private */ 18031 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 18032 var TeamPhoneBook = RestBase.extend({ 18033 18034 /** 18035 * @class 18036 * JavaScript representation of a PhoneBook object. Also exposes 18037 * methods to operate on the object against the server. 18038 * 18039 * @param {Object} options 18040 * An object with the following properties:<ul> 18041 * <li><b>id:</b> The id of the object being constructed</li> 18042 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18043 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18044 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18045 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18046 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18047 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18048 * <li><b>content:</b> {String} Raw string of response</li> 18049 * <li><b>object:</b> {Object} Parsed object of response</li> 18050 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18051 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18052 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18053 * </ul></li> 18054 * </ul></li> 18055 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18056 * @constructs 18057 **/ 18058 init: function (options) { 18059 this._super(options); 18060 }, 18061 18062 /** 18063 * @private 18064 * Gets the REST class for the current object - this is the PhoneBooks class. 18065 * @returns {Object} The PhoneBooks class. 18066 */ 18067 getRestClass: function () { 18068 return TeamPhoneBook; 18069 }, 18070 18071 /** 18072 * @private 18073 * Gets the REST type for the current object - this is a "PhoneBook". 18074 * @returns {String} The PhoneBook string. 18075 */ 18076 getRestType: function () { 18077 return "PhoneBook"; 18078 }, 18079 18080 /** 18081 * @private 18082 * Override default to indicate that this object doesn't support making 18083 * requests. 18084 */ 18085 supportsRequests: false, 18086 18087 /** 18088 * @private 18089 * Override default to indicate that this object doesn't support subscriptions. 18090 */ 18091 supportsSubscriptions: false, 18092 18093 /** 18094 * Getter for the name. 18095 * @returns {String} The name. 18096 */ 18097 getName: function () { 18098 this.isLoaded(); 18099 return this.getData().name; 18100 }, 18101 18102 /** 18103 * Getter for the Uri value. 18104 * @returns {String} The Uri. 18105 */ 18106 getUri: function () { 18107 this.isLoaded(); 18108 return this.getData().uri; 18109 } 18110 18111 }); 18112 18113 window.finesse = window.finesse || {}; 18114 window.finesse.restservices = window.finesse.restservices || {}; 18115 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 18116 18117 return TeamPhoneBook; 18118 }); 18119 18120 /** 18121 * JavaScript representation of the Finesse PhoneBook Assignments collection 18122 * object which contains a list of Not Ready Reason Codes objects. 18123 * 18124 * @requires finesse.clientservices.ClientServices 18125 * @requires Class 18126 * @requires finesse.FinesseBase 18127 * @requires finesse.restservices.RestBase 18128 * @requires finesse.restservices.Dialog 18129 * @requires finesse.restservices.RestCollectionBase 18130 */ 18131 18132 /** 18133 * The following comment prevents JSLint errors concerning undefined global variables. 18134 * It tells JSLint that these identifiers are defined elsewhere. 18135 */ 18136 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 18137 18138 /** The following comment is to prevent jslint errors about 18139 * using variables before they are defined. 18140 */ 18141 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 18142 18143 /** @private */ 18144 define('restservices/TeamPhoneBooks',[ 18145 'restservices/RestCollectionBase', 18146 'restservices/RestBase', 18147 'restservices/TeamPhoneBook' 18148 ], 18149 function (RestCollectionBase, RestBase, TeamPhoneBook) { 18150 var TeamPhoneBooks = RestCollectionBase.extend({ 18151 18152 /** 18153 * @class 18154 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 18155 * methods to operate on the object against the server. 18156 * 18157 * @param {Object} options 18158 * An object with the following properties:<ul> 18159 * <li><b>id:</b> The id of the object being constructed</li> 18160 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18161 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18162 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18163 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18164 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18165 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18166 * <li><b>content:</b> {String} Raw string of response</li> 18167 * <li><b>object:</b> {Object} Parsed object of response</li> 18168 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18169 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18170 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18171 * </ul></li> 18172 * </ul></li> 18173 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18174 * @constructs 18175 **/ 18176 init: function (options) { 18177 this._super(options); 18178 }, 18179 18180 /** 18181 * @private 18182 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 18183 */ 18184 getRestClass: function () { 18185 return TeamPhoneBooks; 18186 }, 18187 18188 /** 18189 * @private 18190 * Gets the REST class for the objects that make up the collection. - this 18191 * is the TeamPhoneBooks class. 18192 */ 18193 getRestItemClass: function () { 18194 return TeamPhoneBook; 18195 }, 18196 18197 /** 18198 * @private 18199 * Gets the REST type for the current object - this is a "ReasonCodes". 18200 */ 18201 getRestType: function () { 18202 return "PhoneBooks"; 18203 }, 18204 18205 /** 18206 * Overrides the parent class. Returns the url for the PhoneBooks resource 18207 */ 18208 getRestUrl: function () { 18209 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 18210 var restObj = this._restObj, 18211 restUrl = ""; 18212 //Prepend the base REST object if one was provided. 18213 if (restObj instanceof RestBase) { 18214 restUrl += restObj.getRestUrl(); 18215 } 18216 //Otherwise prepend with the default webapp name. 18217 else { 18218 restUrl += "/finesse/api"; 18219 } 18220 //Append the REST type. 18221 restUrl += "/PhoneBooks"; 18222 //Append ID if it is not undefined, null, or empty. 18223 if (this._id) { 18224 restUrl += "/" + this._id; 18225 } 18226 return restUrl; 18227 }, 18228 18229 /** 18230 * @private 18231 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 18232 */ 18233 getRestItemType: function () { 18234 return "PhoneBook"; 18235 }, 18236 18237 /** 18238 * @private 18239 * Override default to indicates that the collection supports making 18240 * requests. 18241 */ 18242 supportsRequests: true, 18243 18244 /** 18245 * @private 18246 * Override default to indicates that the collection subscribes to its objects. 18247 */ 18248 supportsRestItemSubscriptions: false, 18249 18250 /** 18251 * Retrieve the Not Ready Reason Codes. 18252 * 18253 * @returns {finesse.restservices.TeamPhoneBooks} 18254 * This TeamPhoneBooks object to allow cascading. 18255 */ 18256 get: function () { 18257 // set loaded to false so it will rebuild the collection after the get 18258 /** @private */ 18259 this._loaded = false; 18260 // reset collection 18261 /** @private */ 18262 this._collection = {}; 18263 // perform get 18264 this._synchronize(); 18265 return this; 18266 }, 18267 18268 /* We only use PUT and GET on Reason Code team assignments 18269 */ 18270 createPutSuccessHandler: function(contact, contentBody, successHandler){ 18271 return function (rsp) { 18272 // Update internal structure based on response. Here we 18273 // inject the contentBody from the PUT request into the 18274 // rsp.object element to mimic a GET as a way to take 18275 // advantage of the existing _processResponse method. 18276 rsp.object = contentBody; 18277 contact._processResponse(rsp); 18278 18279 //Remove the injected Contact object before cascading response 18280 rsp.object = {}; 18281 18282 //cascade response back to consumer's response handler 18283 successHandler(rsp); 18284 }; 18285 }, 18286 18287 /** 18288 * Update - This should be all that is needed. 18289 */ 18290 update: function (newValues, handlers) { 18291 this.isLoaded(); 18292 var contentBody = {}, contentBodyInner = [], i, innerObject; 18293 18294 contentBody[this.getRestType()] = { 18295 }; 18296 18297 for (i in newValues) { 18298 if (newValues.hasOwnProperty(i)) { 18299 innerObject = {}; 18300 innerObject = { 18301 "uri": newValues[i] 18302 }; 18303 contentBodyInner.push(innerObject); 18304 } 18305 } 18306 18307 contentBody[this.getRestType()] = { 18308 "PhoneBook" : contentBodyInner 18309 }; 18310 18311 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18312 handlers = handlers || {}; 18313 18314 this.restRequest(this.getRestUrl(), { 18315 method: 'PUT', 18316 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 18317 error: handlers.error, 18318 content: contentBody 18319 }); 18320 18321 return this; // Allow cascading 18322 } 18323 18324 }); 18325 18326 window.finesse = window.finesse || {}; 18327 window.finesse.restservices = window.finesse.restservices || {}; 18328 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 18329 18330 return TeamPhoneBooks; 18331 }); 18332 18333 /** 18334 * JavaScript representation of the Finesse LayoutConfig object 18335 * @requires ClientServices 18336 * @requires finesse.FinesseBase 18337 * @requires finesse.restservices.RestBase 18338 */ 18339 18340 /** @private */ 18341 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 18342 /** @private */ 18343 var LayoutConfig = RestBase.extend({ 18344 18345 /** 18346 * @class 18347 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 18348 * on the object against the server. 18349 * 18350 * @param {String} id 18351 * Not required... 18352 * @param {Object} callbacks 18353 * An object containing callbacks for instantiation and runtime 18354 * @param {Function} callbacks.onLoad(this) 18355 * Callback to invoke upon successful instantiation 18356 * @param {Function} callbacks.onLoadError(rsp) 18357 * Callback to invoke on instantiation REST request error 18358 * as passed by finesse.clientservices.ClientServices.ajax() 18359 * { 18360 * status: {Number} The HTTP status code returned 18361 * content: {String} Raw string of response 18362 * object: {Object} Parsed object of response 18363 * error: {Object} Wrapped exception that was caught 18364 * error.errorType: {String} Type of error that was caught 18365 * error.errorMessage: {String} Message associated with error 18366 * } 18367 * @param {Function} callbacks.onChange(this) 18368 * Callback to invoke upon successful update 18369 * @param {Function} callbacks.onError(rsp) 18370 * Callback to invoke on update error (refresh or event) 18371 * as passed by finesse.clientservices.ClientServices.ajax() 18372 * { 18373 * status: {Number} The HTTP status code returned 18374 * content: {String} Raw string of response 18375 * object: {Object} Parsed object of response 18376 * error: {Object} Wrapped exception that was caught 18377 * error.errorType: {String} Type of error that was caught 18378 * error.errorMessage: {String} Message associated with error 18379 * } 18380 * 18381 * @constructs 18382 */ 18383 init: function (callbacks) { 18384 this._super("", callbacks); 18385 //when post is performed and id is empty 18386 /*if (id === "") { 18387 this._loaded = true; 18388 }*/ 18389 this._layoutxml = {}; 18390 }, 18391 18392 /** 18393 * Returns REST class of LayoutConfig object 18394 */ 18395 getRestClass: function () { 18396 return LayoutConfig; 18397 }, 18398 18399 /** 18400 * The type of this REST object is LayoutConfig 18401 */ 18402 getRestType: function () { 18403 return "LayoutConfig"; 18404 }, 18405 18406 /** 18407 * Gets the REST URL of this object. 18408 * 18409 * If the parent has an id, the id is appended. 18410 * On occasions of POST, it will not have an id. 18411 */ 18412 getRestUrl: function () { 18413 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 18414 /*if (this._id) { 18415 layoutUri = layoutUri + "/" + this._id; 18416 }*/ 18417 return layoutUri; 18418 }, 18419 18420 /** 18421 * This API does not support subscription 18422 */ 18423 supportsSubscriptions: false, 18424 18425 keepRestResponse: true, 18426 18427 18428 /** 18429 * Gets finesselayout.xml retrieved from the API call 18430 */ 18431 getLayoutxml: function () { 18432 this.isLoaded(); 18433 var layoutxml = this.getData().layoutxml; 18434 18435 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 18436 layoutxml = layoutxml.replace(/&/g,"&"); 18437 18438 return layoutxml; 18439 }, 18440 18441 /** 18442 * Gets the type of this LayoutConfig object 18443 */ 18444 /* 18445 getType: function () { 18446 this.isLoaded(); 18447 return this.getData().type; 18448 },*/ 18449 18450 /** 18451 * Retrieve the LayoutConfig settings. 18452 * If the id is not provided the API call will fail. 18453 * @returns {LayoutConfig} 18454 * This LayoutConfig object to allow cascading. 18455 */ 18456 get: function () { 18457 this._synchronize(); 18458 return this; 18459 }, 18460 18461 /** 18462 * Closure handle updating of the internal data for the LayoutConfig object 18463 * upon a successful update (PUT) request before calling the intended 18464 * success handler provided by the consumer 18465 * 18466 * @param {Object} 18467 * layoutconfig Reference to this LayoutConfig object 18468 * @param {Object} 18469 * LayoutConfig Object that contains the settings to be 18470 * submitted in the api request 18471 * @param {Function} 18472 * successHandler The success handler specified by the consumer 18473 * of this object 18474 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 18475 */ 18476 18477 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 18478 return function (rsp) { 18479 // Update internal structure based on response. Here we 18480 // inject the contentBody from the PUT request into the 18481 // rsp.object element to mimic a GET as a way to take 18482 // advantage of the existing _processResponse method. 18483 rsp.content = contentBody; 18484 rsp.object.LayoutConfig = {}; 18485 rsp.object.LayoutConfig.finesseLayout = contentBody; 18486 layoutconfig._processResponse(rsp); 18487 18488 //Remove the injected layoutConfig object before cascading response 18489 rsp.object.LayoutConfig = {}; 18490 18491 //cascade response back to consumer's response handler 18492 successHandler(rsp); 18493 }; 18494 }, 18495 18496 /** 18497 * Update LayoutConfig 18498 * @param {Object} finesselayout 18499 * The XML for FinesseLayout being stored 18500 * 18501 * @param {Object} handlers 18502 * An object containing callback handlers for the request. Optional. 18503 * @param {Function} options.success(rsp) 18504 * A callback function to be invoked for a successful request. 18505 * { 18506 * status: {Number} The HTTP status code returned 18507 * content: {String} Raw string of response 18508 * object: {Object} Parsed object of response 18509 * } 18510 * @param {Function} options.error(rsp) 18511 * A callback function to be invoked for an unsuccessful request. 18512 * { 18513 * status: {Number} The HTTP status code returned 18514 * content: {String} Raw string of response 18515 * object: {Object} Parsed object of response (HTTP errors) 18516 * error: {Object} Wrapped exception that was caught 18517 * error.errorType: {String} Type of error that was caught 18518 * error.errorMessage: {String} Message associated with error 18519 * } 18520 * @returns {finesse.restservices.LayoutConfig} 18521 * This LayoutConfig object to allow cascading 18522 */ 18523 18524 update: function (layoutxml, handlers) { 18525 this.isLoaded(); 18526 18527 18528 var contentBody = {}, 18529 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 18530 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 18531 18532 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 18533 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 18534 18535 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 18536 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 18537 18538 contentBody[this.getRestType()] = { 18539 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 18540 }; 18541 18542 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18543 handlers = handlers || {}; 18544 18545 this.restRequest(this.getRestUrl(), { 18546 method: 'PUT', 18547 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 18548 error: handlers.error, 18549 content: contentBody 18550 }); 18551 18552 return this; // Allow cascading 18553 } 18554 18555 /** 18556 *TODO createPostSuccessHandler needs to be debugged to make it working 18557 * Closure handle creating new LayoutConfig object 18558 * upon a successful create (POST) request before calling the intended 18559 * success handler provided by the consumer 18560 * 18561 * @param {Object} 18562 * layoutconfig Reference to this LayoutConfig object 18563 * @param {Object} 18564 * LayoutConfig Object that contains the settings to be 18565 * submitted in the api request 18566 * @param {Function} 18567 * successHandler The success handler specified by the consumer 18568 * of this object 18569 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 18570 */ 18571 /* 18572 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 18573 return function (rsp) { 18574 18575 rsp.object = contentBody; 18576 layoutconfig._processResponse(rsp); 18577 18578 //Remove the injected layoutConfig object before cascading response 18579 rsp.object = {}; 18580 18581 //cascade response back to consumer's response handler 18582 successHandler(rsp); 18583 }; 18584 }, */ 18585 18586 /** 18587 * TODO Method needs to be debugged to make POST working 18588 * Add LayoutConfig 18589 * @param {Object} finesselayout 18590 * The XML for FinesseLayout being stored 18591 * 18592 * @param {Object} handlers 18593 * An object containing callback handlers for the request. Optional. 18594 * @param {Function} options.success(rsp) 18595 * A callback function to be invoked for a successful request. 18596 * { 18597 * status: {Number} The HTTP status code returned 18598 * content: {String} Raw string of response 18599 * object: {Object} Parsed object of response 18600 * } 18601 * @param {Function} options.error(rsp) 18602 * A callback function to be invoked for an unsuccessful request. 18603 * { 18604 * status: {Number} The HTTP status code returned 18605 * content: {String} Raw string of response 18606 * object: {Object} Parsed object of response (HTTP errors) 18607 * error: {Object} Wrapped exception that was caught 18608 * error.errorType: {String} Type of error that was caught 18609 * error.errorMessage: {String} Message associated with error 18610 * } 18611 * @returns {finesse.restservices.LayoutConfig} 18612 * This LayoutConfig object to allow cascading 18613 */ 18614 /* 18615 add: function (layoutxml, handlers) { 18616 this.isLoaded(); 18617 var contentBody = {}; 18618 18619 18620 contentBody[this.getRestType()] = { 18621 "layoutxml": layoutxml, 18622 "type": "current" 18623 }; 18624 18625 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18626 handlers = handlers || {}; 18627 18628 this.restRequest(this.getRestUrl(), { 18629 method: 'POST', 18630 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 18631 error: handlers.error, 18632 content: contentBody 18633 }); 18634 18635 return this; // Allow cascading 18636 } */ 18637 }); 18638 18639 window.finesse = window.finesse || {}; 18640 window.finesse.restservices = window.finesse.restservices || {}; 18641 window.finesse.restservices.LayoutConfig = LayoutConfig; 18642 18643 return LayoutConfig; 18644 18645 }); 18646 18647 /** 18648 * JavaScript representation of the Finesse LayoutConfig object for a Team. 18649 * 18650 * @requires finesse.clientservices.ClientServices 18651 * @requires Class 18652 * @requires finesse.FinesseBase 18653 * @requires finesse.restservices.RestBase 18654 * @requires finesse.utilities.Utilities 18655 * @requires finesse.restservices.LayoutConfig 18656 */ 18657 18658 /** The following comment is to prevent jslint errors about 18659 * using variables before they are defined. 18660 */ 18661 /*global Exception */ 18662 18663 /** @private */ 18664 define('restservices/TeamLayoutConfig',[ 18665 'restservices/RestBase', 18666 'utilities/Utilities', 18667 'restservices/LayoutConfig' 18668 ], 18669 function (RestBase, Utilities, LayoutConfig) { 18670 18671 var TeamLayoutConfig = RestBase.extend({ 18672 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 18673 keepRestResponse: true, 18674 18675 /** 18676 * @class 18677 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 18678 * methods to operate on the object against the server. 18679 * 18680 * @param {Object} options 18681 * An object with the following properties:<ul> 18682 * <li><b>id:</b> The id of the object being constructed</li> 18683 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18684 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18685 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18686 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18687 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18688 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18689 * <li><b>content:</b> {String} Raw string of response</li> 18690 * <li><b>object:</b> {Object} Parsed object of response</li> 18691 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18692 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18693 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18694 * </ul></li> 18695 * </ul></li> 18696 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18697 * @constructs 18698 **/ 18699 init: function (options) { 18700 this._super(options); 18701 }, 18702 18703 /** 18704 * @private 18705 * Gets the REST class for the current object - this is the LayoutConfigs class. 18706 * @returns {Object} The LayoutConfigs class. 18707 */ 18708 getRestClass: function () { 18709 return TeamLayoutConfig; 18710 }, 18711 18712 /** 18713 * @private 18714 * Gets the REST type for the current object - this is a "LayoutConfig". 18715 * @returns {String} The LayoutConfig string. 18716 */ 18717 getRestType: function () { 18718 return "TeamLayoutConfig"; 18719 }, 18720 18721 /** 18722 * @private 18723 * Override default to indicate that this object doesn't support making 18724 * requests. 18725 */ 18726 supportsRequests: false, 18727 18728 /** 18729 * @private 18730 * Override default to indicate that this object doesn't support subscriptions. 18731 */ 18732 supportsSubscriptions: false, 18733 18734 /** 18735 * Getter for the category. 18736 * @returns {String} The category. 18737 */ 18738 getLayoutXML: function () { 18739 this.isLoaded(); 18740 var layoutxml = this.getData().layoutxml; 18741 18742 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 18743 layoutxml = layoutxml.replace(/&/g,"&"); 18744 18745 return layoutxml; 18746 }, 18747 18748 /** 18749 * Getter for the code. 18750 * @returns {String} The code. 18751 */ 18752 getUseDefault: function () { 18753 this.isLoaded(); 18754 return this.getData().useDefault; 18755 }, 18756 18757 /** 18758 * Retrieve the TeamLayoutConfig. 18759 * 18760 * @returns {finesse.restservices.TeamLayoutConfig} 18761 */ 18762 get: function () { 18763 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 18764 this._id = "0"; 18765 // set loaded to false so it will rebuild the collection after the get 18766 this._loaded = false; 18767 // reset collection 18768 this._collection = {}; 18769 // perform get 18770 this._synchronize(); 18771 return this; 18772 }, 18773 18774 createPutSuccessHandler: function(contact, contentBody, successHandler){ 18775 return function (rsp) { 18776 // Update internal structure based on response. Here we 18777 // inject the contentBody from the PUT request into the 18778 // rsp.object element to mimic a GET as a way to take 18779 // advantage of the existing _processResponse method. 18780 rsp.object = contentBody; 18781 contact._processResponse(rsp); 18782 18783 //Remove the injected Contact object before cascading response 18784 rsp.object = {}; 18785 18786 //cascade response back to consumer's response handler 18787 successHandler(rsp); 18788 }; 18789 }, 18790 18791 put: function (newValues, handlers) { 18792 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 18793 this._id = "0"; 18794 this.isLoaded(); 18795 18796 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 18797 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 18798 contentBody = {}, 18799 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 18800 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 18801 18802 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 18803 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 18804 18805 contentBody[this.getRestType()] = { 18806 "useDefault": newValues.useDefault, 18807 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 18808 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 18809 }; 18810 18811 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18812 handlers = handlers || {}; 18813 18814 this.restRequest(this.getRestUrl(), { 18815 method: 'PUT', 18816 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 18817 error: handlers.error, 18818 content: contentBody 18819 }); 18820 18821 return this; // Allow cascading 18822 }, 18823 18824 getRestUrl: function(){ 18825 // return team's url + /LayoutConfig 18826 // eg: /api/Team/1/LayoutConfig 18827 if(this._restObj === undefined){ 18828 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 18829 } 18830 return this._restObj.getRestUrl() + '/LayoutConfig'; 18831 } 18832 18833 }); 18834 18835 window.finesse = window.finesse || {}; 18836 window.finesse.restservices = window.finesse.restservices || {}; 18837 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 18838 18839 return TeamLayoutConfig; 18840 }); 18841 18842 /** 18843 * JavaScript representation of a TeamWorkflow. 18844 * 18845 * @requires finesse.clientservices.ClientServices 18846 * @requires Class 18847 * @requires finesse.FinesseBase 18848 * @requires finesse.restservices.RestBase 18849 */ 18850 /** @private */ 18851 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 18852 18853 var TeamWorkflow = RestBase.extend({ 18854 18855 /** 18856 * @class 18857 * JavaScript representation of a TeamWorkflow object. Also exposes 18858 * methods to operate on the object against the server. 18859 * 18860 * @param {Object} options 18861 * An object with the following properties:<ul> 18862 * <li><b>id:</b> The id of the object being constructed</li> 18863 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18864 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18865 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18866 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18867 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18868 * <li><b>status:</b> {Number} The HTTP status description returned</li> 18869 * <li><b>content:</b> {String} Raw string of response</li> 18870 * <li><b>object:</b> {Object} Parsed object of response</li> 18871 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18872 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18873 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18874 * </ul></li> 18875 * </ul></li> 18876 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18877 * @constructs 18878 **/ 18879 init: function (options) { 18880 this._super(options); 18881 }, 18882 18883 /** 18884 * @private 18885 * Gets the REST class for the current object - this is the TeamWorkflow class. 18886 * @returns {Object} The TeamWorkflow class. 18887 */ 18888 getRestClass: function () { 18889 return TeamWorkflow; 18890 }, 18891 18892 /** 18893 * @private 18894 * Gets the REST type for the current object - this is a "Workflow". 18895 * @returns {String} The Workflow string. 18896 */ 18897 getRestType: function () { 18898 return "Workflow"; 18899 }, 18900 18901 /** 18902 * @private 18903 * Override default to indicate that this object doesn't support making 18904 * requests. 18905 */ 18906 supportsRequests: false, 18907 18908 /** 18909 * @private 18910 * Override default to indicate that this object doesn't support subscriptions. 18911 */ 18912 supportsSubscriptions: false, 18913 18914 /** 18915 * Getter for the name. 18916 * @returns {String} The name. 18917 */ 18918 getName: function () { 18919 this.isLoaded(); 18920 return this.getData().name; 18921 }, 18922 18923 /** 18924 * Getter for the description. 18925 * @returns {String} The description. 18926 */ 18927 getDescription: function () { 18928 this.isLoaded(); 18929 return this.getData().description; 18930 }, 18931 18932 /** 18933 * Getter for the Uri value. 18934 * @returns {String} The Uri. 18935 */ 18936 getUri: function () { 18937 this.isLoaded(); 18938 return this.getData().uri; 18939 } 18940 18941 }); 18942 18943 window.finesse = window.finesse || {}; 18944 window.finesse.restservices = window.finesse.restservices || {}; 18945 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 18946 18947 return TeamWorkflow; 18948 }); 18949 18950 /** 18951 * JavaScript representation of the TeamWorkflows collection 18952 * object which contains a list of TeamWorkflow objects. 18953 * 18954 * @requires finesse.clientservices.ClientServices 18955 * @requires Class 18956 * @requires finesse.FinesseBase 18957 * @requires finesse.restservices.RestBase 18958 * @requires finesse.restservices.Dialog 18959 * @requires finesse.restservices.RestCollectionBase 18960 */ 18961 /** @private */ 18962 define('restservices/TeamWorkflows',[ 18963 'restservices/RestCollectionBase', 18964 'restservices/TeamWorkflow', 18965 'restservices/RestBase' 18966 ], 18967 function (RestCollectionBase, TeamWorkflow, RestBase) { 18968 18969 var TeamWorkflows = RestCollectionBase.extend({ 18970 18971 /** 18972 * @class 18973 * JavaScript representation of a TeamWorkflows collection object. Also exposes 18974 * methods to operate on the object against the server. 18975 * 18976 * @param {Object} options 18977 * An object with the following properties:<ul> 18978 * <li><b>id:</b> The id of the object being constructed</li> 18979 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18980 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18981 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18982 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18983 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18984 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18985 * <li><b>content:</b> {String} Raw string of response</li> 18986 * <li><b>object:</b> {Object} Parsed object of response</li> 18987 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18988 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18989 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18990 * </ul></li> 18991 * </ul></li> 18992 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18993 * @constructs 18994 **/ 18995 init: function (options) { 18996 this._super(options); 18997 }, 18998 18999 /** 19000 * @private 19001 * Gets the REST class for the current object - this is the TeamWorkflows class. 19002 */ 19003 getRestClass: function () { 19004 return TeamWorkflows; 19005 }, 19006 19007 /** 19008 * @private 19009 * Gets the REST class for the objects that make up the collection. - this 19010 * is the TeamWorkflow class. 19011 */ 19012 getRestItemClass: function () { 19013 return TeamWorkflow; 19014 }, 19015 19016 /** 19017 * @private 19018 * Gets the REST type for the current object - this is a "Workflows". 19019 */ 19020 getRestType: function () { 19021 return "Workflows"; 19022 }, 19023 19024 /** 19025 * Overrides the parent class. Returns the url for the Workflows resource 19026 */ 19027 getRestUrl: function () { 19028 var restObj = this._restObj, restUrl = ""; 19029 19030 //Prepend the base REST object if one was provided. 19031 //Otherwise prepend with the default webapp name. 19032 if (restObj instanceof RestBase) { 19033 restUrl += restObj.getRestUrl(); 19034 } else { 19035 restUrl += "/finesse/api/Team"; 19036 } 19037 //Append ID if it is not undefined, null, or empty. 19038 if (this._id) { 19039 restUrl += "/" + this._id; 19040 } 19041 //Append the REST type. 19042 restUrl += "/Workflows"; 19043 19044 return restUrl; 19045 }, 19046 19047 /** 19048 * @private 19049 * Gets the REST type for the objects that make up the collection - this is "Workflow". 19050 */ 19051 getRestItemType: function () { 19052 return "Workflow"; 19053 }, 19054 19055 /** 19056 * @private 19057 * Override default to indicates that the collection supports making requests. 19058 */ 19059 supportsRequests: true, 19060 19061 /** 19062 * @private 19063 * Override default to indicates that the collection does not subscribe to its objects. 19064 */ 19065 supportsRestItemSubscriptions: false, 19066 19067 /** 19068 * Retrieve the Sign Out Reason Codes. 19069 * 19070 * @returns {finesse.restservices.TeamWorkflows} 19071 * This TeamWorkflows object to allow cascading. 19072 */ 19073 get: function () { 19074 // set loaded to false so it will rebuild the collection after the get 19075 this._loaded = false; 19076 // reset collection 19077 this._collection = {}; 19078 // perform get 19079 this._synchronize(); 19080 return this; 19081 }, 19082 19083 /* We only use PUT and GET on Reason Code team assignments 19084 * @param {Object} contact 19085 * @param {Object} contentBody 19086 * @param {Function} successHandler 19087 */ 19088 createPutSuccessHandler: function (contact, contentBody, successHandler) { 19089 return function (rsp) { 19090 // Update internal structure based on response. Here we 19091 // inject the contentBody from the PUT request into the 19092 // rsp.object element to mimic a GET as a way to take 19093 // advantage of the existing _processResponse method. 19094 rsp.object = contentBody; 19095 contact._processResponse(rsp); 19096 19097 //Remove the injected contentBody object before cascading response 19098 rsp.object = {}; 19099 19100 //cascade response back to consumer's response handler 19101 successHandler(rsp); 19102 }; 19103 }, 19104 19105 /** 19106 * Update - This should be all that is needed. 19107 * @param {Object} newValues 19108 * @param {Object} handlers 19109 * @returns {finesse.restservices.TeamWorkflows} 19110 * This TeamWorkflows object to allow cascading. 19111 */ 19112 update: function (newValues, handlers) { 19113 this.isLoaded(); 19114 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 19115 19116 contentBody[this.getRestType()] = { 19117 }; 19118 19119 for (i in newValues) { 19120 if (newValues.hasOwnProperty(i)) { 19121 innerObject = { 19122 "uri": newValues[i] 19123 }; 19124 contentBodyInner.push(innerObject); 19125 } 19126 } 19127 19128 contentBody[this.getRestType()] = { 19129 "Workflow" : contentBodyInner 19130 }; 19131 19132 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 19133 handlers = handlers || {}; 19134 19135 this.restRequest(this.getRestUrl(), { 19136 method: 'PUT', 19137 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 19138 error: handlers.error, 19139 content: contentBody 19140 }); 19141 19142 return this; // Allow cascading 19143 } 19144 19145 }); 19146 19147 window.finesse = window.finesse || {}; 19148 window.finesse.restservices = window.finesse.restservices || {}; 19149 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 19150 19151 return TeamWorkflows; 19152 }); 19153 19154 /** 19155 * JavaScript representation of the Finesse TeamMessage Message object. 19156 * 19157 * @requires finesse.clientservices.ClientServices 19158 * @requires Class 19159 * @requires finesse.FinesseBase 19160 * @requires finesse.restservices.RestBase 19161 */ 19162 19163 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 19164 /*global define,finesse */ 19165 19166 /** @private */ 19167 define('restservices/TeamMessage',[ 19168 'restservices/RestBase' 19169 ], 19170 function (RestBase) { 19171 19172 var TeamMessage = RestBase.extend({ 19173 19174 /** 19175 * @class 19176 * JavaScript representation of a TeamMessage message object. Also exposes 19177 * methods to operate on the object against the server. 19178 * 19179 * @param {Object} options 19180 * An object with the following properties:<ul> 19181 * <li><b>id:</b> The id of the object being constructed</li> 19182 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19183 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19184 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19185 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19186 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19187 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19188 * <li><b>content:</b> {String} Raw string of response</li> 19189 * <li><b>object:</b> {Object} Parsed object of response</li> 19190 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19191 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19192 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19193 * </ul></li> 19194 * </ul></li> 19195 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19196 * @constructs 19197 **/ 19198 init: function (options) { 19199 this._super(options); 19200 }, 19201 19202 /** 19203 * @private 19204 * Gets the REST class for the current object - this is the TeamMessage class. 19205 * @returns {Object} The TeamMessage class. 19206 */ 19207 getRestClass: function () { 19208 throw new Error("getRestClass(): Not implemented in subtype."); 19209 }, 19210 19211 /** 19212 * @private 19213 * Gets the REST type for the current object - this is a "TeamMessage". 19214 * @returns {String} The Workflow string. 19215 */ 19216 getRestType: function () { 19217 return "TeamMessage"; 19218 }, 19219 19220 19221 /** 19222 * @private 19223 * Override default to indicate that this object doesn't support making 19224 * requests. 19225 */ 19226 supportsRequests: false, 19227 19228 /** 19229 * @private 19230 * Override default to indicate that this object doesn't support subscriptions. 19231 */ 19232 supportsSubscriptions: false, 19233 19234 /** 19235 * Getter for the TeamMessageuri value. 19236 * @returns {String} uri. 19237 */ 19238 getTeamMessageUri: function () { 19239 this.isLoaded(); 19240 return this.getData().uri; 19241 }, 19242 19243 /** 19244 * Getter for the teamMessage id value. 19245 * @returns {String} id. 19246 */ 19247 getId: function () { 19248 this.isLoaded(); 19249 return this.getData().id; 19250 }, 19251 19252 /** 19253 * Getter for the teamMessage createdBy value. 19254 * @returns {String} createdBy. 19255 */ 19256 getCreatedBy: function () { 19257 this.isLoaded(); 19258 return this.getData().createdBy; 19259 }, 19260 /** 19261 * Getter for the teamMessage createdAt value. 19262 * @returns {String} createdAt. 19263 */ 19264 getCreatedAt: function () { 19265 this.isLoaded(); 19266 return this.getData().createdAt; 19267 }, 19268 19269 /** 19270 * Getter for the teamMessage duration value. 19271 * @returns {String} duration. 19272 */ 19273 getDuration: function () { 19274 this.isLoaded(); 19275 return this.getData().duration; 19276 }, 19277 /** 19278 * Getter for the teamMessage content value. 19279 * @returns {String} content. 19280 */ 19281 getContent: function () { 19282 this.isLoaded(); 19283 return this.getData().content; 19284 19285 }, 19286 19287 add: function (newValues, handlers) { 19288 // this.isLoaded(); 19289 var contentBody = {}; 19290 19291 contentBody[this.getRestType()] = { 19292 "duration": newValues.expires, 19293 "content": newValues.message, 19294 "teams": newValues.teams 19295 19296 }; 19297 19298 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 19299 handlers = handlers || {}; 19300 19301 this.restRequest(this.getRestUrl(), { 19302 method: 'POST', 19303 success: handlers.success, 19304 error: handlers.error, 19305 content: contentBody 19306 }); 19307 19308 return this; // Allow cascading 19309 } 19310 19311 }); 19312 19313 window.finesse = window.finesse || {}; 19314 window.finesse.restservices = window.finesse.restservices || {}; 19315 window.finesse.restservices.TeamMessage = TeamMessage; 19316 19317 return TeamMessage; 19318 }); 19319 19320 /** 19321 * JavaScript representation of the Finesse TeamMessages object for a Team. 19322 * 19323 * @requires Class 19324 * @requires finesse.FinesseBase 19325 * @requires finesse.restservices.RestBase 19326 * @requires finesse.utilities.Utilities 19327 * @requires finesse.restservices.TeamMessage 19328 */ 19329 19330 /** The following comment is to prevent jslint errors about 19331 * using variables before they are defined. 19332 */ 19333 /*global Exception */ 19334 19335 /** @private */ 19336 define('restservices/TeamMessages',[ 19337 'restservices/RestCollectionBase', 19338 'restservices/RestBase', 19339 'restservices/TeamMessage', 19340 'utilities/Utilities' 19341 ], 19342 function (RestCollectionBase, RestBase, TeamMessage, Utilities) { 19343 19344 var TeamMessages = RestCollectionBase.extend({ 19345 19346 /** 19347 * @class 19348 * JavaScript representation of a TeamMessages object for a Team. Also exposes 19349 * methods to operate on the object against the server. 19350 * 19351 * @param {Object} options 19352 * An object with the following properties:<ul> 19353 * <li><b>id:</b> The id of the object being constructed</li> 19354 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19355 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19356 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19357 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19358 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19359 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19360 * <li><b>content:</b> {String} Raw string of response</li> 19361 * <li><b>object:</b> {Object} Parsed object of response</li> 19362 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19363 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19364 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19365 * </ul></li> 19366 * </ul></li> 19367 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19368 * @constructs 19369 **/ 19370 init: function (options) { 19371 this._super(options); 19372 }, 19373 19374 /** 19375 * Gets the REST class for the current object - this is the TeamMessages class. 19376 * @returns {Object} The TeamMessages class. 19377 */ 19378 getRestClass: function () { 19379 return TeamMessages; 19380 }, 19381 /** 19382 * Gets the REST class for the rest item - this is the TeamMessages class. 19383 * @returns {Object} The TeamMessages class. 19384 */ 19385 getRestItemClass: function () { 19386 return TeamMessage; 19387 }, 19388 19389 /** 19390 * Gets the REST type for the current object - that is a "teamMessages" class. 19391 * @returns {String} The teamMessages string. 19392 */ 19393 getRestType: function () { 19394 return "teamMessages"; 19395 }, 19396 19397 /** 19398 * Gets the REST type for the current object - this is a "TeamMessage" class. 19399 * @returns {String} The TeamMessage string. 19400 */ 19401 getRestItemType: function () { 19402 return "TeamMessage"; 19403 }, 19404 19405 19406 /** 19407 * @private 19408 * Override default to indicate that this object support making 19409 * requests. 19410 */ 19411 supportsRequests: true, 19412 19413 /** 19414 * @private 19415 * Override default to indicate that this object support subscriptions. 19416 */ 19417 supportsSubscriptions: true, 19418 /** 19419 * @private 19420 * Override default to indicates that the collection subscribes to its objects. 19421 */ 19422 supportsRestItemSubscriptions: true, 19423 19424 /** 19425 * Getter for the teamMessages. 19426 * @returns {Object} teamMessages. 19427 */ 19428 getBroadcastMessages: function () { 19429 this.isLoaded(); 19430 return this.getData; 19431 }, 19432 19433 19434 getRestUrl: function () { 19435 // return team's url + /TeamMessages 19436 // eg: /api/Team/1/TeamMessages 19437 if (this._restObj === undefined) { 19438 throw new Exception("Broadcast message instances must have a parent team object."); 19439 } 19440 return this._restObj.getRestUrl() + '/TeamMessages'; 19441 }, 19442 19443 _buildCollection: function (data) { 19444 var i, object, objectId, dataArray; 19445 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 19446 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 19447 for (i = 0; i < dataArray.length; i += 1) { 19448 19449 object = {}; 19450 object[this.getRestItemType()] = dataArray[i]; 19451 19452 //get id from object.id instead of uri, since uri is not there for some reason 19453 objectId = object[this.getRestItemType()].id; //this._extractId(object); 19454 19455 //create the Media Object 19456 this._collection[objectId] = new(this.getRestItemClass())({ 19457 id: objectId, 19458 data: object, 19459 parentObj: this._restObj 19460 }); 19461 this.length += 1; 19462 } 19463 } 19464 } 19465 19466 }); 19467 19468 window.finesse = window.finesse || {}; 19469 window.finesse.restservices = window.finesse.restservices || {}; 19470 window.finesse.restservices.TeamMessages = TeamMessages; 19471 19472 return TeamMessages; 19473 }); 19474 19475 /** 19476 * JavaScript representation of the Finesse Team REST object. 19477 * 19478 * @requires finesse.clientservices.ClientServices 19479 * @requires Class 19480 * @requires finesse.FinesseBase 19481 * @requires finesse.restservices.RestBase 19482 * @requires finesse.restservices.RestCollectionBase 19483 * @requires finesse.restservices.User 19484 * @requires finesse.restservices.Users 19485 */ 19486 19487 /** 19488 * The following comment prevents JSLint errors concerning undefined global variables. 19489 * It tells JSLint that these identifiers are defined elsewhere. 19490 */ 19491 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 19492 19493 /** The following comment is to prevent jslint errors about 19494 * using variables before they are defined. 19495 */ 19496 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 19497 19498 /** @private */ 19499 define('restservices/Team',[ 19500 'restservices/RestBase', 19501 'utilities/Utilities', 19502 'restservices/Users', 19503 'restservices/TeamNotReadyReasonCodes', 19504 'restservices/TeamWrapUpReasons', 19505 'restservices/TeamSignOutReasonCodes', 19506 'restservices/TeamPhoneBooks', 19507 'restservices/TeamLayoutConfig', 19508 'restservices/TeamWorkflows', 19509 'restservices/TeamMessages' 19510 ], 19511 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows, TeamMessages) { 19512 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 19513 19514 _teamLayoutConfig: null, 19515 19516 /** 19517 * 19518 * @class 19519 * JavaScript representation of a Team object. Also exposes methods to operate 19520 * on the object against the server. 19521 * 19522 * @param {Object} options 19523 * An object with the following properties:<ul> 19524 * <li><b>id:</b> The id of the object being constructed</li> 19525 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19526 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19527 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19528 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19529 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19530 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19531 * <li><b>content:</b> {String} Raw string of response</li> 19532 * <li><b>object:</b> {Object} Parsed object of response</li> 19533 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19534 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19535 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19536 * </ul></li> 19537 * </ul></li> 19538 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19539 * @augments finesse.restservices.RestBase 19540 * @constructs 19541 * @example 19542 * _team = new finesse.restservices.Team({ 19543 * id: _id, 19544 * onLoad : _handleTeamLoad(team), 19545 * onChange : _handleTeamChange(team) 19546 * }); 19547 **/ 19548 init: function (options) { 19549 this._super(options); 19550 }, 19551 19552 /** 19553 * @private 19554 * Gets the REST class for the current object - this is the Team class. 19555 * @returns {Object} The Team constructor. 19556 */ 19557 getRestClass: function () { 19558 return finesse.restesrvices.Team; 19559 }, 19560 19561 /** 19562 * @private 19563 * Gets the REST type for the current object - this is a "Team". 19564 * @returns {String} The Team string. 19565 */ 19566 getRestType: function () { 19567 return "Team"; 19568 }, 19569 19570 /** 19571 * @private 19572 * Override default to indicate that this object doesn't support making 19573 * requests. 19574 */ 19575 supportsSubscriptions: false, 19576 19577 /** 19578 * Getter for the team id. 19579 * @returns {String} The team id. 19580 */ 19581 getId: function () { 19582 this.isLoaded(); 19583 return this.getData().id; 19584 }, 19585 19586 /** 19587 * Getter for the team name. 19588 * @returns {String} The team name 19589 */ 19590 getName: function () { 19591 this.isLoaded(); 19592 return this.getData().name; 19593 }, 19594 19595 /** 19596 * @private 19597 * Getter for the team uri. 19598 * @returns {String} The team uri 19599 */ 19600 getUri: function () { 19601 this.isLoaded(); 19602 return this.getData().uri; 19603 }, 19604 19605 /** 19606 * Constructs and returns a collection of Users. 19607 * @param {Object} options that sets callback handlers. 19608 * An object with the following properties:<ul> 19609 * <li><b>onLoad(users): (optional)</b> when the object is successfully loaded from the server</li> 19610 * <li><b>onError(): (optional)</b> if loading of the object fails, invoked with the error response object</li> 19611 * @returns {finesse.restservices.Users} Users collection of User objects. 19612 */ 19613 getUsers: function (options) { 19614 this.isLoaded(); 19615 options = options || {}; 19616 19617 options.parentObj = this; 19618 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 19619 // for Users needs the "Users" key to validate the provided payload matches the class type. 19620 options.data = this.getData(); 19621 19622 return new Users(options); 19623 }, 19624 19625 /** 19626 * @private 19627 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 19628 * @param callbacks 19629 * @returns {teamNotReadyReasonCodes} 19630 * A teamNotReadyReasonCodes collection object. 19631 */ 19632 getTeamNotReadyReasonCodes: function (callbacks) { 19633 var options = callbacks || {}; 19634 options.parentObj = this; 19635 this.isLoaded(); 19636 19637 if (!this._teamNotReadyReasonCodes) { 19638 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 19639 } 19640 19641 return this._teamNotReadyReasonCodes; 19642 }, 19643 19644 /** 19645 * @private 19646 * Getter for a teamWrapUpReasons collection object that is associated with Team. 19647 * @param callbacks 19648 * @returns {teamWrapUpReasons} 19649 * A teamWrapUpReasons collection object. 19650 */ 19651 getTeamWrapUpReasons: function (callbacks) { 19652 var options = callbacks || {}; 19653 options.parentObj = this; 19654 this.isLoaded(); 19655 19656 if (!this._teamWrapUpReasons) { 19657 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 19658 } 19659 19660 return this._teamWrapUpReasons; 19661 }, 19662 19663 /** 19664 * @private 19665 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 19666 * @param callbacks 19667 * @returns {teamSignOutReasonCodes} 19668 * A teamSignOutReasonCodes collection object. 19669 */ 19670 19671 getTeamSignOutReasonCodes: function (callbacks) { 19672 var options = callbacks || {}; 19673 options.parentObj = this; 19674 this.isLoaded(); 19675 19676 if (!this._teamSignOutReasonCodes) { 19677 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 19678 } 19679 19680 return this._teamSignOutReasonCodes; 19681 }, 19682 19683 /** 19684 * @private 19685 * Getter for a teamPhoneBooks collection object that is associated with Team. 19686 * @param callbacks 19687 * @returns {teamPhoneBooks} 19688 * A teamPhoneBooks collection object. 19689 */ 19690 getTeamPhoneBooks: function (callbacks) { 19691 var options = callbacks || {}; 19692 options.parentObj = this; 19693 this.isLoaded(); 19694 19695 if (!this._phonebooks) { 19696 this._phonebooks = new TeamPhoneBooks(options); 19697 } 19698 19699 return this._phonebooks; 19700 }, 19701 19702 /** 19703 * @private 19704 * Getter for a teamWorkflows collection object that is associated with Team. 19705 * @param callbacks 19706 * @returns {teamWorkflows} 19707 * A teamWorkflows collection object. 19708 */ 19709 getTeamWorkflows: function (callbacks) { 19710 var options = callbacks || {}; 19711 options.parentObj = this; 19712 this.isLoaded(); 19713 19714 if (!this._workflows) { 19715 this._workflows = new TeamWorkflows(options); 19716 } 19717 19718 return this._workflows; 19719 }, 19720 19721 /** 19722 * @private 19723 * Getter for a teamLayoutConfig object that is associated with Team. 19724 * @param callbacks 19725 * @returns {teamLayoutConfig} 19726 */ 19727 getTeamLayoutConfig: function (callbacks) { 19728 var options = callbacks || {}; 19729 options.parentObj = this; 19730 this.isLoaded(); 19731 19732 if (this._teamLayoutConfig === null) { 19733 this._teamLayoutConfig = new TeamLayoutConfig(options); 19734 } 19735 19736 return this._teamLayoutConfig; 19737 }, 19738 /** 19739 * @private 19740 * Getter for the teamMessages object that is associated with Team. 19741 * @param callbacks 19742 * @returns {teamMessages} 19743 */ 19744 getTeamMessages: function (callbacks) { 19745 var options = callbacks || {}; 19746 options.parentObj = this; 19747 this.isLoaded(); 19748 if(!this._teamMessages) { 19749 this._teamMessages = new TeamMessages(options); 19750 } 19751 return this._teamMessages; 19752 } 19753 19754 }); 19755 19756 window.finesse = window.finesse || {}; 19757 window.finesse.restservices = window.finesse.restservices || {}; 19758 window.finesse.restservices.Team = Team; 19759 19760 return Team; 19761 }); 19762 19763 /** 19764 * JavaScript representation of the Finesse Teams collection. 19765 * object which contains a list of Team objects 19766 * @requires finesse.clientservices.ClientServices 19767 * @requires Class 19768 * @requires finesse.FinesseBase 19769 * @requires finesse.restservices.RestBase 19770 * @requires finesse.restservices.RestCollectionBase 19771 */ 19772 19773 /** @private */ 19774 define('restservices/Teams',[ 19775 'restservices/RestCollectionBase', 19776 'restservices/Team' 19777 ], 19778 function (RestCollectionBase, Team) { 19779 /** @private */ 19780 var Teams = RestCollectionBase.extend({ 19781 19782 /** 19783 * @class 19784 * JavaScript representation of a Teams collection object. Also exposes methods to operate 19785 * on the object against the server. 19786 * 19787 * @param {Object} options 19788 * An object with the following properties:<ul> 19789 * <li><b>id:</b> The id of the object being constructed</li> 19790 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19791 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19792 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19793 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19794 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19795 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19796 * <li><b>content:</b> {String} Raw string of response</li> 19797 * <li><b>object:</b> {Object} Parsed object of response</li> 19798 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19799 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19800 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19801 * </ul></li> 19802 * </ul></li> 19803 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19804 * @constructs 19805 **/ 19806 init: function (options) { 19807 this._super(options); 19808 }, 19809 19810 /** 19811 * @private 19812 * Gets the REST class for the current object - this is the Teams class. 19813 * @returns {Object} The Teams constructor. 19814 */ 19815 getRestClass: function () { 19816 return Teams; 19817 }, 19818 19819 /** 19820 * @private 19821 * Gets the REST class for the objects that make up the collection. - this 19822 * is the Team class. 19823 */ 19824 getRestItemClass: function () { 19825 return Team; 19826 }, 19827 19828 /** 19829 * @private 19830 * Gets the REST type for the current object - this is a "Teams". 19831 * @returns {String} The Teams string. 19832 */ 19833 getRestType: function () { 19834 return "Teams"; 19835 }, 19836 19837 /** 19838 * @private 19839 * Gets the REST type for the objects that make up the collection - this is "Team". 19840 */ 19841 getRestItemType: function () { 19842 return "Team"; 19843 }, 19844 19845 /** 19846 * @private 19847 * Override default to indicates that the collection supports making 19848 * requests. 19849 */ 19850 supportsRequests: true, 19851 19852 /** 19853 * @private 19854 * Override default to indicate that this object doesn't support subscriptions. 19855 */ 19856 supportsRestItemSubscriptions: false, 19857 19858 /** 19859 * @private 19860 * Retrieve the Teams. This call will re-query the server and refresh the collection. 19861 * 19862 * @returns {finesse.restservices.Teams} 19863 * This Teams object to allow cascading. 19864 */ 19865 get: function () { 19866 // set loaded to false so it will rebuild the collection after the get 19867 this._loaded = false; 19868 // reset collection 19869 this._collection = {}; 19870 // perform get 19871 this._synchronize(); 19872 return this; 19873 } 19874 19875 }); 19876 19877 window.finesse = window.finesse || {}; 19878 window.finesse.restservices = window.finesse.restservices || {}; 19879 window.finesse.restservices.Teams = Teams; 19880 19881 return Teams; 19882 }); 19883 19884 /** 19885 * JavaScript representation of the Finesse SystemInfo object 19886 * 19887 * @requires finesse.clientservices.ClientServices 19888 * @requires Class 19889 * @requires finesse.FinesseBase 19890 * @requires finesse.restservices.RestBase 19891 */ 19892 19893 /** @private */ 19894 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 19895 19896 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 19897 /** 19898 * @private 19899 * Returns whether this object supports subscriptions 19900 */ 19901 supportsSubscriptions: false, 19902 19903 doNotRefresh: true, 19904 19905 /** 19906 * @class 19907 * JavaScript representation of a SystemInfo object. 19908 * 19909 * @augments finesse.restservices.RestBase 19910 * @see finesse.restservices.SystemInfo.Statuses 19911 * @constructs 19912 */ 19913 _fakeConstuctor: function () { 19914 /* This is here to hide the real init constructor from the public docs */ 19915 }, 19916 19917 /** 19918 * @private 19919 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 19920 * on the object against the server. 19921 * 19922 * @param {Object} options 19923 * An object with the following properties:<ul> 19924 * <li><b>id:</b> The id of the object being constructed</li> 19925 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19926 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19927 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19928 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19929 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19930 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19931 * <li><b>content:</b> {String} Raw string of response</li> 19932 * <li><b>object:</b> {Object} Parsed object of response</li> 19933 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19934 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19935 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19936 * </ul></li> 19937 * </ul></li> 19938 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19939 **/ 19940 init: function (id, callbacks, restObj) 19941 { 19942 this._super(id, callbacks, restObj); 19943 }, 19944 19945 /** 19946 * @private 19947 * Gets the REST class for the current object - this is the SystemInfo object. 19948 * @returns {Object} The SystemInfo constructor. 19949 */ 19950 getRestClass: function () { 19951 return SystemInfo; 19952 }, 19953 19954 /** 19955 * @private 19956 * Gets the REST type for the current object - this is a "SystemInfo". 19957 * @returns {String} The SystemInfo string. 19958 */ 19959 getRestType: function () 19960 { 19961 return "SystemInfo"; 19962 }, 19963 19964 _validate: function (obj) 19965 { 19966 return true; 19967 }, 19968 19969 /** 19970 * Returns the status of the Finesse system. 19971 * IN_SERVICE if the Finesse API reports that it is in service, 19972 * OUT_OF_SERVICE otherwise. 19973 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 19974 */ 19975 getStatus: function () { 19976 this.isLoaded(); 19977 return this.getData().status; 19978 }, 19979 19980 /** 19981 * Returns the lastCTIHeartbeatStatus 19982 * success - last CTI heartbeat status was successful. 19983 * failure - last CTI heartbeat status was unsuccessful. 19984 * @returns {finesse.restservices.SystemInfo.lastCTIHeartbeatStatus} Last Heartbeat to CTI was successful or not. 19985 */ 19986 getLastCTIHeartbeatStatus: function () { 19987 this.isLoaded(); 19988 return this.getData().lastCTIHeartbeatStatus; 19989 }, 19990 19991 /** 19992 * Returns the reason due to which Finesse is OUT OF SERVICE. 19993 * It returns empty string when Finesse status is IN_SERVICE. 19994 * @returns {String} statusReason if finesse is OUT OF SERVICE , or empty string otherwise. 19995 */ 19996 getStatusReason: function () { 19997 this.isLoaded(); 19998 return this.getData().statusReason; 19999 }, 20000 20001 /** 20002 * Returns the current timestamp from this SystemInfo object. 20003 * This is used to calculate time drift delta between server and client. 20004 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 20005 */ 20006 getCurrentTimestamp: function () { 20007 this.isLoaded(); 20008 return this.getData().currentTimestamp; 20009 }, 20010 20011 /** 20012 * Getter for the xmpp domain of the system. 20013 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 20014 */ 20015 getXmppDomain: function () { 20016 this.isLoaded(); 20017 return this.getData().xmppDomain; 20018 }, 20019 20020 /** 20021 * Getter for the xmpp pubsub domain of the system. 20022 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 20023 */ 20024 getXmppPubSubDomain: function () { 20025 this.isLoaded(); 20026 return this.getData().xmppPubSubDomain; 20027 }, 20028 20029 /** 20030 * Getter for the deployment type (UCCE or UCCX). 20031 * @returns {String} "UCCE" or "UCCX" 20032 */ 20033 getDeploymentType: function () { 20034 this.isLoaded(); 20035 return this.getData().deploymentType; 20036 }, 20037 20038 /** 20039 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 20040 * @returns {Boolean} True for single node deployments, false otherwise. 20041 */ 20042 isSingleNode: function () { 20043 var secondary = this.getData().secondaryNode; 20044 if (secondary && secondary.host) { 20045 return false; 20046 } 20047 return true; 20048 }, 20049 20050 /** 20051 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 20052 * @param {String} arguments - any number of arguments to match against 20053 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 20054 */ 20055 getAlternateHost: function () { 20056 var i, 20057 isPrimary = false, 20058 primary = this.getData().primaryNode, 20059 secondary = this.getData().secondaryNode, 20060 xmppDomain = this.getData().xmppDomain, 20061 alternateHost; 20062 20063 if (primary && primary.host) { 20064 if (xmppDomain === primary.host) { 20065 isPrimary = true; 20066 } 20067 if (secondary && secondary.host) { 20068 if (isPrimary) { 20069 return secondary.host; 20070 } 20071 return primary.host; 20072 } 20073 } 20074 }, 20075 20076 /** 20077 * Gets the peripheral ID that Finesse is connected to. The peripheral 20078 * ID is the ID of the PG Routing Client (PIM). 20079 * 20080 * @returns {String} The peripheral Id if UCCE, or empty string otherwise. 20081 */ 20082 getPeripheralId : function () { 20083 this.isLoaded(); 20084 20085 var peripheralId = this.getData().peripheralId; 20086 if (peripheralId === null) { 20087 return ""; 20088 } else { 20089 return this.getData().peripheralId; 20090 } 20091 }, 20092 20093 /** 20094 * Gets the license. Only apply to UCCX. 20095 * 20096 * @returns {String} The license if UCCX, or empty string otherwise. 20097 */ 20098 getlicense : function () { 20099 this.isLoaded(); 20100 return this.getData().license || ""; 20101 }, 20102 20103 /** 20104 * Gets the systemAuthMode for the current deployment 20105 * 20106 * @returns {String} The System auth mode for current deployment 20107 */ 20108 getSystemAuthMode : function() { 20109 this.isLoaded(); 20110 return this.getData().systemAuthMode; 20111 }, 20112 20113 /** 20114 * Gets the ctiVersion for the current deployment 20115 * 20116 * @returns {String} The CTI version used in finesse to connect CTI server 20117 */ 20118 getCtiVersion : function() { 20119 this.isLoaded(); 20120 return this.getData().ctiVersion; 20121 }, 20122 20123 /** 20124 * Gets the ctiHeartbeatInterval for the current deployment 20125 * 20126 * @returns {String} The ctiHeartbeatInterval used in finesse to connect CTI server 20127 */ 20128 getHeartbeatInterval : function() { 20129 this.isLoaded(); 20130 return this.getData().ctiHeartbeatInterval; 20131 }, 20132 20133 /** 20134 * Gets the failover time slot assigned to an agent. It is used in the client side failover logic. 20135 * 20136 * @returns {String} A sequence number 20137 */ 20138 getFailoverTimeSlot : function() { 20139 this.isLoaded(); 20140 return this.getData().failoverTimeSlot; 20141 }, 20142 20143 /** 20144 * Gets the time zone offset 20145 * 20146 * @returns {String} time zone offset 20147 */ 20148 getTimeZoneOffset: function() { 20149 this.isLoaded(); 20150 return this.getData().timezoneOffset; 20151 } 20152 20153 }); 20154 20155 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 20156 /** 20157 * Finesse is in service. 20158 */ 20159 IN_SERVICE: "IN_SERVICE", 20160 /** 20161 * Finesse is not in service. 20162 */ 20163 OUT_OF_SERVICE: "OUT_OF_SERVICE", 20164 /** 20165 * @class SystemInfo status values. 20166 * @constructs 20167 */ 20168 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20169 20170 }; 20171 20172 window.finesse = window.finesse || {}; 20173 window.finesse.restservices = window.finesse.restservices || {}; 20174 window.finesse.restservices.SystemInfo = SystemInfo; 20175 20176 return SystemInfo; 20177 }); 20178 20179 define('restservices/DialogLogoutActions',[], function () 20180 { 20181 var DialogLogoutActions = /** @lends finesse.restservices.DialogLogoutActions.prototype */ { 20182 20183 /** 20184 * Set this action to close active dialogs when the agent logs out. 20185 */ 20186 CLOSE: "CLOSE", 20187 20188 /** 20189 * Set this action to transfer active dialogs when the agent logs out. 20190 */ 20191 TRANSFER: "TRANSFER", 20192 20193 /** 20194 * @class Actions used to handle tasks that are associated with a given media at logout time. 20195 * 20196 * @constructs 20197 */ 20198 _fakeConstructor: function () 20199 { 20200 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20201 20202 /** 20203 * Is the given action a valid dialog logout action. 20204 * 20205 * @param {String} action the action to evaluate 20206 * @returns {Boolean} true if the action is valid; false otherwise 20207 */ 20208 isValidAction: function(action) 20209 { 20210 if ( !action ) 20211 { 20212 return false; 20213 } 20214 20215 return DialogLogoutActions.hasOwnProperty(action.toUpperCase()); 20216 } 20217 }; 20218 20219 window.finesse = window.finesse || {}; 20220 window.finesse.restservices = window.finesse.restservices || {}; 20221 window.finesse.restservices.DialogLogoutActions = DialogLogoutActions; 20222 20223 return DialogLogoutActions; 20224 }); 20225 define('restservices/InterruptActions',[], function () 20226 { 20227 var InterruptActions = /** @lends finesse.restservices.InterruptActions.prototype */ 20228 { 20229 /** 20230 * The interrupt will be accepted and the agent will not work on dialogs in this media until the media is no longer interrupted. 20231 */ 20232 ACCEPT: "ACCEPT", 20233 20234 /** 20235 * the interrupt will be ignored and the agent is allowed to work on dialogs while the media is interrupted. 20236 */ 20237 IGNORE: "IGNORE", 20238 20239 /** 20240 * @class 20241 * 20242 * The action to be taken in the event this media is interrupted. The action will be one of the following:<ul> 20243 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on dialogs in this media 20244 * until the media is no longer interrupted.</li> 20245 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on dialogs while the 20246 * media is interrupted.</li></ul> 20247 * 20248 * @constructs 20249 */ 20250 _fakeConstructor: function () 20251 { 20252 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20253 20254 /** 20255 * Method to check whether the given action a valid dialog logout action. 20256 * 20257 * @param {String} The action to evaluate. 20258 * @returns {Boolean} true if the action is valid; false otherwise 20259 * @example restInterruptActions.isValidAction(); 20260 */ 20261 isValidAction: function(action) 20262 { 20263 if ( !action ) 20264 { 20265 return false; 20266 } 20267 20268 return InterruptActions.hasOwnProperty(action.toUpperCase()); 20269 } 20270 }; 20271 20272 window.finesse = window.finesse || {}; 20273 window.finesse.restservices = window.finesse.restservices || {}; 20274 window.finesse.restservices.InterruptActions = InterruptActions; 20275 20276 return InterruptActions; 20277 }); 20278 /** 20279 * Utility class for looking up a ReasonCode using the code and category. 20280 * 20281 */ 20282 20283 /** @private */ 20284 define('restservices/ReasonCodeLookup',['restservices/RestBase', 'utilities/Utilities', 'restservices/TeamResource'], function (RestBase, Utilities, TeamResource) { 20285 20286 var ReasonCodeLookup = RestBase.extend(/** @lends finesse.restservices.ReasonCodeLookup.prototype */{ 20287 /** 20288 * @private 20289 * Returns whether this object supports subscriptions 20290 */ 20291 supportsSubscriptions: false, 20292 20293 doNotRefresh: true, 20294 20295 autoSubscribe: false, 20296 20297 supportsRequests: false, 20298 20299 /** 20300 * @class 20301 * Utility class for looking up a ReasonCode using the code and category. 20302 * 20303 * @constructs 20304 */ 20305 _fakeConstuctor: function () { 20306 /* This is here to hide the real init constructor from the public docs */ 20307 }, 20308 20309 /** 20310 * @private 20311 * JavaScript representation of a ReasonCodeLookup object. Also exposes methods to operate 20312 * on the object against the server. 20313 * 20314 * @param {Object} options 20315 * An object with the following properties:<ul> 20316 * <li><b>id:</b> The id of the object being constructed</li> 20317 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 20318 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 20319 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 20320 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 20321 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 20322 * <li><b>status:</b> {Number} The HTTP status code returned</li> 20323 * <li><b>content:</b> {String} Raw string of response</li> 20324 * <li><b>object:</b> {Object} Parsed object of response</li> 20325 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 20326 * <li><b>errorType:</b> {String} Type of error that was caught</li> 20327 * <li><b>errorMessage:</b> {String} Message associated with error</li> 20328 * </ul></li> 20329 * </ul></li> 20330 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 20331 **/ 20332 init: function (options){ 20333 this._super(options); 20334 }, 20335 20336 /** 20337 * @private 20338 * Do get disabled. 20339 */ 20340 doGet: function(handlers) { 20341 return; 20342 }, 20343 20344 /** 20345 * @private 20346 * Gets the REST class for the current object - this is the ReasonCodeLookup object. 20347 */ 20348 getRestClass: function () { 20349 return ReasonCodeLookup; 20350 }, 20351 20352 /** 20353 * @private 20354 * Gets the REST type for the current object - this is a "ReasonCodeLookup". 20355 */ 20356 getRestType: function () { 20357 return "ReasonCode"; 20358 }, 20359 20360 20361 /** 20362 * @private 20363 * Parses a uriString to retrieve the id portion 20364 * @param {String} uriString 20365 * @return {String} id 20366 */ 20367 _parseIdFromUriString : function (uriString) { 20368 return Utilities.getId(uriString); 20369 }, 20370 20371 /** 20372 * Performs a GET against the Finesse server, for looking up the reason code 20373 * with its reason code value, and category. 20374 * Note that there is no return value; use the success handler to process a 20375 * valid return. 20376 * 20377 * @param {finesse.interfaces.RequestHandlers} handlers 20378 * An object containing the handlers for the request 20379 * @param {String} reasonCodeValue The code for the reason code to lookup 20380 * @param {String} reasonCodeCategory The category for the reason code to lookup. 20381 * The possible values are "NOT_READY" and "LOGOUT". 20382 * 20383 * @example 20384 * new finesse.restservices.ReasonCodeLookup().lookupReasonCode({ 20385 * success: _handleReasonCodeGet, 20386 * error: _handleReasonCodeGetError 20387 * }, '32762', 'NOT_READY'); 20388 * _handleReasonCodeGet(_reasonCode) { 20389 * var id = _reasonCode.id; 20390 * var uri = _reasonCode.uri; 20391 * var label = _reasonCode.label; 20392 * ... 20393 * } 20394 * 20395 */ 20396 lookupReasonCode : function (handlers, reasonCodeValue, reasonCodeCategory) { 20397 20398 if(!this._teamResource){ 20399 var config = finesse.container? finesse.container : finesse.gadget; 20400 var teamId= config.Config.teamId; 20401 this._teamResource = new TeamResource({teamId: teamId}); 20402 } 20403 // Get all the reason codes on team, including system and global reason codes 20404 this._teamResource.lookupReasonCode(handlers, reasonCodeValue, reasonCodeCategory); 20405 } 20406 }); 20407 20408 20409 window.finesse = window.finesse || {}; 20410 window.finesse.restservices = window.finesse.restservices || {}; 20411 window.finesse.restservices.ReasonCodeLookup = ReasonCodeLookup; 20412 20413 return ReasonCodeLookup; 20414 }); 20415 20416 /** 20417 * JavaScript representation of the Finesse ChatConfig object 20418 * @requires ClientServices 20419 * @requires finesse.FinesseBase 20420 * @requires finesse.restservices.RestBase 20421 */ 20422 20423 /** @private */ 20424 define('restservices/ChatConfig',['restservices/RestBase'], function (RestBase) { 20425 20426 var ChatConfig = RestBase.extend(/** @lends finesse.restservices.ChatConfig.prototype */{ 20427 20428 /** 20429 * @class 20430 * JavaScript representation of a ChatConfig object. Also exposes methods to operate 20431 * on the object against the server. 20432 * 20433 * @constructor 20434 * @param {String} id 20435 * Not required... 20436 * @param {Object} callbacks 20437 * An object containing callbacks for instantiation and runtime 20438 * @param {Function} callbacks.onLoad(this) 20439 * Callback to invoke upon successful instantiation, passes in User object 20440 * @param {Function} callbacks.onLoadError(rsp) 20441 * Callback to invoke on instantiation REST request error 20442 * as passed by finesse.clientservices.ClientServices.ajax() 20443 * { 20444 * status: {Number} The HTTP status code returned 20445 * content: {String} Raw string of response 20446 * object: {Object} Parsed object of response 20447 * error: {Object} Wrapped exception that was caught 20448 * error.errorType: {String} Type of error that was caught 20449 * error.errorMessage: {String} Message associated with error 20450 * } 20451 * @param {Function} callbacks.onChange(this) 20452 * Callback to invoke upon successful update, passes in User object 20453 * @param {Function} callbacks.onError(rsp) 20454 * Callback to invoke on update error (refresh or event) 20455 * as passed by finesse.clientservices.ClientServices.ajax() 20456 * { 20457 * status: {Number} The HTTP status code returned 20458 * content: {String} Raw string of response 20459 * object: {Object} Parsed object of response 20460 * error: {Object} Wrapped exception that was caught 20461 * error.errorType: {String} Type of error that was caught 20462 * error.errorMessage: {String} Message associated with error 20463 * } 20464 * @constructs 20465 */ 20466 init: function (callbacks) { 20467 this._super("", callbacks); 20468 }, 20469 20470 /** 20471 * Gets the REST class for the current object - this is the ChatConfig object. 20472 */ 20473 getRestClass: function () { 20474 return ChatConfig; 20475 }, 20476 20477 /** 20478 * Gets the REST type for the current object - this is a "ChatConfig". 20479 */ 20480 getRestType: function () { 20481 return "ChatConfig"; 20482 }, 20483 20484 /** 20485 * Overrides the parent class. Returns the url for the ChatConfig resource 20486 */ 20487 getRestUrl: function () { 20488 return ("/finesse/api/" + this.getRestType()); 20489 }, 20490 20491 /** 20492 * Returns whether this object supports subscriptions 20493 */ 20494 supportsSubscriptions: false, 20495 20496 /** 20497 * Getter for the Chat primary node of the ChatConfig 20498 * @returns {String} 20499 */ 20500 getPrimaryNode: function () { 20501 this.isLoaded(); 20502 return this.getData().primaryNode; 20503 }, 20504 20505 /** 20506 * Getter for the Chat secondary node (if any) of the ChatConfig 20507 * @returns {String} 20508 */ 20509 getSecondaryNode: function () { 20510 this.isLoaded(); 20511 return this.getData().secondaryNode; 20512 }, 20513 20514 /** 20515 * Retrieve the chat config settings 20516 * @returns {finesse.restservices.ChatConfig} 20517 * This ChatConfig object to allow cascading 20518 */ 20519 get: function () { 20520 this._synchronize(); 20521 20522 return this; // Allow cascading 20523 }, 20524 20525 /** 20526 * Closure handle updating of the internal data for the ChatConfig object 20527 * upon a successful update (PUT) request before calling the intended 20528 * success handler provided by the consumer 20529 * 20530 * @param {Object} 20531 * chatconfig Reference to this ChatConfig object 20532 * @param {Object} 20533 * chatSettings Object that contains the chat server settings to be 20534 * submitted in the api request 20535 * @param {Function} 20536 * successHandler The success handler specified by the consumer 20537 * of this object 20538 * @returns {finesse.restservices.ChatConfig} This ChatConfig object to allow cascading 20539 */ 20540 createPutSuccessHandler: function (chatconfig, chatSettings, successHandler) { 20541 return function (rsp) { 20542 // Update internal structure based on response. Here we 20543 // inject the chatSettings object into the 20544 // rsp.object.ChatConfig element as a way to take 20545 // advantage of the existing _processResponse method. 20546 rsp.object.ChatConfig = chatSettings; 20547 chatconfig._processResponse(rsp); 20548 20549 //Remove the injected chatSettings object before cascading response 20550 rsp.object.ChatConfig = {}; 20551 20552 //cascade response back to consumer's response handler 20553 successHandler(rsp); 20554 }; 20555 }, 20556 20557 /** 20558 * Update the chat config settings 20559 * @param {Object} chatSettings 20560 * The Chat server settings you want to configure 20561 * @param {Object} handlers 20562 * An object containing callback handlers for the request. Optional. 20563 * @param {Function} options.success(rsp) 20564 * A callback function to be invoked for a successful request. 20565 * { 20566 * status: {Number} The HTTP status code returned 20567 * content: {String} Raw string of response 20568 * object: {Object} Parsed object of response 20569 * } 20570 * @param {Function} options.error(rsp) 20571 * A callback function to be invoked for an unsuccessful request. 20572 * { 20573 * status: {Number} The HTTP status code returned 20574 * content: {String} Raw string of response 20575 * object: {Object} Parsed object of response (HTTP errors) 20576 * error: {Object} Wrapped exception that was caught 20577 * error.errorType: {String} Type of error that was caught 20578 * error.errorMessage: {String} Message associated with error 20579 * } 20580 * @returns {finesse.restservices.ChatConfig} 20581 * This ChatConfig object to allow cascading 20582 */ 20583 update: function (chatSettings, handlers) { 20584 this.isLoaded(); 20585 var contentBody = {}; 20586 20587 contentBody[this.getRestType()] = { 20588 "primaryNode": chatSettings.primaryNode, 20589 "secondaryNode": chatSettings.secondaryNode 20590 }; 20591 20592 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 20593 handlers = handlers || {}; 20594 20595 this.restRequest(this.getRestUrl(), { 20596 method: 'PUT', 20597 success: this.createPutSuccessHandler(this, chatSettings, handlers.success), 20598 error: handlers.error, 20599 content: contentBody 20600 }); 20601 20602 return this; // Allow cascading 20603 } 20604 }); 20605 20606 window.finesse = window.finesse || {}; 20607 window.finesse.restservices = window.finesse.restservices || {}; 20608 window.finesse.restservices.ChatConfig = ChatConfig; 20609 20610 return ChatConfig; 20611 }); 20612 20613 /** 20614 * JavaScript representation of the Finesse ECCVariableConfig object 20615 * @requires ClientServices 20616 * @requires finesse.FinesseBase 20617 * @requires finesse.restservices.RestBase 20618 */ 20619 20620 /** The following comment is to prevent jslint errors about 20621 * using variables before they are defined. 20622 */ 20623 /*global finesse*/ 20624 20625 /** @private */ 20626 define('restservices/ECCVariableConfig.js',['restservices/RestBase'], function (RestBase) { 20627 var ECCVariableConfig = RestBase.extend(/** @lends finesse.restservices.ECCVariableConfig.prototype */{ 20628 20629 /** 20630 * @class 20631 * JavaScript representation of a ECCVariableConfig object. Also exposes methods to operate 20632 * on the object against the server. 20633 * 20634 * @constructor 20635 * @param {String} id 20636 * Not required... 20637 * @param {Object} callbacks 20638 * An object containing callbacks for instantiation and runtime 20639 * @param {Function} callbacks.onLoad(this) 20640 * Callback to invoke upon successful instantiation 20641 * @param {Function} callbacks.onLoadError(rsp) 20642 * Callback to invoke on instantiation REST request error 20643 * as passed by finesse.clientservices.ClientServices.ajax() 20644 * { 20645 * status: {Number} The HTTP status code returned 20646 * content: {String} Raw string of response 20647 * object: {Object} Parsed object of response 20648 * error: {Object} Wrapped exception that was caught 20649 * error.errorType: {String} Type of error that was caught 20650 * error.errorMessage: {String} Message associated with error 20651 * } 20652 * @param {Function} callbacks.onChange(this) 20653 * Callback to invoke upon successful update 20654 * @param {Function} callbacks.onError(rsp) 20655 * Callback to invoke on update error (refresh or event) 20656 * as passed by finesse.clientservices.ClientServices.ajax() 20657 * { 20658 * status: {Number} The HTTP status code returned 20659 * content: {String} Raw string of response 20660 * object: {Object} Parsed object of response 20661 * error: {Object} Wrapped exception that was caught 20662 * error.errorType: {String} Type of error that was caught 20663 * error.errorMessage: {String} Message associated with error 20664 * } 20665 * 20666 * @constructs 20667 */ 20668 init: function (callbacks) { 20669 this._super("", callbacks); 20670 }, 20671 20672 /** 20673 * Gets the REST class for the current object - this is the 20674 * ECCVariableConfig object. 20675 */ 20676 getRestClass: function () { 20677 return ECCVariableConfig; 20678 }, 20679 20680 /** 20681 * Gets the REST type for the current object - this is a 20682 * "ECCVariableConfig". 20683 */ 20684 getRestType: function () { 20685 return "ECCVariableConfig"; 20686 }, 20687 20688 /** 20689 * Overrides the parent class. Returns the url for the 20690 * ECCVariableConfig resource. 20691 * @returns {String} 20692 */ 20693 getRestUrl: function () { 20694 return ("/finesse/api/" + this.getRestType()); 20695 }, 20696 20697 /** 20698 * Returns whether this object supports subscriptions 20699 */ 20700 supportsSubscriptions: false, 20701 20702 /** 20703 * Retrieve the ECCVariableConfig settings. 20704 * @returns {ECCVariableConfig} 20705 * This ECCVariableConfig object to allow cascading. 20706 */ 20707 get: function () { 20708 this._synchronize(); 20709 return this; 20710 } 20711 }); 20712 20713 window.finesse = window.finesse || {}; 20714 window.finesse.restservices = window.finesse.restservices || {}; 20715 window.finesse.restservices.ECCVariableConfig = ECCVariableConfig; 20716 20717 return ECCVariableConfig; 20718 }); 20719 20720 /** 20721 * Provides standard way resolve message keys with substitution 20722 * 20723 * @requires finesse.container.I18n or gadgets.Prefs 20724 */ 20725 20726 // Add Utilities to the finesse.utilities namespace 20727 define('utilities/I18n',['utilities/Utilities'], function (Utilities) { 20728 var I18n = (function () { 20729 20730 /** 20731 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 20732 * @private 20733 */ 20734 var _getMsg; 20735 20736 return { 20737 /** 20738 * Provides a message resolver for this utility singleton. 20739 * @param {Function} getMsg 20740 * A function that returns a string given a message key. 20741 * If the key is not found, this function must return 20742 * something that tests false (i.e. undefined or ""). 20743 */ 20744 setGetter : function (getMsg) { 20745 _getMsg = getMsg; 20746 }, 20747 20748 /** 20749 * Resolves the given message key, also performing substitution. 20750 * This generic utility will use a custom function to resolve the key 20751 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 20752 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 20753 * upon the first invocation and store that reference for efficiency. 20754 * 20755 * Since this will construct a new gadgets.Prefs object, it is recommended 20756 * for gadgets to explicitly provide the setter to prevent duplicate 20757 * gadgets.Prefs objects. This does not apply if your gadget does not need 20758 * access to gadgets.Prefs other than getMsg. 20759 * 20760 * @param {String} key 20761 * The key to lookup 20762 * @param {String} arguments 20763 * Arguments for substitution 20764 * @returns {String/Function} 20765 * The resolved string if successful, otherwise a function that returns 20766 * a '???' string that can also be casted into a string. 20767 */ 20768 getString : function (key) { 20769 var prefs, i, retStr, noMsg, getFailed = "", args; 20770 if (!_getMsg) { 20771 if (finesse.container && finesse.container.I18n) { 20772 _getMsg = finesse.container.I18n.getMsg; 20773 } else if (gadgets) { 20774 prefs = new gadgets.Prefs(); 20775 _getMsg = prefs.getMsg; 20776 } 20777 } 20778 20779 try { 20780 var keyValue = _getMsg(key); 20781 if (typeof keyValue === "string") { 20782 retStr = keyValue.replace(/'/g, "'"); // replacing ' character to ' to fix localization issue 20783 } else { 20784 retStr = keyValue; 20785 } 20786 } catch (e) { 20787 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 20788 } 20789 20790 if (retStr) { 20791 // Lookup was successful, perform substitution (if any) 20792 args = [ retStr ]; 20793 for (i = 1; i < arguments.length; i += 1) { 20794 args.push(arguments[i]); 20795 } 20796 return Utilities.formatString.apply(this, args); 20797 } 20798 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 20799 /** @private */ 20800 noMsg = function () { 20801 return "???" + key + "???" + getFailed; 20802 }; 20803 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 20804 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 20805 /** @private */ 20806 noMsg.toString = function () { 20807 return "???" + key + "???" + getFailed; 20808 }; 20809 return noMsg; 20810 20811 } 20812 }; 20813 }()); 20814 20815 window.finesse = window.finesse || {}; 20816 window.finesse.utilities = window.finesse.utilities || {}; 20817 window.finesse.utilities.I18n = I18n; 20818 20819 return I18n; 20820 }); 20821 20822 /** 20823 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 20824 * 20825 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 20826 */ 20827 // Add Utilities to the finesse.utilities namespace 20828 define('utilities/Logger',[], function () { 20829 var Logger = (function () { 20830 20831 var 20832 20833 /** @private **/ 20834 debugOn, 20835 20836 /** 20837 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 20838 * @param num is the number to pad to 2 digits 20839 * @returns a two digit padded string 20840 * @private 20841 */ 20842 padTwoDigits = function (num) { 20843 return (num < 10) ? '0' + num : num; 20844 }, 20845 20846 /** 20847 * Checks to see if we have a console - this allows us to support Firefox or IE. 20848 * @returns {Boolean} True for Firefox, False for IE 20849 * @private 20850 */ 20851 hasConsole = function () { 20852 var retval = false; 20853 try 20854 { 20855 if (window.console !== undefined) 20856 { 20857 retval = true; 20858 } 20859 } 20860 catch (err) 20861 { 20862 retval = false; 20863 } 20864 20865 return retval; 20866 }, 20867 20868 /** 20869 * Gets a timestamp. 20870 * @returns {String} is a timestamp in the following format: HH:MM:SS 20871 * @private 20872 */ 20873 getTimeStamp = function () { 20874 var date = new Date(), timeStr; 20875 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 20876 20877 return timeStr; 20878 }; 20879 20880 return { 20881 /** 20882 * Enable debug mode. Debug mode may impact performance on the UI. 20883 * 20884 * @param {Boolean} enable 20885 * True to enable debug logging. 20886 * @private 20887 */ 20888 setDebug : function (enable) { 20889 debugOn = enable; 20890 }, 20891 20892 /** 20893 * Logs a string as DEBUG. 20894 * 20895 * @param str is the string to log. 20896 * @private 20897 */ 20898 log : function (str) { 20899 var timeStr = getTimeStamp(); 20900 20901 if (debugOn) { 20902 if (hasConsole()) 20903 { 20904 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 20905 } 20906 } 20907 }, 20908 20909 /** 20910 * Logs a string as INFO. 20911 * 20912 * @param str is the string to log. 20913 * @private 20914 */ 20915 info : function (str) { 20916 var timeStr = getTimeStamp(); 20917 20918 if (hasConsole()) 20919 { 20920 window.console.info(timeStr + ": " + "INFO" + " - " + str); 20921 } 20922 }, 20923 20924 /** 20925 * Logs a string as WARN. 20926 * 20927 * @param str is the string to log. 20928 * @private 20929 */ 20930 warn : function (str) { 20931 var timeStr = getTimeStamp(); 20932 20933 if (hasConsole()) 20934 { 20935 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 20936 } 20937 }, 20938 /** 20939 * Logs a string as ERROR. 20940 * 20941 * @param str is the string to log. 20942 * @private 20943 */ 20944 error : function (str) { 20945 var timeStr = getTimeStamp(); 20946 20947 if (hasConsole()) 20948 { 20949 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 20950 } 20951 } 20952 }; 20953 }()); 20954 20955 return Logger; 20956 }); 20957 20958 /** 20959 * BackSpaceHandler.js: provides functionality to prevent the page from navigating back and hence losing the unsaved data when backspace is pressed. 20960 * 20961 */ 20962 define('utilities/BackSpaceHandler',[], function () { 20963 var eventCallback = function(event) { 20964 var doPrevent = false, d = event.srcElement || event.target; 20965 if (event.keyCode === 8) { 20966 if ((d.tagName.toUpperCase() === 'INPUT' && (d.type 20967 .toUpperCase() === 'TEXT' 20968 || d.type.toUpperCase() === 'PASSWORD' 20969 || d.type.toUpperCase() === 'FILE' 20970 || d.type.toUpperCase() === 'SEARCH' 20971 || d.type.toUpperCase() === 'EMAIL' 20972 || d.type.toUpperCase() === 'NUMBER' || d.type 20973 .toUpperCase() === 'DATE')) 20974 || d.tagName.toUpperCase() === 'TEXTAREA') { 20975 doPrevent = d.readOnly || d.disabled; 20976 } else { 20977 //if HTML content is editable doPrevent will be false and vice versa 20978 doPrevent = (!d.isContentEditable); 20979 } 20980 } 20981 20982 if (doPrevent) { 20983 event.preventDefault(); 20984 } 20985 }; 20986 20987 if (window.addEventListener) { 20988 window.addEventListener('keydown', eventCallback); 20989 } else if (window.attachEvent) { 20990 window.attachEvent('onkeydown', eventCallback); 20991 } else { 20992 window.console.error("Unable to attach backspace handler event "); 20993 } 20994 }); 20995 !function(a,b){"function"==typeof define&&define.amd?define('utilities/../../thirdparty/tv4/tv4.min.js',[],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.tv4=b()}(this,function(){function a(a){return encodeURI(a).replace(/%25[0-9][0-9]/g,function(a){return"%"+a.substring(3)})}function b(b){var c="";m[b.charAt(0)]&&(c=b.charAt(0),b=b.substring(1));var d="",e="",f=!0,g=!1,h=!1;"+"===c?f=!1:"."===c?(e=".",d="."):"/"===c?(e="/",d="/"):"#"===c?(e="#",f=!1):";"===c?(e=";",d=";",g=!0,h=!0):"?"===c?(e="?",d="&",g=!0):"&"===c&&(e="&",d="&",g=!0);for(var i=[],j=b.split(","),k=[],l={},o=0;o<j.length;o++){var p=j[o],q=null;if(-1!==p.indexOf(":")){var r=p.split(":");p=r[0],q=parseInt(r[1],10)}for(var s={};n[p.charAt(p.length-1)];)s[p.charAt(p.length-1)]=!0,p=p.substring(0,p.length-1);var t={truncate:q,name:p,suffices:s};k.push(t),l[p]=t,i.push(p)}var u=function(b){for(var c="",i=0,j=0;j<k.length;j++){var l=k[j],m=b(l.name);if(null===m||void 0===m||Array.isArray(m)&&0===m.length||"object"==typeof m&&0===Object.keys(m).length)i++;else if(c+=j===i?e:d||",",Array.isArray(m)){g&&(c+=l.name+"=");for(var n=0;n<m.length;n++)n>0&&(c+=l.suffices["*"]?d||",":",",l.suffices["*"]&&g&&(c+=l.name+"=")),c+=f?encodeURIComponent(m[n]).replace(/!/g,"%21"):a(m[n])}else if("object"==typeof m){g&&!l.suffices["*"]&&(c+=l.name+"=");var o=!0;for(var p in m)o||(c+=l.suffices["*"]?d||",":","),o=!1,c+=f?encodeURIComponent(p).replace(/!/g,"%21"):a(p),c+=l.suffices["*"]?"=":",",c+=f?encodeURIComponent(m[p]).replace(/!/g,"%21"):a(m[p])}else g&&(c+=l.name,h&&""===m||(c+="=")),null!=l.truncate&&(m=m.substring(0,l.truncate)),c+=f?encodeURIComponent(m).replace(/!/g,"%21"):a(m)}return c};return u.varNames=i,{prefix:e,substitution:u}}function c(a){if(!(this instanceof c))return new c(a);for(var d=a.split("{"),e=[d.shift()],f=[],g=[],h=[];d.length>0;){var i=d.shift(),j=i.split("}")[0],k=i.substring(j.length+1),l=b(j);g.push(l.substitution),f.push(l.prefix),e.push(k),h=h.concat(l.substitution.varNames)}this.fill=function(a){for(var b=e[0],c=0;c<g.length;c++){var d=g[c];b+=d(a),b+=e[c+1]}return b},this.varNames=h,this.template=a}function d(a,b){if(a===b)return!0;if(a&&b&&"object"==typeof a&&"object"==typeof b){if(Array.isArray(a)!==Array.isArray(b))return!1;if(Array.isArray(a)){if(a.length!==b.length)return!1;for(var c=0;c<a.length;c++)if(!d(a[c],b[c]))return!1}else{var e;for(e in a)if(void 0===b[e]&&void 0!==a[e])return!1;for(e in b)if(void 0===a[e]&&void 0!==b[e])return!1;for(e in a)if(!d(a[e],b[e]))return!1}return!0}return!1}function e(a){var b=String(a).replace(/^\s+|\s+$/g,"").match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);return b?{href:b[0]||"",protocol:b[1]||"",authority:b[2]||"",host:b[3]||"",hostname:b[4]||"",port:b[5]||"",pathname:b[6]||"",search:b[7]||"",hash:b[8]||""}:null}function f(a,b){function c(a){var b=[];return a.replace(/^(\.\.?(\/|$))+/,"").replace(/\/(\.(\/|$))+/g,"/").replace(/\/\.\.$/,"/../").replace(/\/?[^\/]*/g,function(a){"/.."===a?b.pop():b.push(a)}),b.join("").replace(/^\//,"/"===a.charAt(0)?"/":"")}return b=e(b||""),a=e(a||""),b&&a?(b.protocol||a.protocol)+(b.protocol||b.authority?b.authority:a.authority)+c(b.protocol||b.authority||"/"===b.pathname.charAt(0)?b.pathname:b.pathname?(a.authority&&!a.pathname?"/":"")+a.pathname.slice(0,a.pathname.lastIndexOf("/")+1)+b.pathname:a.pathname)+(b.protocol||b.authority||b.pathname?b.search:b.search||a.search)+b.hash:null}function g(a){return a.split("#")[0]}function h(a,b){if(a&&"object"==typeof a)if(void 0===b?b=a.id:"string"==typeof a.id&&(b=f(b,a.id),a.id=b),Array.isArray(a))for(var c=0;c<a.length;c++)h(a[c],b);else{"string"==typeof a.$ref&&(a.$ref=f(b,a.$ref));for(var d in a)"enum"!==d&&h(a[d],b)}}function i(a){a=a||"en";var b=v[a];return function(a){var c=b[a.code]||u[a.code];if("string"!=typeof c)return"Unknown error code "+a.code+": "+JSON.stringify(a.messageParams);var d=a.params;return c.replace(/\{([^{}]*)\}/g,function(a,b){var c=d[b];return"string"==typeof c||"number"==typeof c?c:a})}}function j(a,b,c,d,e){if(Error.call(this),void 0===a)throw new Error("No error code supplied: "+d);this.message="",this.params=b,this.code=a,this.dataPath=c||"",this.schemaPath=d||"",this.subErrors=e||null;var f=new Error(this.message);if(this.stack=f.stack||f.stacktrace,!this.stack)try{throw f}catch(f){this.stack=f.stack||f.stacktrace}}function k(a,b){if(b.substring(0,a.length)===a){var c=b.substring(a.length);if(b.length>0&&"/"===b.charAt(a.length-1)||"#"===c.charAt(0)||"?"===c.charAt(0))return!0}return!1}function l(a){var b,c,d=new o,e={setErrorReporter:function(a){return"string"==typeof a?this.language(a):(c=a,!0)},addFormat:function(){d.addFormat.apply(d,arguments)},language:function(a){return a?(v[a]||(a=a.split("-")[0]),v[a]?(b=a,a):!1):b},addLanguage:function(a,b){var c;for(c in r)b[c]&&!b[r[c]]&&(b[r[c]]=b[c]);var d=a.split("-")[0];if(v[d]){v[a]=Object.create(v[d]);for(c in b)"undefined"==typeof v[d][c]&&(v[d][c]=b[c]),v[a][c]=b[c]}else v[a]=b,v[d]=b;return this},freshApi:function(a){var b=l();return a&&b.language(a),b},validate:function(a,e,f,g){var h=i(b),j=c?function(a,b,d){return c(a,b,d)||h(a,b,d)}:h,k=new o(d,!1,j,f,g);"string"==typeof e&&(e={$ref:e}),k.addSchema("",e);var l=k.validateAll(a,e,null,null,"");return!l&&g&&(l=k.banUnknownProperties(a,e)),this.error=l,this.missing=k.missing,this.valid=null===l,this.valid},validateResult:function(){var a={};return this.validate.apply(a,arguments),a},validateMultiple:function(a,e,f,g){var h=i(b),j=c?function(a,b,d){return c(a,b,d)||h(a,b,d)}:h,k=new o(d,!0,j,f,g);"string"==typeof e&&(e={$ref:e}),k.addSchema("",e),k.validateAll(a,e,null,null,""),g&&k.banUnknownProperties(a,e);var l={};return l.errors=k.errors,l.missing=k.missing,l.valid=0===l.errors.length,l},addSchema:function(){return d.addSchema.apply(d,arguments)},getSchema:function(){return d.getSchema.apply(d,arguments)},getSchemaMap:function(){return d.getSchemaMap.apply(d,arguments)},getSchemaUris:function(){return d.getSchemaUris.apply(d,arguments)},getMissingUris:function(){return d.getMissingUris.apply(d,arguments)},dropSchemas:function(){d.dropSchemas.apply(d,arguments)},defineKeyword:function(){d.defineKeyword.apply(d,arguments)},defineError:function(a,b,c){if("string"!=typeof a||!/^[A-Z]+(_[A-Z]+)*$/.test(a))throw new Error("Code name must be a string in UPPER_CASE_WITH_UNDERSCORES");if("number"!=typeof b||b%1!==0||1e4>b)throw new Error("Code number must be an integer > 10000");if("undefined"!=typeof r[a])throw new Error("Error already defined: "+a+" as "+r[a]);if("undefined"!=typeof s[b])throw new Error("Error code already used: "+s[b]+" as "+b);r[a]=b,s[b]=a,u[a]=u[b]=c;for(var d in v){var e=v[d];e[a]&&(e[b]=e[b]||e[a])}},reset:function(){d.reset(),this.error=null,this.missing=[],this.valid=!0},missing:[],error:null,valid:!0,normSchema:h,resolveUrl:f,getDocumentUri:g,errorCodes:r};return e.language(a||"en"),e}Object.keys||(Object.keys=function(){var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&"function"!=typeof e||null===e)throw new TypeError("Object.keys called on non-object");var f=[];for(var g in e)a.call(e,g)&&f.push(g);if(b)for(var h=0;d>h;h++)a.call(e,c[h])&&f.push(c[h]);return f}}()),Object.create||(Object.create=function(){function a(){}return function(b){if(1!==arguments.length)throw new Error("Object.create implementation only accepts one parameter.");return a.prototype=b,new a}}()),Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!==d?d=0:0!==d&&d!==1/0&&d!==-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Object.isFrozen||(Object.isFrozen=function(a){for(var b="tv4_test_frozen_key";a.hasOwnProperty(b);)b+=Math.random();try{return a[b]=!0,delete a[b],!1}catch(c){return!0}});var m={"+":!0,"#":!0,".":!0,"/":!0,";":!0,"?":!0,"&":!0},n={"*":!0};c.prototype={toString:function(){return this.template},fillFromObject:function(a){return this.fill(function(b){return a[b]})}};var o=function(a,b,c,d,e){if(this.missing=[],this.missingMap={},this.formatValidators=a?Object.create(a.formatValidators):{},this.schemas=a?Object.create(a.schemas):{},this.collectMultiple=b,this.errors=[],this.handleError=b?this.collectError:this.returnError,d&&(this.checkRecursive=!0,this.scanned=[],this.scannedFrozen=[],this.scannedFrozenSchemas=[],this.scannedFrozenValidationErrors=[],this.validatedSchemasKey="tv4_validation_id",this.validationErrorsKey="tv4_validation_errors_id"),e&&(this.trackUnknownProperties=!0,this.knownPropertyPaths={},this.unknownPropertyPaths={}),this.errorReporter=c||i("en"),"string"==typeof this.errorReporter)throw new Error("debug");if(this.definedKeywords={},a)for(var f in a.definedKeywords)this.definedKeywords[f]=a.definedKeywords[f].slice(0)};o.prototype.defineKeyword=function(a,b){this.definedKeywords[a]=this.definedKeywords[a]||[],this.definedKeywords[a].push(b)},o.prototype.createError=function(a,b,c,d,e,f,g){var h=new j(a,b,c,d,e);return h.message=this.errorReporter(h,f,g),h},o.prototype.returnError=function(a){return a},o.prototype.collectError=function(a){return a&&this.errors.push(a),null},o.prototype.prefixErrors=function(a,b,c){for(var d=a;d<this.errors.length;d++)this.errors[d]=this.errors[d].prefixWith(b,c);return this},o.prototype.banUnknownProperties=function(a,b){for(var c in this.unknownPropertyPaths){var d=this.createError(r.UNKNOWN_PROPERTY,{path:c},c,"",null,a,b),e=this.handleError(d);if(e)return e}return null},o.prototype.addFormat=function(a,b){if("object"==typeof a){for(var c in a)this.addFormat(c,a[c]);return this}this.formatValidators[a]=b},o.prototype.resolveRefs=function(a,b){if(void 0!==a.$ref){if(b=b||{},b[a.$ref])return this.createError(r.CIRCULAR_REFERENCE,{urls:Object.keys(b).join(", ")},"","",null,void 0,a);b[a.$ref]=!0,a=this.getSchema(a.$ref,b)}return a},o.prototype.getSchema=function(a,b){var c;if(void 0!==this.schemas[a])return c=this.schemas[a],this.resolveRefs(c,b);var d=a,e="";if(-1!==a.indexOf("#")&&(e=a.substring(a.indexOf("#")+1),d=a.substring(0,a.indexOf("#"))),"object"==typeof this.schemas[d]){c=this.schemas[d];var f=decodeURIComponent(e);if(""===f)return this.resolveRefs(c,b);if("/"!==f.charAt(0))return void 0;for(var g=f.split("/").slice(1),h=0;h<g.length;h++){var i=g[h].replace(/~1/g,"/").replace(/~0/g,"~");if(void 0===c[i]){c=void 0;break}c=c[i]}if(void 0!==c)return this.resolveRefs(c,b)}void 0===this.missing[d]&&(this.missing.push(d),this.missing[d]=d,this.missingMap[d]=d)},o.prototype.searchSchemas=function(a,b){if(Array.isArray(a))for(var c=0;c<a.length;c++)this.searchSchemas(a[c],b);else if(a&&"object"==typeof a){"string"==typeof a.id&&k(b,a.id)&&void 0===this.schemas[a.id]&&(this.schemas[a.id]=a);for(var d in a)if("enum"!==d)if("object"==typeof a[d])this.searchSchemas(a[d],b);else if("$ref"===d){var e=g(a[d]);e&&void 0===this.schemas[e]&&void 0===this.missingMap[e]&&(this.missingMap[e]=e)}}},o.prototype.addSchema=function(a,b){if("string"!=typeof a||"undefined"==typeof b){if("object"!=typeof a||"string"!=typeof a.id)return;b=a,a=b.id}a===g(a)+"#"&&(a=g(a)),this.schemas[a]=b,delete this.missingMap[a],h(b,a),this.searchSchemas(b,a)},o.prototype.getSchemaMap=function(){var a={};for(var b in this.schemas)a[b]=this.schemas[b];return a},o.prototype.getSchemaUris=function(a){var b=[];for(var c in this.schemas)(!a||a.test(c))&&b.push(c);return b},o.prototype.getMissingUris=function(a){var b=[];for(var c in this.missingMap)(!a||a.test(c))&&b.push(c);return b},o.prototype.dropSchemas=function(){this.schemas={},this.reset()},o.prototype.reset=function(){this.missing=[],this.missingMap={},this.errors=[]},o.prototype.validateAll=function(a,b,c,d,e){var f;if(b=this.resolveRefs(b),!b)return null;if(b instanceof j)return this.errors.push(b),b;var g,h=this.errors.length,i=null,k=null;if(this.checkRecursive&&a&&"object"==typeof a){if(f=!this.scanned.length,a[this.validatedSchemasKey]){var l=a[this.validatedSchemasKey].indexOf(b);if(-1!==l)return this.errors=this.errors.concat(a[this.validationErrorsKey][l]),null}if(Object.isFrozen(a)&&(g=this.scannedFrozen.indexOf(a),-1!==g)){var m=this.scannedFrozenSchemas[g].indexOf(b);if(-1!==m)return this.errors=this.errors.concat(this.scannedFrozenValidationErrors[g][m]),null}if(this.scanned.push(a),Object.isFrozen(a))-1===g&&(g=this.scannedFrozen.length,this.scannedFrozen.push(a),this.scannedFrozenSchemas.push([])),i=this.scannedFrozenSchemas[g].length,this.scannedFrozenSchemas[g][i]=b,this.scannedFrozenValidationErrors[g][i]=[];else{if(!a[this.validatedSchemasKey])try{Object.defineProperty(a,this.validatedSchemasKey,{value:[],configurable:!0}),Object.defineProperty(a,this.validationErrorsKey,{value:[],configurable:!0})}catch(n){a[this.validatedSchemasKey]=[],a[this.validationErrorsKey]=[]}k=a[this.validatedSchemasKey].length,a[this.validatedSchemasKey][k]=b,a[this.validationErrorsKey][k]=[]}}var o=this.errors.length,p=this.validateBasic(a,b,e)||this.validateNumeric(a,b,e)||this.validateString(a,b,e)||this.validateArray(a,b,e)||this.validateObject(a,b,e)||this.validateCombinations(a,b,e)||this.validateHypermedia(a,b,e)||this.validateFormat(a,b,e)||this.validateDefinedKeywords(a,b,e)||null;if(f){for(;this.scanned.length;){var q=this.scanned.pop();delete q[this.validatedSchemasKey]}this.scannedFrozen=[],this.scannedFrozenSchemas=[]}if(p||o!==this.errors.length)for(;c&&c.length||d&&d.length;){var r=c&&c.length?""+c.pop():null,s=d&&d.length?""+d.pop():null;p&&(p=p.prefixWith(r,s)),this.prefixErrors(o,r,s)}return null!==i?this.scannedFrozenValidationErrors[g][i]=this.errors.slice(h):null!==k&&(a[this.validationErrorsKey][k]=this.errors.slice(h)),this.handleError(p)},o.prototype.validateFormat=function(a,b){if("string"!=typeof b.format||!this.formatValidators[b.format])return null;var c=this.formatValidators[b.format].call(null,a,b);return"string"==typeof c||"number"==typeof c?this.createError(r.FORMAT_CUSTOM,{message:c},"","/format",null,a,b):c&&"object"==typeof c?this.createError(r.FORMAT_CUSTOM,{message:c.message||"?"},c.dataPath||"",c.schemaPath||"/format",null,a,b):null},o.prototype.validateDefinedKeywords=function(a,b,c){for(var d in this.definedKeywords)if("undefined"!=typeof b[d])for(var e=this.definedKeywords[d],f=0;f<e.length;f++){var g=e[f],h=g(a,b[d],b,c);if("string"==typeof h||"number"==typeof h)return this.createError(r.KEYWORD_CUSTOM,{key:d,message:h},"","",null,a,b).prefixWith(null,d);if(h&&"object"==typeof h){var i=h.code;if("string"==typeof i){if(!r[i])throw new Error("Undefined error code (use defineError): "+i);i=r[i]}else"number"!=typeof i&&(i=r.KEYWORD_CUSTOM);var j="object"==typeof h.message?h.message:{key:d,message:h.message||"?"},k=h.schemaPath||"/"+d.replace(/~/g,"~0").replace(/\//g,"~1");return this.createError(i,j,h.dataPath||null,k,null,a,b)}}return null},o.prototype.validateBasic=function(a,b,c){var d;return(d=this.validateType(a,b,c))?d.prefixWith(null,"type"):(d=this.validateEnum(a,b,c))?d.prefixWith(null,"type"):null},o.prototype.validateType=function(a,b){if(void 0===b.type)return null;var c=typeof a;null===a?c="null":Array.isArray(a)&&(c="array");var d=b.type;Array.isArray(d)||(d=[d]);for(var e=0;e<d.length;e++){var f=d[e];if(f===c||"integer"===f&&"number"===c&&a%1===0)return null}return this.createError(r.INVALID_TYPE,{type:c,expected:d.join("/")},"","",null,a,b)},o.prototype.validateEnum=function(a,b){if(void 0===b["enum"])return null;for(var c=0;c<b["enum"].length;c++){var e=b["enum"][c];if(d(a,e))return null}return this.createError(r.ENUM_MISMATCH,{value:"undefined"!=typeof JSON?JSON.stringify(a):a},"","",null,a,b)},o.prototype.validateNumeric=function(a,b,c){return this.validateMultipleOf(a,b,c)||this.validateMinMax(a,b,c)||this.validateNaN(a,b,c)||null};var p=Math.pow(2,-51),q=1-p;o.prototype.validateMultipleOf=function(a,b){var c=b.multipleOf||b.divisibleBy;if(void 0===c)return null;if("number"==typeof a){var d=a/c%1;if(d>=p&&q>d)return this.createError(r.NUMBER_MULTIPLE_OF,{value:a,multipleOf:c},"","",null,a,b)}return null},o.prototype.validateMinMax=function(a,b){if("number"!=typeof a)return null;if(void 0!==b.minimum){if(a<b.minimum)return this.createError(r.NUMBER_MINIMUM,{value:a,minimum:b.minimum},"","/minimum",null,a,b);if(b.exclusiveMinimum&&a===b.minimum)return this.createError(r.NUMBER_MINIMUM_EXCLUSIVE,{value:a,minimum:b.minimum},"","/exclusiveMinimum",null,a,b)}if(void 0!==b.maximum){if(a>b.maximum)return this.createError(r.NUMBER_MAXIMUM,{value:a,maximum:b.maximum},"","/maximum",null,a,b);if(b.exclusiveMaximum&&a===b.maximum)return this.createError(r.NUMBER_MAXIMUM_EXCLUSIVE,{value:a,maximum:b.maximum},"","/exclusiveMaximum",null,a,b)}return null},o.prototype.validateNaN=function(a,b){return"number"!=typeof a?null:isNaN(a)===!0||a===1/0||a===-(1/0)?this.createError(r.NUMBER_NOT_A_NUMBER,{value:a},"","/type",null,a,b):null},o.prototype.validateString=function(a,b,c){return this.validateStringLength(a,b,c)||this.validateStringPattern(a,b,c)||null},o.prototype.validateStringLength=function(a,b){return"string"!=typeof a?null:void 0!==b.minLength&&a.length<b.minLength?this.createError(r.STRING_LENGTH_SHORT,{length:a.length,minimum:b.minLength},"","/minLength",null,a,b):void 0!==b.maxLength&&a.length>b.maxLength?this.createError(r.STRING_LENGTH_LONG,{length:a.length,maximum:b.maxLength},"","/maxLength",null,a,b):null},o.prototype.validateStringPattern=function(a,b){if("string"!=typeof a||"string"!=typeof b.pattern&&!(b.pattern instanceof RegExp))return null;var c;if(b.pattern instanceof RegExp)c=b.pattern;else{var d,e="",f=b.pattern.match(/^\/(.+)\/([img]*)$/);f?(d=f[1],e=f[2]):d=b.pattern,c=new RegExp(d,e)}return c.test(a)?null:this.createError(r.STRING_PATTERN,{pattern:b.pattern},"","/pattern",null,a,b)},o.prototype.validateArray=function(a,b,c){return Array.isArray(a)?this.validateArrayLength(a,b,c)||this.validateArrayUniqueItems(a,b,c)||this.validateArrayItems(a,b,c)||null:null},o.prototype.validateArrayLength=function(a,b){var c;return void 0!==b.minItems&&a.length<b.minItems&&(c=this.createError(r.ARRAY_LENGTH_SHORT,{length:a.length,minimum:b.minItems},"","/minItems",null,a,b),this.handleError(c))?c:void 0!==b.maxItems&&a.length>b.maxItems&&(c=this.createError(r.ARRAY_LENGTH_LONG,{length:a.length,maximum:b.maxItems},"","/maxItems",null,a,b),this.handleError(c))?c:null},o.prototype.validateArrayUniqueItems=function(a,b){if(b.uniqueItems)for(var c=0;c<a.length;c++)for(var e=c+1;e<a.length;e++)if(d(a[c],a[e])){var f=this.createError(r.ARRAY_UNIQUE,{match1:c,match2:e},"","/uniqueItems",null,a,b);if(this.handleError(f))return f}return null},o.prototype.validateArrayItems=function(a,b,c){if(void 0===b.items)return null;var d,e;if(Array.isArray(b.items)){for(e=0;e<a.length;e++)if(e<b.items.length){if(d=this.validateAll(a[e],b.items[e],[e],["items",e],c+"/"+e))return d}else if(void 0!==b.additionalItems)if("boolean"==typeof b.additionalItems){if(!b.additionalItems&&(d=this.createError(r.ARRAY_ADDITIONAL_ITEMS,{},"/"+e,"/additionalItems",null,a,b),this.handleError(d)))return d}else if(d=this.validateAll(a[e],b.additionalItems,[e],["additionalItems"],c+"/"+e))return d}else for(e=0;e<a.length;e++)if(d=this.validateAll(a[e],b.items,[e],["items"],c+"/"+e))return d;return null},o.prototype.validateObject=function(a,b,c){return"object"!=typeof a||null===a||Array.isArray(a)?null:this.validateObjectMinMaxProperties(a,b,c)||this.validateObjectRequiredProperties(a,b,c)||this.validateObjectProperties(a,b,c)||this.validateObjectDependencies(a,b,c)||null},o.prototype.validateObjectMinMaxProperties=function(a,b){var c,d=Object.keys(a);return void 0!==b.minProperties&&d.length<b.minProperties&&(c=this.createError(r.OBJECT_PROPERTIES_MINIMUM,{propertyCount:d.length,minimum:b.minProperties},"","/minProperties",null,a,b),this.handleError(c))?c:void 0!==b.maxProperties&&d.length>b.maxProperties&&(c=this.createError(r.OBJECT_PROPERTIES_MAXIMUM,{propertyCount:d.length,maximum:b.maxProperties},"","/maxProperties",null,a,b),this.handleError(c))?c:null},o.prototype.validateObjectRequiredProperties=function(a,b){if(void 0!==b.required)for(var c=0;c<b.required.length;c++){var d=b.required[c];if(void 0===a[d]){var e=this.createError(r.OBJECT_REQUIRED,{key:d},"","/required/"+c,null,a,b);if(this.handleError(e))return e}}return null},o.prototype.validateObjectProperties=function(a,b,c){var d;for(var e in a){var f=c+"/"+e.replace(/~/g,"~0").replace(/\//g,"~1"),g=!1;if(void 0!==b.properties&&void 0!==b.properties[e]&&(g=!0,d=this.validateAll(a[e],b.properties[e],[e],["properties",e],f)))return d;if(void 0!==b.patternProperties)for(var h in b.patternProperties){var i=new RegExp(h);if(i.test(e)&&(g=!0,d=this.validateAll(a[e],b.patternProperties[h],[e],["patternProperties",h],f)))return d}if(g)this.trackUnknownProperties&&(this.knownPropertyPaths[f]=!0,delete this.unknownPropertyPaths[f]);else if(void 0!==b.additionalProperties){if(this.trackUnknownProperties&&(this.knownPropertyPaths[f]=!0,delete this.unknownPropertyPaths[f]),"boolean"==typeof b.additionalProperties){if(!b.additionalProperties&&(d=this.createError(r.OBJECT_ADDITIONAL_PROPERTIES,{key:e},"","/additionalProperties",null,a,b).prefixWith(e,null),this.handleError(d)))return d}else if(d=this.validateAll(a[e],b.additionalProperties,[e],["additionalProperties"],f))return d}else this.trackUnknownProperties&&!this.knownPropertyPaths[f]&&(this.unknownPropertyPaths[f]=!0)}return null},o.prototype.validateObjectDependencies=function(a,b,c){var d;if(void 0!==b.dependencies)for(var e in b.dependencies)if(void 0!==a[e]){var f=b.dependencies[e];if("string"==typeof f){if(void 0===a[f]&&(d=this.createError(r.OBJECT_DEPENDENCY_KEY,{key:e,missing:f},"","",null,a,b).prefixWith(null,e).prefixWith(null,"dependencies"),this.handleError(d)))return d}else if(Array.isArray(f))for(var g=0;g<f.length;g++){var h=f[g];if(void 0===a[h]&&(d=this.createError(r.OBJECT_DEPENDENCY_KEY,{key:e,missing:h},"","/"+g,null,a,b).prefixWith(null,e).prefixWith(null,"dependencies"),this.handleError(d)))return d}else if(d=this.validateAll(a,f,[],["dependencies",e],c))return d}return null},o.prototype.validateCombinations=function(a,b,c){return this.validateAllOf(a,b,c)||this.validateAnyOf(a,b,c)||this.validateOneOf(a,b,c)||this.validateNot(a,b,c)||null},o.prototype.validateAllOf=function(a,b,c){if(void 0===b.allOf)return null;for(var d,e=0;e<b.allOf.length;e++){var f=b.allOf[e];if(d=this.validateAll(a,f,[],["allOf",e],c))return d}return null},o.prototype.validateAnyOf=function(a,b,c){if(void 0===b.anyOf)return null;var d,e,f=[],g=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths);for(var h=!0,i=0;i<b.anyOf.length;i++){this.trackUnknownProperties&&(this.unknownPropertyPaths={},this.knownPropertyPaths={});var j=b.anyOf[i],k=this.errors.length,l=this.validateAll(a,j,[],["anyOf",i],c);if(null===l&&k===this.errors.length){if(this.errors=this.errors.slice(0,g),this.trackUnknownProperties){for(var m in this.knownPropertyPaths)e[m]=!0,delete d[m];for(var n in this.unknownPropertyPaths)e[n]||(d[n]=!0);h=!1;continue}return null}l&&f.push(l.prefixWith(null,""+i).prefixWith(null,"anyOf"))}return this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),h?(f=f.concat(this.errors.slice(g)),this.errors=this.errors.slice(0,g),this.createError(r.ANY_OF_MISSING,{},"","/anyOf",f,a,b)):void 0},o.prototype.validateOneOf=function(a,b,c){if(void 0===b.oneOf)return null;var d,e,f=null,g=[],h=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths);for(var i=0;i<b.oneOf.length;i++){this.trackUnknownProperties&&(this.unknownPropertyPaths={},this.knownPropertyPaths={});var j=b.oneOf[i],k=this.errors.length,l=this.validateAll(a,j,[],["oneOf",i],c);if(null===l&&k===this.errors.length){if(null!==f)return this.errors=this.errors.slice(0,h),this.createError(r.ONE_OF_MULTIPLE,{index1:f,index2:i},"","/oneOf",null,a,b);if(f=i,this.trackUnknownProperties){for(var m in this.knownPropertyPaths)e[m]=!0,delete d[m];for(var n in this.unknownPropertyPaths)e[n]||(d[n]=!0)}}else l&&g.push(l)}return this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),null===f?(g=g.concat(this.errors.slice(h)),this.errors=this.errors.slice(0,h),this.createError(r.ONE_OF_MISSING,{},"","/oneOf",g,a,b)):(this.errors=this.errors.slice(0,h),null)},o.prototype.validateNot=function(a,b,c){if(void 0===b.not)return null;var d,e,f=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths,this.unknownPropertyPaths={},this.knownPropertyPaths={});var g=this.validateAll(a,b.not,null,null,c),h=this.errors.slice(f);return this.errors=this.errors.slice(0,f),this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),null===g&&0===h.length?this.createError(r.NOT_PASSED,{},"","/not",null,a,b):null},o.prototype.validateHypermedia=function(a,b,d){if(!b.links)return null;for(var e,f=0;f<b.links.length;f++){var g=b.links[f];if("describedby"===g.rel){for(var h=new c(g.href),i=!0,j=0;j<h.varNames.length;j++)if(!(h.varNames[j]in a)){i=!1;break}if(i){var k=h.fillFromObject(a),l={$ref:k};if(e=this.validateAll(a,l,[],["links",f],d))return e}}}};var r={INVALID_TYPE:0,ENUM_MISMATCH:1,ANY_OF_MISSING:10,ONE_OF_MISSING:11,ONE_OF_MULTIPLE:12,NOT_PASSED:13,NUMBER_MULTIPLE_OF:100,NUMBER_MINIMUM:101,NUMBER_MINIMUM_EXCLUSIVE:102,NUMBER_MAXIMUM:103,NUMBER_MAXIMUM_EXCLUSIVE:104,NUMBER_NOT_A_NUMBER:105,STRING_LENGTH_SHORT:200,STRING_LENGTH_LONG:201,STRING_PATTERN:202,OBJECT_PROPERTIES_MINIMUM:300,OBJECT_PROPERTIES_MAXIMUM:301,OBJECT_REQUIRED:302,OBJECT_ADDITIONAL_PROPERTIES:303,OBJECT_DEPENDENCY_KEY:304,ARRAY_LENGTH_SHORT:400,ARRAY_LENGTH_LONG:401,ARRAY_UNIQUE:402,ARRAY_ADDITIONAL_ITEMS:403,FORMAT_CUSTOM:500,KEYWORD_CUSTOM:501,CIRCULAR_REFERENCE:600,UNKNOWN_PROPERTY:1e3},s={};for(var t in r)s[r[t]]=t;var u={INVALID_TYPE:"Invalid type: {type} (expected {expected})",ENUM_MISMATCH:"No enum match for: {value}",ANY_OF_MISSING:'Data does not match any schemas from "anyOf"',ONE_OF_MISSING:'Data does not match any schemas from "oneOf"',ONE_OF_MULTIPLE:'Data is valid against more than one schema from "oneOf": indices {index1} and {index2}',NOT_PASSED:'Data matches schema from "not"',NUMBER_MULTIPLE_OF:"Value {value} is not a multiple of {multipleOf}",NUMBER_MINIMUM:"Value {value} is less than minimum {minimum}",NUMBER_MINIMUM_EXCLUSIVE:"Value {value} is equal to exclusive minimum {minimum}",NUMBER_MAXIMUM:"Value {value} is greater than maximum {maximum}",NUMBER_MAXIMUM_EXCLUSIVE:"Value {value} is equal to exclusive maximum {maximum}",NUMBER_NOT_A_NUMBER:"Value {value} is not a valid number",STRING_LENGTH_SHORT:"String is too short ({length} chars), minimum {minimum}",STRING_LENGTH_LONG:"String is too long ({length} chars), maximum {maximum}",STRING_PATTERN:"String does not match pattern: {pattern}",OBJECT_PROPERTIES_MINIMUM:"Too few properties defined ({propertyCount}), minimum {minimum}",OBJECT_PROPERTIES_MAXIMUM:"Too many properties defined ({propertyCount}), maximum {maximum}",OBJECT_REQUIRED:"Missing required property: {key}",OBJECT_ADDITIONAL_PROPERTIES:"Additional properties not allowed",OBJECT_DEPENDENCY_KEY:"Dependency failed - key must exist: {missing} (due to key: {key})",ARRAY_LENGTH_SHORT:"Array is too short ({length}), minimum {minimum}",ARRAY_LENGTH_LONG:"Array is too long ({length}), maximum {maximum}",ARRAY_UNIQUE:"Array items are not unique (indices {match1} and {match2})",ARRAY_ADDITIONAL_ITEMS:"Additional items not allowed",FORMAT_CUSTOM:"Format validation failed ({message})",KEYWORD_CUSTOM:"Keyword failed: {key} ({message})",CIRCULAR_REFERENCE:"Circular $refs: {urls}",UNKNOWN_PROPERTY:"Unknown property (not in schema)"};j.prototype=Object.create(Error.prototype),j.prototype.constructor=j,j.prototype.name="ValidationError",j.prototype.prefixWith=function(a,b){if(null!==a&&(a=a.replace(/~/g,"~0").replace(/\//g,"~1"),this.dataPath="/"+a+this.dataPath),null!==b&&(b=b.replace(/~/g,"~0").replace(/\//g,"~1"),this.schemaPath="/"+b+this.schemaPath),null!==this.subErrors)for(var c=0;c<this.subErrors.length;c++)this.subErrors[c].prefixWith(a,b);return this};var v={},w=l();return w.addLanguage("en-gb",u),w.tv4=w,w}); 20996 define('utilities/JsonValidator',[ 20997 "../../thirdparty/tv4/tv4.min.js" 20998 ], 20999 function (tv4) { 21000 21001 var jsonValdiator = /** @lends finesse.utilities.JsonValidator */ { 21002 21003 /** 21004 * @class 21005 * For JSON validation 21006 * 21007 * @constructs 21008 */ 21009 _fakeConstuctor: function () { 21010 /* This is here for jsdocs. */ 21011 }, 21012 21013 /** 21014 * Validates JSON data by applying a specific schema 21015 * 21016 * @param jsonData - JSON data 21017 * @param schema - JSON schema that would validate the parameter jsonData. 21018 * It needs to follow the <a href="http://json-schema.org">JSON schema definition standard</a> 21019 * @returns - JSON Result that is of the below format 21020 * <pre> 21021 * { 21022 * "valid": [true/false], 21023 * "error": [tv4 error object if schema is not valid] 21024 * } 21025 * </pre> 21026 * The error object will look something like this: 21027 * <pre> 21028 * { 21029 * "code": 0, 21030 * "message": "Invalid type: string", 21031 * "dataPath": "/intKey", 21032 * "schemaPath": "/properties/intKey/type" 21033 * } 21034 * </pre> 21035 */ 21036 validateJson: function (jsonData, schema) { 21037 //window.console.info("To validate schema"); 21038 var valid = tv4.validate(jsonData, schema); 21039 var result = {}; 21040 result.valid = valid; 21041 if (!valid) { 21042 result.error = tv4.error; 21043 } 21044 return result; 21045 } 21046 } 21047 21048 21049 21050 window.finesse = window.finesse || {}; 21051 window.finesse.utilities = window.finesse.utilities || {}; 21052 window.finesse.utilities.JsonValidator = jsonValdiator; 21053 21054 return jsonValdiator; 21055 }); 21056 /* using variables before they are defined. 21057 */ 21058 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 21059 21060 /** 21061 * Allows each gadget to communicate with the server to send logs. 21062 */ 21063 21064 /** 21065 * @class 21066 * @private 21067 * Allows each product to initialize its method of storage 21068 */ 21069 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 21070 21071 var FinesseLogger = (function () { 21072 21073 var 21074 21075 /** 21076 * Array use to collect ongoing logs in memory 21077 * @private 21078 */ 21079 _logArray = [], 21080 21081 _triedLogPostOnceOnOverload = false; 21082 21083 /** 21084 * The final data string sent to the server, =_logArray.join 21085 * @private 21086 */ 21087 _logStr = "", 21088 21089 /** 21090 * Keep track of size of log 21091 * @private 21092 */ 21093 _logSize = 0, 21094 21095 /** 21096 * Flag to keep track show/hide of send log link 21097 * @private 21098 */ 21099 _sendLogShown = false, 21100 21101 /** 21102 * Flag to keep track if local log initialized 21103 * @private 21104 */ 21105 _loggingInitialized = false, 21106 21107 21108 /** 21109 * local log size limit 21110 * @private 21111 */ 21112 _maxLocalStorageSize = 5000000, 21113 21114 /** 21115 * half local log size limit 21116 * @private 21117 */ 21118 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 21119 21120 21121 /** 21122 * threshold for purge 21123 * @private 21124 */ 21125 _purgeStartPercent = 0.75, 21126 21127 /** 21128 * log item prefix 21129 * @private 21130 */ 21131 _linePrefix = null, 21132 21133 /** 21134 * locallog session 21135 * @private 21136 */ 21137 _session = null, 21138 21139 /** 21140 * Flag to keep track show/hide of send log link 21141 * @private 21142 */ 21143 _sessionKey = null, 21144 /** 21145 * Log session metadata 21146 * @private 21147 */ 21148 _logInfo = {}, 21149 21150 /** 21151 * Flag to find sessions 21152 * @private 21153 */ 21154 _findSessionsObj = null, 21155 21156 _userObject = null, 21157 21158 /** 21159 * Wrap up console.log esp. for IE9 21160 * @private 21161 */ 21162 _myConsoleLog = function (str) { 21163 if (window.console !== undefined) { 21164 window.console.log(str); 21165 } 21166 }, 21167 /** 21168 * Initialize the Local Logging 21169 * @private 21170 */ 21171 _initLogging = function () { 21172 if (_loggingInitialized) { 21173 return; 21174 } 21175 //Build a new store 21176 _session = sessionStorage.getItem("finSessKey"); 21177 //if the _session is null or empty, skip the init 21178 if (!_session) { 21179 return; 21180 } 21181 _sessionKey = "Fi"+_session; 21182 _linePrefix = _sessionKey + "_"; 21183 _logInfo = {}; 21184 _logInfo.name = _session; 21185 _logInfo.size = 0; 21186 _logInfo.head = 0; 21187 _logInfo.tail = 0; 21188 _logInfo.startTime = new Date().getTime(); 21189 _loggingInitialized = true; 21190 _initSessionList(); 21191 }, 21192 21193 /** 21194 * get total data size 21195 * 21196 * @return {Integer} which is the amount of data stored in local storage. 21197 * @private 21198 */ 21199 _getTotalData = function () 21200 { 21201 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 21202 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 21203 if (!sessionsInfoStr) { 21204 return 0; 21205 } 21206 sessionsInfoObj = JSON.parse(sessionsInfoStr); 21207 21208 for (sessName in sessionsInfoObj.sessions) 21209 { 21210 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 21211 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 21212 if (!sessLogInfoStr) { 21213 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 21214 } 21215 else { 21216 sessLogInfoObj = JSON.parse(sessLogInfoStr); 21217 totalData = totalData + sessLogInfoObj.size; 21218 } 21219 } 21220 } 21221 21222 return totalData; 21223 }, 21224 21225 /** 21226 * Remove lines from tail up until store size decreases to half of max size limit. 21227 * 21228 * @private 21229 */ 21230 _purgeCurrentSession = function() { 21231 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 21232 curStoreSize = _getTotalData(); 21233 if (curStoreSize < _halfMaxLocalStorageSize) { 21234 return; 21235 } 21236 logInfoStr = localStorage.getItem(_sessionKey); 21237 if (!logInfoStr) { 21238 return; 21239 } 21240 theLogInfo = JSON.parse(logInfoStr); 21241 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 21242 while(curStoreSize > _halfMaxLocalStorageSize) { 21243 try { 21244 tailKey = _sessionKey+"_"+theLogInfo.tail; 21245 line = localStorage.getItem(tailKey); 21246 if (line) { 21247 purgedSize = purgedSize +line.length; 21248 localStorage.removeItem(tailKey); 21249 curStoreSize = curStoreSize - line.length; 21250 theLogInfo.size = theLogInfo.size - line.length; 21251 } 21252 } 21253 catch (err) { 21254 _myConsoleLog("purgeCurrentSession encountered err="+err); 21255 } 21256 if (theLogInfo.tail < theLogInfo.head) { 21257 theLogInfo.tail = theLogInfo.tail + 1; 21258 } 21259 else { 21260 break; 21261 } 21262 } 21263 //purge stops here, we need to update session's meta data in storage 21264 secLogInfoStr = localStorage.getItem(_sessionKey); 21265 if (!secLogInfoStr) { 21266 //somebody cleared the localStorage 21267 return; 21268 } 21269 21270 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 21271 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 21272 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 21273 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 21274 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 21275 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 21276 }, 21277 21278 /** 21279 * Purge a session 21280 * 21281 * @param sessionName is the name of the session 21282 * @return {Integer} which is the current amount of data purged 21283 * @private 21284 */ 21285 _purgeSession = function (sessionName) { 21286 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 21287 //Get the session logInfo 21288 logInfoStr = localStorage.getItem("Fi" + sessionName); 21289 if (!logInfoStr) { 21290 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 21291 return 0; 21292 } 21293 theLogInfo = JSON.parse(logInfoStr); 21294 21295 //Note: This assumes that we don't crash in the middle of purging 21296 //=> if we do then it should get deleted next time 21297 //Purge tail->head 21298 while (theLogInfo.tail <= theLogInfo.head) 21299 { 21300 try { 21301 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 21302 theLogInfo.tail = theLogInfo.tail + 1; 21303 } 21304 catch (err) { 21305 _myConsoleLog("In _purgeSession err="+err); 21306 break; 21307 } 21308 } 21309 21310 //Remove the entire session 21311 localStorage.removeItem("Fi" + sessionName); 21312 21313 //Update FinesseSessionsInfo 21314 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 21315 if (!sessionsInfoStr) { 21316 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 21317 return 0; 21318 } 21319 sessionsInfoObj = JSON.parse(sessionsInfoStr); 21320 if (sessionsInfoObj.sessions !== null) 21321 { 21322 delete sessionsInfoObj.sessions[sessionName]; 21323 21324 sessionsInfoObj.total = sessionsInfoObj.total - 1; 21325 sessionsInfoObj.lastWrittenBy = _session; 21326 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 21327 } 21328 21329 return theLogInfo.size; 21330 }, 21331 21332 /** 21333 * purge old sessions 21334 * 21335 * @param storeSize 21336 * @return {Boolean} whether purging reaches its target 21337 * @private 21338 */ 21339 _purgeOldSessions = function (storeSize) { 21340 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 21341 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 21342 if (!sessionsInfoStr) { 21343 _myConsoleLog("Could not get FinesseSessionsInfo"); 21344 return true; 21345 } 21346 sessionsInfoObj = JSON.parse(sessionsInfoStr); 21347 curStoreSize = _getTotalData(); 21348 21349 activeSession = _session; 21350 sessions = sessionsInfoObj.sessions; 21351 for (sessName in sessions) { 21352 if (sessions.hasOwnProperty(sessName)) { 21353 if (sessName !== activeSession) { 21354 purgedSize = purgedSize + _purgeSession(sessName); 21355 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 21356 return true; 21357 } 21358 } 21359 } 21360 } 21361 //purge is not done, so return false 21362 return false; 21363 }, 21364 21365 /** 21366 * handle insert error 21367 * 21368 * @param error 21369 * @private 21370 */ 21371 _insertLineHandleError = function (error) { 21372 _myConsoleLog(error); 21373 }, 21374 21375 /** 21376 * check storage data size and if need purge 21377 * @private 21378 */ 21379 _checkSizeAndPurge = function () { 21380 var purgeIsDone=false, totalSize = _getTotalData(); 21381 if (totalSize > 0.75*_maxLocalStorageSize) { 21382 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 21383 purgeIsDone = _purgeOldSessions(totalSize); 21384 if (purgeIsDone) { 21385 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 21386 } 21387 else { 21388 //after all old sessions purged, still need purge 21389 totalSize = _getTotalData(); 21390 if (totalSize > 0.75*_maxLocalStorageSize) { 21391 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 21392 _purgeCurrentSession(); 21393 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 21394 } 21395 } 21396 } 21397 }, 21398 21399 /** 21400 * check if the session is already in meta data 21401 * 21402 * @param metaData 21403 * @param sessionName 21404 * @return {Boolean} true if session has metaData (false otherwise) 21405 * @private 21406 */ 21407 _sessionsInfoContains = function (metaData, sessionName) { 21408 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 21409 return true; 21410 } 21411 return false; 21412 }, 21413 21414 21415 /** 21416 * setup sessions in local storage 21417 * 21418 * @param logInfo 21419 * @private 21420 */ 21421 _getAndSetNumberOfSessions = function (logInfo) { 21422 var numOfSessionsPass1, numOfSessionsPass2, l; 21423 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 21424 if (numOfSessionsPass1 === null) { 21425 //Init first time 21426 numOfSessionsPass1 = {}; 21427 numOfSessionsPass1.total = 1; 21428 numOfSessionsPass1.sessions = {}; 21429 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 21430 numOfSessionsPass1.lastWrittenBy = logInfo.name; 21431 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 21432 } 21433 else { 21434 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 21435 //check if the session is already in the FinesseSessionSInfo 21436 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 21437 return; 21438 } 21439 //Save numOfSessionsPass1 21440 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 21441 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 21442 numOfSessionsPass1.lastWrittenBy = logInfo.name; 21443 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 21444 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 21445 if (!numOfSessionsPass2) { 21446 _myConsoleLog("Could not get FinesseSessionsInfo"); 21447 return; 21448 } 21449 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 21450 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 21451 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 21452 //// _myConsoleLog("Rebuild sessions"); 21453 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 21454 ////} 21455 ////else { 21456 //// _sessionTimerId = null; 21457 ////callback(numOfSessionsPass2.sessions); 21458 ////} 21459 } 21460 if (!localStorage.getItem(_sessionKey)) { 21461 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 21462 } 21463 }, 21464 21465 21466 /** 21467 * init session list 21468 * @private 21469 */ 21470 _initSessionList = function () { 21471 _getAndSetNumberOfSessions(_logInfo); 21472 }, 21473 21474 /** 21475 * do the real store of log line 21476 * 21477 * @param line 21478 * @private 21479 */ 21480 _persistLine = function (line) { 21481 var key, logInfoStr; 21482 logInfoStr = localStorage.getItem(_sessionKey); 21483 if (logInfoStr === null) { 21484 return; 21485 } 21486 _logInfo = JSON.parse(logInfoStr); 21487 _logInfo.head = _logInfo.head + 1; 21488 key = _linePrefix + _logInfo.head; 21489 localStorage.setItem(key, line); 21490 //Save the size 21491 _logInfo.size = _logInfo.size + line.length; 21492 if (_logInfo.tail === 0) { 21493 _logInfo.tail = _logInfo.head; 21494 } 21495 21496 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 21497 _checkSizeAndPurge(); 21498 }, 21499 21500 /** 21501 * Insert a line into the localStorage. 21502 * 21503 * @param line line to be inserted 21504 * @private 21505 */ 21506 _insertLine = function (line) { 21507 //_myConsoleLog("_insertLine: [" + line + "]"); 21508 //Write the next line to localStorage 21509 try { 21510 //Persist the line 21511 _persistLine(line); 21512 } 21513 catch (err) { 21514 _myConsoleLog("error in _insertLine(), err="+err); 21515 //_insertLineHandleError(err); 21516 } 21517 }, 21518 21519 21520 /** 21521 * Clear the local storage 21522 * @private 21523 */ 21524 _clearLocalStorage = function() { 21525 localStorage.clear(); 21526 21527 }, 21528 21529 getIntValue = function (strVal, defaultVal) { 21530 try { 21531 return parseInt(strVal); 21532 } catch (e) { 21533 return defaultVal; 21534 } 21535 }, 21536 21537 /** 21538 * To check if the log collection data is present in session 21539 * Storing the log schedule data as 3 keys in session 21540 * 1.isDesktopAutoLogScheduled: flag which indicates if log collection is scheduled 21541 * 2.logSchedule: schedule data stored as JSON object 21542 * 3.scheduledLogArrayData: contains the log data of primary server during failover, 21543 * which needs to be posted to secondary server as soon as failover is completed 21544 */ 21545 checkIfLogCollectionSchedulePresentInSession = function () { 21546 logCollDataObj = JSON.parse(sessionStorage.getItem(window.finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA) || null); 21547 var ifLogCollScheduled = (logCollDataObj && logCollDataObj.isDesktopAutoLogScheduled === true && (logCollDataObj.logSchedule != null)); 21548 return ifLogCollScheduled; 21549 }, 21550 21551 /** 21552 * Collect logs when onCollect called 21553 * 21554 * @param data 21555 * @private 21556 */ 21557 _collectMethod = function(data) { 21558 //Size of log should not exceed 1.5MB 21559 21560 var info,maxLength = getIntValue(window.finesse.container.Config.clientLogSize, 1572864); 21561 21562 //add size buffer equal to the size of info to be added when publish 21563 info = Utilities.getSanitizedUserAgentString() + " "; 21564 info = escape(info); 21565 21566 //If log was empty previously, fade in buttons 21567 if (!_sendLogShown) { 21568 _sendLogShown = true; 21569 _logSize = info.length; 21570 } 21571 21572 //if local storage logging is enabled, then insert the log into local storage 21573 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 21574 if (data) { 21575 if (data.length>0 && data.substring(0,1) === '\n') { 21576 _insertLine(data.substring(1)); 21577 } 21578 else { 21579 _insertLine(data); 21580 } 21581 } 21582 } 21583 21584 //escape all data to get accurate size (shindig will escape when it builds request) 21585 //escape 6 special chars for XML: &<>"'\n 21586 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 21587 //Send Error Report crashes with Control characters. Replacing the control characters with empty string 21588 data = data.replace(/[\x00-\x1F\x7F-\x9F]/g, ""); 21589 data = escape(data+"\n"); 21590 21591 21592 if (data.length < maxLength){ 21593 //make room for new data if log is exceeding max length 21594 while (_logSize + data.length > maxLength) { 21595 // If there is a log schedule, post the logs once to server 21596 if (checkIfLogCollectionSchedulePresentInSession() && !_triedLogPostOnceOnOverload && _userObject) { 21597 ClientServices.log('FinesseLogger._collectMethod(): Posting the logs once to server as the memory logs exceeded maximum allowed length'); 21598 var callbacks = {success: FinesseLogger.logPostedOnOverloadSuccess, error: FinesseLogger.logPostedOnOverloadFailure}; 21599 ClientServices.log('FinesseLogger._collectMethod(): Setting the flag _triedLogPostOnceOnOverload to true as there will be no retry posting in case of error'); 21600 _triedLogPostOnceOnOverload = true; 21601 FinesseLogger.publishCompressedClientLog(_userObject, {}, callbacks); 21602 } 21603 _logSize -= (_logArray.shift()).length; 21604 } 21605 } 21606 21607 //Else push the log into memory, increment the log size 21608 _logArray.push(data); 21609 21610 //inc the size accordingly 21611 _logSize+=data.length; 21612 21613 }; 21614 21615 return { 21616 21617 /** 21618 * @private 21619 * Initiate FinesseLogger. 21620 */ 21621 init: function () { 21622 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 21623 _initLogging(); 21624 }, 21625 21626 /** 21627 * @param {*} user 21628 * @param {*} options 21629 * Initiate subscription for the user 21630 */ 21631 initSubscription: function(user, options) { 21632 options = options || {}; 21633 _userObject = user || null; 21634 var tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 21635 /** @private */ 21636 options.onAdd = function(clientLogObj){ 21637 tmpOnAdd(clientLogObj); 21638 // Do not clear logs in case of schedule event 21639 if (clientLogObj && clientLogObj._data && clientLogObj._data.schedule){ 21640 return; 21641 } 21642 _logArray.length = 0; _logSize =0; 21643 _sendLogShown = false; 21644 }; 21645 user.getClientLog(options); 21646 }, 21647 21648 /** 21649 * @private 21650 * Clear all items stored in localStorage. 21651 */ 21652 clear : function () { 21653 _clearLocalStorage(); 21654 }, 21655 21656 /** 21657 * @private 21658 * Initialize the local storage logging. 21659 */ 21660 initLocalLog: function () { 21661 _initLogging(); 21662 }, 21663 21664 /** 21665 * @private 21666 * Inserts a line into the localStorage. 21667 * @param line to insert 21668 */ 21669 localLog : function (line) { 21670 _insertLine(line); 21671 }, 21672 21673 /** 21674 * @private 21675 * Method to store the log collection data in session storage before failover so that it can be passed 21676 * to secondary server during failover and the log collection continues to happen. 21677 * @param line to insert 21678 */ 21679 addLogDataToSessionStorage: function () { 21680 var cachedLogData = JSON.parse(sessionStorage.getItem(finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA) || null); 21681 21682 if (checkIfLogCollectionSchedulePresentInSession()) { 21683 cachedLogData["scheduledLogArrayData"] = _logArray || []; 21684 sessionStorage.setItem(finesse.utilities.Utilities.CONSTANTS.LOG_COLL_DATA, JSON.stringify(cachedLogData)); 21685 } 21686 }, 21687 21688 /** 21689 * @private 21690 * Method to set the flag _triedLogPostOnceOnOverload to false so that the 21691 * logs can be posted again if the log in memory overflows 21692 * for a new schedule 21693 * 21694 */ 21695 setLogPostedFalse: function () { 21696 _triedLogPostOnceOnOverload = false; 21697 }, 21698 21699 /** 21700 * @private 21701 * Callback Method when log posting after memory log overload is success 21702 * 21703 */ 21704 logPostedOnOverloadSuccess: function () { 21705 ClientServices.log('FinesseLogger.logPostedOnOverloadSuccess(): posted logs successfully'); 21706 }, 21707 21708 /** 21709 * @private 21710 * Callback Method when log posting after memory log overload is Failure 21711 * @param error 21712 */ 21713 logPostedOnOverloadFailure: function (err) { 21714 ClientServices.log('FinesseLogger.logPostedOnOverloadFailure(): logs posting failed:Status:'+err.status+",Content:"+err.content); 21715 }, 21716 21717 /** 21718 * @ignore 21719 * Publish Local storage compressed client logs to server and clear the memory 21720 * 21721 * @param userObj 21722 * @param options 21723 * @param callBack 21724 */ 21725 publishLocalStorageCompressedFile: function(userObj, options, callBack) { 21726 // Avoid null references. 21727 options = options || {}; 21728 callBack = callBack || {}; 21729 21730 var logZip = new JSZip(); 21731 var filename = 'Desktop-Client-LocalStorageLog' + '.' + userObj.getId() + (options.logScheduleId? '.schedule' + options.logScheduleId: '') + '.' + new Date().toISOString() + '.txt'; 21732 logZip.file(filename, window.finesse.modules.LocalStorageViewer.getContents(null, null)); 21733 logZip.generateAsync({ 21734 type: 'blob', 21735 compression: 'DEFLATE', 21736 compressionOptions: { 21737 level: 9 // level 1 is for (best speed) and 9 is for (best compression)). 21738 } 21739 }).then(function (content) { 21740 options.uploadzipfile = content; 21741 options.onLoad = function (clientLogObj) { 21742 clientLogObj.sendLogs(options.uploadzipfile,{ 21743 error: callBack.error, 21744 success: callBack.success, 21745 isZip: true 21746 }); 21747 }; 21748 userObj.getClientLog(options); 21749 }); 21750 }, 21751 21752 21753 /** 21754 * @ignore 21755 * Publish compressed client logs to server and clear the memory 21756 * 21757 * @param userObj 21758 * @param options 21759 * @param callBack 21760 * @param newLogData 21761 */ 21762 publishCompressedClientLog: function(userObj, options, callBack, newLogData) { 21763 // Avoid null references. 21764 options = options || {}; 21765 callBack = callBack || {}; 21766 21767 if (callBack.sending === "function") { 21768 callBack.sending(); 21769 } 21770 21771 _logStr = Utilities.getSanitizedUserAgentString() + " "; 21772 if(newLogData && newLogData.length > 0) { 21773 _logStr += unescape(newLogData.join("")); 21774 } else { 21775 //join the logs to correct string format 21776 _logStr += unescape(_logArray.join("")); 21777 } 21778 21779 var filename = 'Desktop-ClientLog' + '.' + userObj.getId() + (options.logScheduleId? '.schedule' + options.logScheduleId: '') + '.' + new Date().toISOString() + '.txt'; 21780 21781 _logStr = _logStr.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, ""); 21782 21783 var logZip = new JSZip(); 21784 logZip.file(filename, _logStr); 21785 logZip.generateAsync({ 21786 type: 'blob', 21787 compression: 'DEFLATE', 21788 compressionOptions: { 21789 level: 9 21790 } 21791 }).then(function (content) { 21792 options.uploadzipfile = content; 21793 21794 var tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 21795 /** @private */ 21796 options.onAdd = function(){ 21797 tmpOnAdd(); 21798 _logArray.length = 0; _logSize =0; 21799 _sendLogShown = false; 21800 callBack.success(); 21801 }; 21802 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 21803 /** @private */ 21804 options.onLoad = function (clientLogObj) { 21805 clientLogObj.sendLogs(options.uploadzipfile,{ 21806 error: callBack.error, 21807 success: options.onAdd, 21808 isZip: true 21809 }); 21810 }; 21811 21812 userObj.getClientLog(options); 21813 }); 21814 }, 21815 21816 /** 21817 * @ignore 21818 * Publish logs to server and clear the memory 21819 * 21820 * @param userObj 21821 * @param options 21822 * @param callBack 21823 */ 21824 publish: function(userObj, options, callBack) { 21825 // Avoid null references. 21826 options = options || {}; 21827 callBack = callBack || {}; 21828 21829 if (callBack.sending === "function") { 21830 callBack.sending(); 21831 } 21832 21833 //logs the basic version and machine info and escaped new line 21834 _logStr = Utilities.getSanitizedUserAgentString() + " "; 21835 21836 //join the logs to correct string format 21837 _logStr += unescape(_logArray.join("")); 21838 21839 21840 //turning log string to JSON obj 21841 var logObj = { 21842 ClientLog: { 21843 logData : _logStr //_logStr 21844 } 21845 }, 21846 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 21847 /** @private */ 21848 options.onAdd = function(){ 21849 tmpOnAdd(); 21850 _logArray.length = 0; _logSize =0; 21851 _sendLogShown = false; 21852 }; 21853 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 21854 /** @private */ 21855 options.onLoad = function (clientLogObj) { 21856 clientLogObj.sendLogs(logObj,{ 21857 error: callBack.error 21858 }); 21859 }; 21860 userObj.getClientLog(options); 21861 } 21862 }; 21863 }()); 21864 21865 window.finesse = window.finesse || {}; 21866 window.finesse.cslogger = window.finesse.cslogger || {}; 21867 /** @private */ 21868 window.finesse.cslogger.FinesseLogger = FinesseLogger; 21869 21870 return FinesseLogger; 21871 }); 21872 21873 /** 21874 * Contains a list of topics used for containerservices pubsub. 21875 * 21876 */ 21877 21878 /** 21879 * @class 21880 * Contains a list of topics with some utility functions. 21881 */ 21882 /** @private */ 21883 define('containerservices/Topics',[], function () { 21884 21885 var Topics = (function () { 21886 21887 /** 21888 * The namespace prepended to all Finesse topics. 21889 */ 21890 this.namespace = "finesse.containerservices"; 21891 21892 /** 21893 * @private 21894 * Gets the full topic name with the ContainerServices namespace prepended. 21895 * @param {String} topic 21896 * The topic category. 21897 * @returns {String} 21898 * The full topic name with prepended namespace. 21899 */ 21900 var _getNSTopic = function (topic) { 21901 return this.namespace + "." + topic; 21902 }; 21903 21904 21905 21906 /** @scope finesse.containerservices.Topics */ 21907 return { 21908 /** 21909 * @private 21910 * request channel. */ 21911 REQUESTS: _getNSTopic("requests"), 21912 21913 /** 21914 * @private 21915 * reload gadget channel. */ 21916 RELOAD_GADGET: _getNSTopic("reloadGadget"), 21917 21918 /** 21919 * @private 21920 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 21921 */ 21922 getTopic: function (restUri) { 21923 //The topic should not start with '/' else it will get replaced with 21924 //'.' which is invalid. 21925 //Thus, remove '/' if it is at the beginning of the string 21926 if (restUri.indexOf('/') === 0) { 21927 restUri = restUri.substr(1); 21928 } 21929 21930 //Replace every instance of "/" with ".". This is done to follow the 21931 //OpenAjaxHub topic name convention. 21932 return restUri.replace(/\//g, "."); 21933 } 21934 }; 21935 }()); 21936 21937 window.finesse = window.finesse || {}; 21938 window.finesse.containerservices = window.finesse.containerservices || {}; 21939 window.finesse.containerservices.Topics = Topics; 21940 21941 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 21942 finesse.containerservices = finesse.containerservices || {}; 21943 21944 return Topics; 21945 }); 21946 21947 /** The following comment is to prevent jslint errors about 21948 * using variables before they are defined. 21949 */ 21950 /*global finesse*/ 21951 21952 /** 21953 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 21954 * 21955 * @requires OpenAjax, finesse.containerservices.Topics 21956 */ 21957 21958 /** @private */ 21959 define('containerservices/MasterPublisher',[ 21960 "utilities/Utilities", 21961 "containerservices/Topics" 21962 ], 21963 function (Utilities, Topics) { 21964 21965 var MasterPublisher = function () { 21966 21967 var 21968 21969 /** 21970 * Reference to the gadget pubsub Hub instance. 21971 * @private 21972 */ 21973 _hub = gadgets.Hub, 21974 21975 /** 21976 * Reference to the Topics class. 21977 * @private 21978 */ 21979 _topics = Topics, 21980 21981 /** 21982 * Reference to conversion utilities class. 21983 * @private 21984 */ 21985 _utils = Utilities, 21986 21987 /** 21988 * References to ClientServices logger methods 21989 * @private 21990 */ 21991 _logger = { 21992 log: finesse.clientservices.ClientServices.log 21993 }, 21994 21995 /** 21996 * The types of possible request types supported when listening to the 21997 * requests channel. Each request type could result in different operations. 21998 * @private 21999 */ 22000 _REQTYPES = { 22001 ACTIVETAB: "ActiveTabReq", 22002 SET_ACTIVETAB: "SetActiveTabReq", 22003 RELOAD_GADGET: "ReloadGadgetReq" 22004 }, 22005 22006 /** 22007 * Handles client requests made to the request topic. The type of the 22008 * request is described in the "type" property within the data payload. Each 22009 * type can result in a different operation. 22010 * @param {String} topic 22011 * The topic which data was published to. 22012 * @param {Object} data 22013 * The data containing requests information published by clients. 22014 * @param {String} data.type 22015 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 22016 * @param {Object} data.data 22017 * May contain data relevant for the particular requests. 22018 * @param {String} [data.invokeID] 22019 * The ID used to identify the request with the response. The invoke ID 22020 * will be included in the data in the publish to the topic. It is the 22021 * responsibility of the client to correlate the published data to the 22022 * request made by using the invoke ID. 22023 * @private 22024 */ 22025 _clientRequestHandler = function (topic, data) { 22026 22027 //Ensure a valid data object with "type" and "data" properties. 22028 if (typeof data === "object" && 22029 typeof data.type === "string" && 22030 typeof data.data === "object") { 22031 switch (data.type) { 22032 case _REQTYPES.ACTIVETAB: 22033 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 22034 break; 22035 case _REQTYPES.SET_ACTIVETAB: 22036 if (typeof data.data.id === "string") { 22037 _logger.log("Handling request to activate tab: " + data.data.id); 22038 if (!finesse.container.Tabs.activateTab(data.data.id)) { 22039 _logger.log("No tab found with id: " + data.data.id); 22040 } 22041 } 22042 break; 22043 case _REQTYPES.RELOAD_GADGET: 22044 _hub.publish("finesse.containerservices.reloadGadget", data.data); 22045 break; 22046 default: 22047 break; 22048 } 22049 } 22050 }; 22051 22052 (function () { 22053 22054 //Listen to a request channel to respond to any requests made by other 22055 //clients because the Master may have access to useful information. 22056 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 22057 }()); 22058 22059 //BEGIN TEST CODE// 22060 /** 22061 * Test code added to expose private functions that are used by unit test 22062 * framework. This section of code is removed during the build process 22063 * before packaging production code. The [begin|end]TestSection are used 22064 * by the build to identify the section to strip. 22065 * @ignore 22066 */ 22067 this.beginTestSection = 0; 22068 22069 /** 22070 * @ignore 22071 */ 22072 this.getTestObject = function () { 22073 //Load mock dependencies. 22074 var _mock = new MockControl(); 22075 _hub = _mock.createMock(gadgets.Hub); 22076 22077 return { 22078 //Expose mock dependencies 22079 mock: _mock, 22080 hub: _hub, 22081 22082 //Expose internal private functions 22083 reqtypes: _REQTYPES, 22084 22085 clientRequestHandler: _clientRequestHandler 22086 22087 }; 22088 }; 22089 22090 22091 /** 22092 * @ignore 22093 */ 22094 this.endTestSection = 0; 22095 //END TEST CODE// 22096 }; 22097 22098 window.finesse = window.finesse || {}; 22099 window.finesse.containerservices = window.finesse.containerservices || {}; 22100 window.finesse.containerservices.MasterPublisher = MasterPublisher; 22101 22102 return MasterPublisher; 22103 }); 22104 22105 /** 22106 * JavaScript representation of the Finesse WorkflowActionEvent object. 22107 * 22108 * @requires finesse.FinesseBase 22109 */ 22110 22111 /** The following comment is to prevent jslint errors about 22112 * using variables before they are defined. 22113 */ 22114 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 22115 /** @private */ 22116 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 22117 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 22118 /** 22119 * Reference to the WorkflowActionEvent name 22120 * This will be set by setWorkflowActionEvent 22121 * @private 22122 */ 22123 _name: null, 22124 22125 /** 22126 * Reference to the WorkflowActionEvent type 22127 * This will be set by setWorkflowActionEvent 22128 * @private 22129 */ 22130 _type: null, 22131 22132 /** 22133 * Reference to the WorkflowActionEvent handledBy value 22134 * This will be set by setWorkflowActionEvent 22135 * @private 22136 */ 22137 _handledBy: null, 22138 22139 /** 22140 * Reference to the WorkflowActionEvent params array 22141 * This will be set by setWorkflowActionEvent 22142 * @private 22143 */ 22144 _params: [], 22145 22146 /** 22147 * Reference to the WorkflowActionEvent actionVariables array 22148 * This will be set by setWorkflowActionEvent 22149 * @private 22150 */ 22151 _actionVariables: [], 22152 22153 /** 22154 * @class 22155 * JavaScript representation of a WorkflowActionEvent object. 22156 * The WorkflowActionEvent object is delivered as the payload of 22157 * a WorkflowAction callback. This can be subscribed to by using 22158 * {@link finesse.containerservices.ContainerServices#addHandler} with a 22159 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 22160 * Gadgets should key on events with a handleBy value of "OTHER". 22161 * 22162 * @constructs 22163 **/ 22164 init: function () { 22165 this._super(); 22166 }, 22167 22168 /** 22169 * Validate that the passed in object is a WorkflowActionEvent object 22170 * and sets the variables if it is 22171 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 22172 * it validates successfully. 22173 * @returns {Boolean} Whether it is valid or not. 22174 * @private 22175 */ 22176 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 22177 var returnValue; 22178 22179 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 22180 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 22181 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 22182 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 22183 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 22184 this._name = maybeWorkflowActionEvent.name; 22185 this._type = maybeWorkflowActionEvent.type; 22186 this._handledBy = maybeWorkflowActionEvent.handledBy; 22187 this._params = maybeWorkflowActionEvent.params; 22188 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 22189 returnValue = true; 22190 } else { 22191 returnValue = false; 22192 } 22193 22194 return returnValue; 22195 }, 22196 22197 /** 22198 * Getter for the WorkflowActionEvent name. 22199 * @returns {String} The name of the WorkflowAction. 22200 */ 22201 getName: function () { 22202 // escape nulls to empty string 22203 return this._name || ""; 22204 }, 22205 22206 /** 22207 * Getter for the WorkflowActionEvent type. 22208 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 22209 */ 22210 getType: function () { 22211 // escape nulls to empty string 22212 return this._type || ""; 22213 }, 22214 22215 /** 22216 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 22217 * events with a handleBy of "OTHER". 22218 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 22219 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 22220 */ 22221 getHandledBy: function () { 22222 // escape nulls to empty string 22223 return this._handledBy || ""; 22224 }, 22225 22226 22227 /** 22228 * Getter for the WorkflowActionEvent Params map. 22229 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 22230 * BROWSER_POP<ul> 22231 * <li>windowName : Name of window to pop into, or blank to always open new window. 22232 * <li>path : URL to open.</ul> 22233 * HTTP_REQUEST<ul> 22234 * <li>method : "PUT" or "POST". 22235 * <li>location : "FINESSE" or "OTHER". 22236 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 22237 * <li>path : Request URL. 22238 * <li>body : Request content for POST requests.</ul> 22239 */ 22240 getParams: function () { 22241 var map = {}, 22242 params = this._params, 22243 i, 22244 param; 22245 22246 if (params === null || params.length === 0) { 22247 return map; 22248 } 22249 22250 for (i = 0; i < params.length; i += 1) { 22251 param = params[i]; 22252 // escape nulls to empty string 22253 param.name = param.name || ""; 22254 param.value = param.value || ""; 22255 param.expandedValue = param.expandedValue || ""; 22256 map[param.name] = param; 22257 } 22258 22259 return map; 22260 }, 22261 22262 /** 22263 * Getter for the WorkflowActionEvent ActionVariables map 22264 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 22265 */ 22266 getActionVariables: function() { 22267 var map = {}, 22268 actionVariables = this._actionVariables, 22269 i, 22270 actionVariable; 22271 22272 if (actionVariables === null || actionVariables.length === 0) { 22273 return map; 22274 } 22275 22276 for (i = 0; i < actionVariables.length; i += 1) { 22277 actionVariable = actionVariables[i]; 22278 // escape nulls to empty string 22279 actionVariable.name = actionVariable.name || ""; 22280 actionVariable.type = actionVariable.type || ""; 22281 actionVariable.node = actionVariable.node || ""; 22282 actionVariable.testValue = actionVariable.testValue || ""; 22283 actionVariable.actualValue = actionVariable.actualValue || ""; 22284 map[actionVariable.name] = actionVariable; 22285 } 22286 22287 return map; 22288 } 22289 }); 22290 22291 22292 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 22293 /** 22294 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 22295 * with the action, but first and foremost Finesse will handle this WorkflowAction. 22296 */ 22297 FINESSE: "FINESSE", 22298 22299 /** 22300 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 22301 * ignore this action and expects Gadget Developers to take action. 22302 */ 22303 OTHER: "OTHER", 22304 22305 /** 22306 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 22307 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 22308 * @constructs 22309 */ 22310 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 22311 }; 22312 22313 window.finesse = window.finesse || {}; 22314 window.finesse.containerservices = window.finesse.containerservices || {}; 22315 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 22316 22317 return WorkflowActionEvent; 22318 }); 22319 22320 /** 22321 * JavaScript representation of the Finesse TimerTickEvent 22322 * 22323 * @requires finesse.FinesseBase 22324 */ 22325 22326 /** The following comment is to prevent jslint errors about 22327 * using variables before they are defined. 22328 */ 22329 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 22330 /** @private */ 22331 define('containerservices/TimerTickEvent',[ 22332 "FinesseBase" 22333 ], 22334 function (FinesseBase) { 22335 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 22336 /** 22337 * date the TimerTickEvent was queued 22338 * @private 22339 */ 22340 _dateQueued: null, 22341 22342 /** 22343 * the frequency of the timer tick (in miiliseconds) 22344 * @private 22345 */ 22346 _tickFrequency: 1000, 22347 22348 /** 22349 * @class 22350 * JavaScript representation of a TimerTickEvent object. 22351 * The TimerTickEvent object is delivered as the payload of 22352 * a TimerTickEvent callback. This can be subscribed to by using 22353 * {@link finesse.containerservices.ContainerServices#addHandler} with a 22354 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 22355 * 22356 * @constructs 22357 **/ 22358 init: function (tickFrequency, dateQueued) { 22359 this._super(); 22360 22361 this._tickFrequency = tickFrequency; 22362 this._dateQueued = dateQueued; 22363 }, 22364 22365 /** 22366 * Get the "tickFrequency" field 22367 * @param {int} which is the "TickFrequency" field 22368 * @private 22369 */ 22370 getTickFrequency: function () { 22371 return this._tickFrequency; 22372 }, 22373 22374 /** 22375 * Getter for the TimerTickEvent "DateQueued" field. 22376 * @returns {Date} which is a Date object when the TimerTickEvent was queued 22377 */ 22378 getDateQueued: function () { 22379 return this._dateQueued; 22380 } 22381 22382 }); 22383 22384 window.finesse = window.finesse || {}; 22385 window.finesse.containerservices = window.finesse.containerservices || {}; 22386 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 22387 22388 return TimerTickEvent; 22389 }); 22390 22391 /** 22392 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 22393 * 22394 * @requires finesse.FinesseBase 22395 */ 22396 22397 /** The following comment is to prevent jslint errors about 22398 * using variables before they are defined. 22399 */ 22400 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 22401 /** @private */ 22402 define('containerservices/GadgetViewChangedEvent',[ 22403 "FinesseBase" 22404 ], 22405 function (FinesseBase) { 22406 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 22407 /** 22408 * Reference to the gadget id 22409 * @private 22410 */ 22411 _gadgetId: null, 22412 22413 /** 22414 * Reference to the tab id 22415 * @private 22416 */ 22417 _tabId: null, 22418 22419 /** 22420 * Reference to the maxAvailableHeight 22421 * @private 22422 */ 22423 _maxAvailableHeight: null, 22424 22425 /** 22426 * Reference to the view 22427 * E.g. 'default' or 'canvas' 22428 * @private 22429 */ 22430 _view: null, 22431 22432 /** 22433 * @class 22434 * JavaScript representation of a GadgetViewChangedEvent object. 22435 * The GadgetViewChangedEvent object is delivered as the payload of 22436 * a GadgetViewChangedEvent callback. This can be subscribed to by using 22437 * {@link finesse.containerservices.ContainerServices#addHandler} with a 22438 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 22439 * 22440 * @constructs 22441 **/ 22442 init: function (gadgetId, tabId, maxAvailableHeight, view) { 22443 this._super(); 22444 22445 this._gadgetId = gadgetId; 22446 this._tabId = tabId; 22447 this._maxAvailableHeight = maxAvailableHeight; 22448 this._view = view; 22449 }, 22450 22451 /** 22452 * Getter for the gadget id. 22453 * @returns {String} The identifier for the gadget changing view. 22454 */ 22455 getGadgetId: function () { 22456 // escape nulls to empty string 22457 return this._gadgetId || ""; 22458 }, 22459 22460 /** 22461 * Getter for the maximum available height. 22462 * @returns {String} The maximum available height for the gadget's view. 22463 */ 22464 getMaxAvailableHeight: function () { 22465 // escape nulls to empty string 22466 return this._maxAvailableHeight || ""; 22467 }, 22468 22469 /** 22470 * Getter for the tab id. 22471 * @returns {String} The identifier for the tab where the gadget changing view resides. 22472 */ 22473 getTabId: function () { 22474 // escape nulls to empty string 22475 return this._tabId || ""; 22476 }, 22477 22478 /** 22479 * Getter for the view. 22480 * @returns {String} The view type the gadget is changing to. 22481 */ 22482 getView: function () { 22483 // escape nulls to empty string 22484 return this._view || ""; 22485 } 22486 }); 22487 22488 window.finesse = window.finesse || {}; 22489 window.finesse.containerservices = window.finesse.containerservices || {}; 22490 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 22491 22492 return GadgetViewChangedEvent; 22493 }); 22494 22495 /** 22496 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 22497 * 22498 * @requires finesse.FinesseBase 22499 */ 22500 22501 /** The following comment is to prevent jslint errors about 22502 * using variables before they are defined. 22503 */ 22504 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 22505 /** @private */ 22506 define('containerservices/MaxAvailableHeightChangedEvent',[ 22507 "FinesseBase" 22508 ], 22509 function (FinesseBase) { 22510 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 22511 22512 /** 22513 * Reference to the maxAvailableHeight 22514 * @private 22515 */ 22516 _maxAvailableHeight: null, 22517 22518 /** 22519 * @class 22520 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 22521 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 22522 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 22523 * {@link finesse.containerservices.ContainerServices#addHandler} with a 22524 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 22525 * 22526 * @constructs 22527 **/ 22528 init: function (maxAvailableHeight) { 22529 this._super(); 22530 22531 this._maxAvailableHeight = maxAvailableHeight; 22532 }, 22533 22534 /** 22535 * Getter for the maximum available height. 22536 * @returns {String} The maximum available height for a gadget in canvas view 22537 */ 22538 getMaxAvailableHeight: function () { 22539 // escape nulls to empty string 22540 return this._maxAvailableHeight || ""; 22541 } 22542 }); 22543 22544 window.finesse = window.finesse || {}; 22545 window.finesse.containerservices = window.finesse.containerservices || {}; 22546 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 22547 22548 return MaxAvailableHeightChangedEvent; 22549 }); 22550 22551 /** 22552 * Exposes a set of API wrappers that will hide the dirty work of 22553 * constructing Finesse API requests and consuming Finesse events. 22554 * 22555 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 22556 */ 22557 22558 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 22559 /*global window:true, gadgets:true, publisher:true, define:true, finesse:true, _tabTracker:true, _workflowActionEventTracker:true, _masterReloader:true, _accessTokenRefreshed:true, frameElement:true, $:true, parent:true, MockControl:true, _getNotifierReference:true, _gadgetViewChanged:true, _maxAvailableHeightChanged:true */ 22560 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 22561 /** @private */ 22562 define('containerservices/ContainerServices',[ 22563 "utilities/Utilities", 22564 "restservices/Notifier", 22565 "containerservices/Topics", 22566 "containerservices/MasterPublisher", 22567 "containerservices/WorkflowActionEvent", 22568 "containerservices/TimerTickEvent", 22569 "containerservices/GadgetViewChangedEvent", 22570 "containerservices/MaxAvailableHeightChangedEvent" 22571 ], 22572 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 22573 22574 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 22575 22576 var 22577 22578 /** 22579 * Shortcut reference to the Utilities singleton 22580 * This will be set by init() 22581 * @private 22582 */ 22583 _util, 22584 22585 /** 22586 * Shortcut reference to the gadget pubsub Hub instance. 22587 * This will be set by init() 22588 * @private 22589 */ 22590 _hub, 22591 22592 /** 22593 * Boolean whether this instance is master or not 22594 * @private 22595 */ 22596 _master = false, 22597 22598 /** 22599 * Whether the Client Services have been initiated yet. 22600 * @private 22601 */ 22602 _inited = false, 22603 22604 /** 22605 * References to ClientServices logger methods 22606 * @private 22607 */ 22608 _logger = { 22609 log: finesse.clientservices.ClientServices.log 22610 }, 22611 22612 /** 22613 * Stores the list of subscription IDs for all subscriptions so that it 22614 * could be retrieve for unsubscriptions. 22615 * @private 22616 */ 22617 _subscriptionID = {}, 22618 22619 /** 22620 * Reference to the gadget's parent container 22621 * @private 22622 */ 22623 _container, 22624 22625 /** 22626 * Reference to the MasterPublisher 22627 * @private 22628 */ 22629 _publisher, 22630 22631 /** 22632 * Object that will contain the Notifiers 22633 * @private 22634 */ 22635 _notifiers = {}, 22636 22637 /** 22638 * Reference to the tabId that is associated with the gadget 22639 * @private 22640 */ 22641 _myTab = null, 22642 22643 /** 22644 * Reference to the visibility of current gadget 22645 * @private 22646 */ 22647 _visible = false, 22648 22649 /** 22650 * Reference for auth modes constants. 22651 * @private 22652 */ 22653 _authModes, 22654 22655 /** 22656 * Shortcut reference to the Topics class. 22657 * This will be set by init() 22658 * @private 22659 */ 22660 _topics, 22661 22662 /** 22663 * Check whether the common desktop apis are available for finext. 22664 * In case it not available it will use the existing finesse Tab logic 22665 * @private 22666 */ 22667 _commonDesktop, 22668 22669 /** 22670 * Associates a topic name with the private handler function. 22671 * Adding a new topic requires that you add this association here 22672 * in to keep addHandler generic. 22673 * @param {String} topic : Specifies the callback to retrieve 22674 * @return {Function} The callback function associated with the topic param. 22675 * @private 22676 */ 22677 _topicCallback = function (topic) { 22678 var callback, notifier; 22679 switch (topic) 22680 { 22681 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 22682 callback = _tabTracker; 22683 break; 22684 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 22685 callback = _workflowActionEventTracker; 22686 break; 22687 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 22688 callback = _masterReloader; 22689 break; 22690 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 22691 callback = _gadgetViewChanged; 22692 break; 22693 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 22694 callback = _maxAvailableHeightChanged; 22695 break; 22696 case finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT: 22697 callback = _accessTokenRefreshed; 22698 break; 22699 default: 22700 callback = function (param) { 22701 var data = null; 22702 22703 notifier = _getNotifierReference(topic); 22704 22705 if (arguments.length === 1) { 22706 data = param; 22707 } else { 22708 data = arguments; 22709 } 22710 notifier.notifyListeners(data); 22711 }; 22712 } 22713 return callback; 22714 }, 22715 22716 /** 22717 * Ensure that ClientServices have been inited. 22718 * @private 22719 */ 22720 _isInited = function () { 22721 if (!_inited) { 22722 throw new Error("ContainerServices needs to be inited."); 22723 } 22724 return _inited; 22725 }, 22726 22727 /** 22728 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 22729 * @param {String} topic : Specifies the notifier to retrieve 22730 * @return {Notifier} The notifier object. 22731 * @private 22732 */ 22733 _getNotifierReference = function (topic) { 22734 if (!_notifiers.hasOwnProperty(topic)) 22735 { 22736 _notifiers[topic] = new Notifier(); 22737 } 22738 22739 return _notifiers[topic]; 22740 }, 22741 22742 /** 22743 * Utility function to make a subscription to a particular topic. Only one 22744 * callback function is registered to a particular topic at any time. 22745 * @param {String} topic 22746 * The full topic name. The topic name should follow the OpenAjax 22747 * convention using dot notation (ex: finesse.api.User.1000). 22748 * @param {Function} callback 22749 * The function that should be invoked with the data when an event 22750 * is delivered to the specific topic. 22751 * @returns {Boolean} 22752 * True if the subscription was made successfully and the callback was 22753 * been registered. False if the subscription already exist, the 22754 * callback was not overwritten. 22755 * @private 22756 */ 22757 _subscribe = function (topic, callback) { 22758 _isInited(); 22759 22760 //Ensure that the same subscription isn't made twice. 22761 if (!_subscriptionID[topic]) { 22762 //Store the subscription ID using the topic name as the key. 22763 _subscriptionID[topic] = _hub.subscribe(topic, 22764 //Invoke the callback just with the data object. 22765 function (topic, data) { 22766 callback(data); 22767 }); 22768 return true; 22769 } 22770 return false; 22771 }, 22772 22773 /** 22774 * Unsubscribe from a particular topic. 22775 * @param {String} topic : The full topic name. 22776 * @private 22777 */ 22778 _unsubscribe = function (topic) { 22779 _isInited(); 22780 22781 //Unsubscribe from the topic using the subscription ID recorded when 22782 //the subscription was made, then delete the ID from data structure. 22783 _hub.unsubscribe(_subscriptionID[topic]); 22784 delete _subscriptionID[topic]; 22785 }, 22786 22787 /** 22788 * Get my tab id. 22789 * @returns {String} tabid : The tabid of this container/gadget. 22790 * @private 22791 */ 22792 _getMyTab = function () { 22793 22794 // Adding startsWith to the string prototype for IE browser 22795 // See defect CSCvj93044 22796 22797 if (!String.prototype.startsWith) { 22798 String.prototype.startsWith = function(searchString, position) { 22799 position = position || 0; 22800 return this.indexOf(searchString, position) === position; 22801 }; 22802 } 22803 22804 if(_commonDesktop){ 22805 /** 22806 * This change is done for SPOG. SPOG container will set routNmae(i.e. current nav item) 22807 * as user preference 22808 */ 22809 var prefs,routeName; 22810 if (gadgets && gadgets.Prefs) { 22811 prefs = gadgets.Prefs(); 22812 routeName = prefs.getString('routeName'); 22813 } 22814 22815 if (routeName) { 22816 _myTab = routeName; 22817 } else { 22818 //This will return the nav name of the currently selected iframe.This selection is similar to the existing finesse desktop. 22819 //This is not tested with the page level gadget 22820 _myTab = _commonDesktop.route.getAllRoute()[$(frameElement).closest('div[data-group-id]').attr('data-group-id')-1]; 22821 if(_myTab){ 22822 _myTab = _myTab.startsWith('#/') ? _myTab.slice(2) : _myTab; 22823 } 22824 } 22825 }else{ 22826 if (_myTab === null){ 22827 try { 22828 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 22829 }catch (err) { 22830 _logger.log("Error accessing current tab: " + err.message); 22831 _myTab = null; 22832 } 22833 } 22834 } 22835 return _myTab; 22836 }, 22837 22838 /** 22839 * Callback function that is called when an activeTab message is posted to the Hub. 22840 * Notifies listener functions if this tab is the one that was just made active. 22841 * @param {String} tabId : The tabId which was just made visible. 22842 * @private 22843 */ 22844 _tabTracker = function(tabId) { 22845 if (tabId === _getMyTab()) { 22846 if(!_visible) { 22847 _visible = true; 22848 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 22849 } 22850 } else { 22851 _visible = false; 22852 } 22853 }, 22854 22855 /** 22856 * Make a request to set a particular tab active. This 22857 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 22858 * to ensure the gadget gets properly initialized. 22859 * @param {String} tabId 22860 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 22861 * @private 22862 */ 22863 _activateTab = function ( tabId ) { 22864 _logger.log("Sending request to activate tab: " + tabId); 22865 if(_hub){ 22866 var data = { 22867 type: "SetActiveTabReq", 22868 data: { id: tabId }, 22869 invokeID: (new Date()).getTime() 22870 }; 22871 _hub.publish(_topics.REQUESTS, data); 22872 } else { 22873 throw new Error("Hub is not defined."); 22874 } 22875 22876 }, 22877 22878 /** 22879 * Callback function that is called when a gadget view changed message is posted to the Hub. 22880 * @private 22881 */ 22882 _gadgetViewChanged = function (data) { 22883 if (data) { 22884 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 22885 data.gadgetId, 22886 data.tabId, 22887 data.maxAvailableHeight, 22888 data.view); 22889 22890 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 22891 } 22892 }, 22893 22894 /** 22895 * Callback function that is called when a max available height changed message is posted to the Hub. 22896 * @private 22897 */ 22898 _maxAvailableHeightChanged = function (data) { 22899 if (data) { 22900 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 22901 data.maxAvailableHeight); 22902 22903 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 22904 } 22905 }, 22906 22907 /** 22908 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 22909 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 22910 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 22911 * @private 22912 */ 22913 _workflowActionEventTracker = function(workflowActionEvent) { 22914 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 22915 22916 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 22917 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 22918 } 22919 // else 22920 // { 22921 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 22922 // } 22923 22924 }, 22925 22926 /** 22927 * Callback function that is called when a reloadGadget event message is posted to the Hub. 22928 * 22929 * Grabs the id of the gadget we want to reload from the data and reload it! 22930 * 22931 * @param {String} topic 22932 * which topic the event came on (unused) 22933 * @param {Object} data 22934 * the data published with the event 22935 * @private 22936 */ 22937 _masterReloader = function (topic, data) { 22938 var gadgetId = data.gadgetId; 22939 if (gadgetId) { 22940 _container.reloadGadget(gadgetId); 22941 } 22942 }, 22943 22944 _toggleGadgetExpandCollapse = function(type) { 22945 var data = {type: type, options: {'gadgetId': _findMyGadgetId()}}; 22946 parent.postMessage(data, "*"); 22947 }, 22948 22949 /** 22950 * Pulls the gadget id from the url parameters 22951 * @return {String} id of the gadget 22952 * @private 22953 */ 22954 _findMyGadgetId = function () { 22955 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 22956 return gadgets.util.getUrlParameters().mid; 22957 } 22958 }; 22959 22960 return { 22961 /** 22962 * @class 22963 * This class provides container-level services for gadget developers, exposing container events by 22964 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 22965 * event handling (add/remove). 22966 * @example 22967 * containerServices = finesse.containerservices.ContainerServices.init(); 22968 * containerServices.addHandler( 22969 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 22970 * function() { 22971 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 22972 * // automatically adjust the height of the gadget to show the html 22973 * gadgets.window.adjustHeight(); 22974 * }); 22975 * containerServices.makeActiveTabReq(); 22976 * 22977 * @constructs 22978 */ 22979 _fakeConstuctor: function () { 22980 /* This is here so we can document init() as a method rather than as a constructor. */ 22981 }, 22982 22983 /** 22984 * Initialize ContainerServices for use in gadget. 22985 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 22986 * @returns ContainerServices instance. 22987 */ 22988 init: function (master) { 22989 if (!_inited) { 22990 _inited = true; 22991 // Set shortcuts 22992 _util = Utilities; 22993 _authModes = _util.getAuthModes(); 22994 try { 22995 _commonDesktop = window.top.cd; 22996 } catch(err) { 22997 _logger.log("Error accessing common desktop: " + err.message); 22998 } 22999 23000 //init the hub only when it's available 23001 if(gadgets.Hub) { 23002 _hub = gadgets.Hub; 23003 } 23004 23005 if(Topics) { 23006 _topics = Topics; 23007 } 23008 23009 if (master) { 23010 _master = true; 23011 _container = finesse.container.Container; 23012 _publisher = new MasterPublisher(); 23013 23014 // subscribe for reloading gadget events 23015 // we only want the master ContainerServices handling these events 23016 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 23017 } else { 23018 // For SPOG like containers where parent.finesse is undefined. 23019 if(parent.finesse){ 23020 _container = parent.finesse.container.Container; 23021 } 23022 } 23023 } 23024 23025 this.makeActiveTabReq(); 23026 23027 if(finesse.modules && finesse.modules.ToastPopover){ 23028 finesse.ToastPopoverInstance = new finesse.modules.ToastPopover(); 23029 } 23030 23031 /* initialize popOverService */ 23032 if(window.finesse.containerservices.PopoverService){ 23033 window.finesse.containerservices.PopoverService.init(this); 23034 } 23035 23036 //Return the CS object for object chaining. 23037 return this; 23038 }, 23039 23040 /** 23041 * Shows the UI Dialog with the specified parameters. 23042 * @param {Object} options 23043 * An object containing additional options for the dialog. 23044 * @param {String} options.title 23045 * Title to use. 23046 * @param {Function} options.close 23047 * A function to invoke when the dialog is closed. 23048 * @param {String} options.message 23049 * The message to display in the dialog. 23050 * @param {Boolean} options.isBlocking 23051 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 23052 * @see finesse.containerservices.ContainerServices#hideDialog 23053 * @example 23054 * // containerServices = finesse.containerservices.ContainerServices.init(); 23055 * containerServices.showDialog( 23056 * {title:'Error Occurred',message: 'Something went wrong', close: {Function} 23057 * }); 23058 * 23059 * NoteIn case of SPOG like containers, _container.showDialog will not be present. in this case fallback dialog is used 23060 */ 23061 showDialog: function(options) { 23062 if (_container && _container.showDialog && _container.showDialog !== this.showDialog) { 23063 return _container.showDialog(options); 23064 } 23065 23066 // showDialog _container will be undefined incase of gadget hosted in spog. Hence jQuery dialog is used as a fallback modal/popup. 23067 if ( !this.fallbackDialog ) { 23068 this.fallbackDialog = $('<div />').dialog({ 23069 autoOpen: false, 23070 modal: true, 23071 dialogClass: "finesse-fallback-dialog" 23072 }); 23073 } 23074 23075 this.hideDialog(); 23076 23077 return this.fallbackDialog.dialog({ 23078 title: options.title, 23079 buttons: options.buttons, 23080 close: options.close || this.hideDialog, 23081 }).html(options.message).dialog("open"); 23082 }, 23083 23084 /** 23085 * Hides the UI Dialog. 23086 * @see finesse.containerservices.ContainerServices#showDialog 23087 * @example 23088 * // containerServices = finesse.containerservices.ContainerServices.init(); 23089 * containerServices.hideDialog(); 23090 */ 23091 hideDialog: function() { 23092 if (_container && _container.hideDialog && _container.hideDialog !== this.hideDialog) { 23093 return _container.hideDialog(); 23094 } 23095 23096 if ( this.fallbackDialog ) { 23097 return this.fallbackDialog.dialog('close'); 23098 } 23099 }, 23100 23101 /** 23102 * Shows the Certificate Banner with message "Gadget certificates are yet to be accepted." 23103 * @param {Function} [callback] 23104 * Callback is invoked when user closes the banner manually. 23105 * @returns {String} id 23106 * Note : Gadgets calling showCertificateBanner can ignore the return value as it is not required. 23107 * @see finesse.containerservices.ContainerServices#hideCertificateBanner 23108 * @example 23109 * // For Gadgets 23110 * // containerServices = finesse.containerservices.ContainerServices.init(); 23111 * containerServices.showCertificateBanner(callback); 23112 * 23113 * // For non gadget Client , id is required to hide the certificate banner 23114 * // which is returned when showCertificateBanner is called 23115 * var id = containerServices.showCertificateBanner(callback); 23116 * 23117 */ 23118 showCertificateBanner: function(callback) { 23119 if ((_container.showCertificateBanner !== undefined) && (_container.showCertificateBanner !== this.showCertificateBanner)) { 23120 var options = {}; 23121 /** 23122 * If getMyGadgetId is undefined , i.e. for components, a unique id will be returned, 23123 * which should be sent while calling hideCertificateBanner 23124 */ 23125 options.id = window.finesse.containerservices.ContainerServices.getMyGadgetId(); 23126 options.callback = callback; 23127 return _container.showCertificateBanner(options); 23128 } 23129 }, 23130 23131 /** 23132 * Request to hide the Certificate Banner. 23133 * The banner will be hidden when all gadgets which invoked showCertificateBanner 23134 * have made a corresponding invocation to hideCertificateBanner, or when the user closes the banner manually. 23135 * @param {String} [id] 23136 * Note : Gadgets do not need to send the id parameter. 23137 * The id is used by the desktop in scenarios where the banner has to be invoked by a non-gadget client. 23138 *@example 23139 * // For Gadgets 23140 * // containerServices = finesse.containerservices.ContainerServices.init(); 23141 * containerServices.hideCertificateBanner(); 23142 * 23143 * // For non gadget Client 23144 * containerServices.hideCertificateBanner(id); 23145 * 23146 */ 23147 hideCertificateBanner: function(id) { 23148 if(id === undefined && window.finesse.containerservices.ContainerServices.getMyGadgetId() === undefined) { 23149 throw new Error('ID returned when showCertificateBanner was called need to be sent as params'); 23150 } 23151 if ((_container.hideCertificateBanner !== undefined) && (_container.hideCertificateBanner !== this.hideCertificateBanner)) { 23152 return _container.hideCertificateBanner(id || window.finesse.containerservices.ContainerServices.getMyGadgetId()); 23153 } 23154 }, 23155 23156 /** 23157 * Reloads the current gadget. 23158 * For use from within a gadget only. 23159 * @example 23160 * // containerServices = finesse.containerservices.ContainerServices.init(); 23161 * containerServices.reloadMyGadget(); 23162 * 23163 */ 23164 reloadMyGadget: function () { 23165 var topic, gadgetId, data; 23166 23167 if (!_master) { 23168 // first unsubscribe this gadget from all topics on the hub 23169 for (topic in _notifiers) { 23170 if (_notifiers.hasOwnProperty(topic)) { 23171 _unsubscribe(topic); 23172 delete _notifiers[topic]; 23173 } 23174 } 23175 23176 // send an asynch request to the hub to tell the master container 23177 // services that we want to refresh this gadget 23178 gadgetId = _findMyGadgetId(); 23179 data = { 23180 type: "ReloadGadgetReq", 23181 data: {gadgetId: gadgetId}, 23182 invokeID: (new Date()).getTime() 23183 }; 23184 _hub.publish(_topics.REQUESTS, data); 23185 } 23186 }, 23187 23188 /** 23189 * Updates the url for this gadget and then reload it. 23190 * 23191 * This allows the gadget to be reloaded from a different location 23192 * than what is uploaded to the current server. For example, this 23193 * would be useful for 3rd party gadgets to implement their own failover 23194 * mechanisms. 23195 * 23196 * For use from within a gadget only. 23197 * 23198 * @param {String} url 23199 * url from which to reload gadget 23200 * @example 23201 * // containerServices = finesse.containerservices.ContainerServices.init(); 23202 * containerServices.reloadMyGadgetFromUrl(url); 23203 */ 23204 reloadMyGadgetFromUrl: function (url) { 23205 if (!_master) { 23206 var gadgetId = _findMyGadgetId(); 23207 23208 // update the url in the container 23209 _container.modifyGadgetUrl(gadgetId, url); 23210 23211 // reload it 23212 this.reloadMyGadget(); 23213 } 23214 }, 23215 23216 /** 23217 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 23218 * will be invoked when that topic is notified. 23219 * @param {String} topic 23220 * The Hub topic to which we are listening. 23221 * @param {Function} callback 23222 * The callback function to invoke. 23223 * @see finesse.containerservices.ContainerServices.Topics 23224 * @see finesse.containerservices.ContainerServices#removeHandler 23225 * @example 23226 * containerServices.addHandler( 23227 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 23228 * function() { 23229 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 23230 * // automatically adjust the height of the gadget to show the html 23231 * gadgets.window.adjustHeight(); 23232 * }); 23233 */ 23234 addHandler: function (topic, callback) { 23235 _isInited(); 23236 var notifier = null; 23237 23238 try { 23239 // For backwards compatibility... 23240 if (topic === "tabVisible") { 23241 if (window.console && typeof window.console.log === "function") { 23242 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 23243 } 23244 23245 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 23246 } 23247 23248 // Add the callback to the notifier. 23249 _util.validateHandler(callback); 23250 23251 notifier = _getNotifierReference(topic); 23252 23253 notifier.addListener(callback); 23254 23255 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 23256 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 23257 // to only when necessary. 23258 _subscribe(topic, _topicCallback(topic)); 23259 23260 } catch (err) { 23261 throw new Error("addHandler(): " + err); 23262 } 23263 }, 23264 23265 /** 23266 * Removes a previously-added handler for one of the supported topics. 23267 * @param {String} topic 23268 * The Hub topic from which we are removing the callback. 23269 * @param {Function} callback 23270 * The name of the callback function to remove. 23271 * @see finesse.containerservices.ContainerServices.Topics 23272 * @see finesse.containerservices.ContainerServices#addHandler 23273 * @example 23274 * containerServices.removeHandler( 23275 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 23276 * function() { 23277 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 23278 * // automatically adjust the height of the gadget to show the html 23279 * gadgets.window.adjustHeight(); 23280 * }); 23281 */ 23282 removeHandler: function(topic, callback) { 23283 var notifier = null; 23284 23285 try { 23286 _util.validateHandler(callback); 23287 23288 notifier = _getNotifierReference(topic); 23289 23290 notifier.removeListener(callback); 23291 } catch (err) { 23292 throw new Error("removeHandler(): " + err); 23293 } 23294 }, 23295 23296 /** 23297 * Wrapper API for publishing data on the Openajax hub 23298 * @param {String} topic 23299 * The Hub topic to which we are publishing. 23300 * @param {Object} data 23301 * The data to be published on the hub. 23302 * @example 23303 * containerServices.publish('TOPIC_NAME',data); 23304 * @see 23305 * finesse.containerservices.ContainerServices.Topics 23306 */ 23307 publish : function(topic , data){ 23308 if(_hub){ 23309 _hub.publish(topic, data); 23310 } else { 23311 throw new Error("Hub is not defined."); 23312 } 23313 }, 23314 23315 /** 23316 * Returns the visibility of current gadget. Note that this 23317 * will not be set until after the initialization of the gadget. 23318 * @return {Boolean} The visibility of current gadget. 23319 * @example 23320 * containerServices.tabVisible(); 23321 */ 23322 tabVisible: function(){ 23323 return _visible; 23324 }, 23325 23326 /** 23327 * Make a request to check the current tab. The 23328 * activeTab event will be invoked if on the active tab. The 23329 * handler for activeTab can be added using {@link finesse.containerservices.ContainerServices#addHandler} 23330 * with TOPIC 'ACTIVE_TAB' 23331 * Called by default when ContainerServices is initialized, 23332 * i.e. finesse.containerservices.ContainerServices.init(); 23333 * @example 23334 * containerServices.makeActiveTabReq(); 23335 */ 23336 makeActiveTabReq : function () { 23337 if(_hub){ 23338 var data = { 23339 type: "ActiveTabReq", 23340 data: {}, 23341 invokeID: (new Date()).getTime() 23342 }; 23343 _hub.publish(_topics.REQUESTS, data); 23344 } else { 23345 throw new Error("Hub is not defined."); 23346 } 23347 23348 }, 23349 23350 /** 23351 * Make a request to set a particular tab active. 23352 * @param {String} tabId 23353 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 23354 * @example 23355 * containerServices.activateTab(tabId); 23356 */ 23357 activateTab : function (tabId) { 23358 _activateTab(tabId); 23359 }, 23360 23361 /** 23362 * Make a request to set this container's tab active. 23363 * This will sent request to activate the tab in which 23364 * gadget is present. 23365 * @example 23366 * containerServices.activateMyTab(); 23367 */ 23368 activateMyTab : function () { 23369 _activateTab( _getMyTab() ); 23370 }, 23371 23372 /** 23373 * Get the tabId of my container/gadget. 23374 * @returns {String} tabid : The tabid of this container/gadget. 23375 * 23376 * @example 23377 * containerServices.getMyTabId(); 23378 */ 23379 getMyTabId : function () { 23380 return _getMyTab(); 23381 }, 23382 23383 /** 23384 * Gets the id of the gadget. 23385 * @returns {number} the id of the gadget 23386 * @example 23387 * containerServices.getMyGadgetId(); 23388 */ 23389 getMyGadgetId : function () { 23390 return _findMyGadgetId(); 23391 }, 23392 23393 /** 23394 * expandMyGadget method allows the gadget to expand itself. 23395 * 23396 * To enable collapsible feature for any gadget add below gadget ModulePrefs inside the gadget code. 23397 * 23398 * <ModulePrefs title="Sample Gadget" description="Sample Gadget"> 23399 * <Optional feature="collapsible" /> 23400 * </ModulePrefs> 23401 */ 23402 expandMyGadget: function(){ 23403 _toggleGadgetExpandCollapse('EXPAND_GADGET'); 23404 }, 23405 23406 /** 23407 * collapseMyGadget method allows the gadget to collapse itself. In collapse state, only gadget header will be displayed. 23408 * 23409 * To enable collapsible feature for any gadget add below gadget ModulePrefs inside the gadget code. 23410 * 23411 * <ModulePrefs title="Sample Gadget" description="Sample Gadget"> 23412 * <Optional feature="collapsible" /> 23413 * </ModulePrefs> 23414 */ 23415 collapseMyGadget: function(){ 23416 _toggleGadgetExpandCollapse('COLLAPSE_GADGET'); 23417 }, 23418 23419 //BEGIN TEST CODE// 23420 /** 23421 * Test code added to expose private functions that are used by unit test 23422 * framework. This section of code is removed during the build process 23423 * before packaging production code. The [begin|end]TestSection are used 23424 * by the build to identify the section to strip. 23425 * @ignore 23426 */ 23427 beginTestSection : 0, 23428 23429 /** 23430 * @ignore 23431 */ 23432 getTestObject: function () { 23433 //Load mock dependencies. 23434 var _mock = new MockControl(); 23435 _util = _mock.createMock(Utilities); 23436 _hub = _mock.createMock(gadgets.Hub); 23437 _inited = true; 23438 return { 23439 //Expose mock dependencies 23440 mock: _mock, 23441 hub: _hub, 23442 util: _util, 23443 addHandler: this.addHandler, 23444 removeHandler: this.removeHandler 23445 }; 23446 }, 23447 23448 /** 23449 * @ignore 23450 */ 23451 endTestSection: 0 23452 //END TEST CODE// 23453 }; 23454 }()); 23455 23456 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 23457 /** 23458 * Topic for subscribing to be notified when the active tab changes. 23459 * The provided callback will be invoked when the tab containing the gadget 23460 * subscribed to the topic becomes active. To ensure code is called 23461 * when the gadget is already on the active tab use the 23462 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 23463 * method. 23464 */ 23465 ACTIVE_TAB: "finesse.containerservices.activeTab", 23466 23467 /** 23468 * Topic for WorkflowAction events traffic. 23469 * The provided callback will be invoked when a WorkflowAction needs 23470 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 23471 * that can be used to interrogate the WorkflowAction and determine to use or not. 23472 */ 23473 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 23474 23475 /** 23476 * Topic for Timer Tick event. 23477 * The provided callback will be invoked when this event is fired. 23478 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 23479 */ 23480 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 23481 23482 /** 23483 * Topic for Non Voice Gadgets to communicate with Finesse Container. 23484 * Finesse container will handle this event 23485 */ 23486 FINEXT_NON_VOICE_GADGET_EVENT : "finext.nv", 23487 23488 /** 23489 * Topic for listening to the active call event. 23490 * The provided callback will be invoked when a agent will have a call 23491 * or when the call is ended. 23492 * The callback will be passed ActiveCallStatusEvent 23493 * Example, when the call is active ActiveCallStatusEvent {status: true,type: "info"} 23494 * and when the call is ended or inactive ActiveCallStatusEvent {status: false, type: "info"} 23495 */ 23496 ACTIVE_CALL_STATUS_EVENT : "finesse.containerservices.activeCallStatusEvent", 23497 23498 /** 23499 * Topic for Gadgets to communicate with Finesse Popover Container. 23500 * The provided callback will be invoked when popover event is fired. 23501 */ 23502 FINEXT_POPOVER_EVENT : "finext.popover", 23503 23504 /** 23505 * Topic for Reload Gadget events traffic. 23506 * Only the master ContainerServices instance will handle this event. 23507 */ 23508 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 23509 23510 /** 23511 * Topic for listening to gadget view changed events. 23512 * The provided callback will be invoked when a gadget changes view. 23513 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 23514 */ 23515 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 23516 23517 /** 23518 * Topic for listening to max available height changed events. 23519 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 23520 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 23521 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 23522 */ 23523 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 23524 23525 /** 23526 * @class This is the set of Topics used for subscribing for events from ContainerServices. 23527 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 23528 * 23529 * @constructs 23530 */ 23531 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 23532 }; 23533 23534 window.finesse = window.finesse || {}; 23535 window.finesse.containerservices = window.finesse.containerservices || {}; 23536 window.finesse.containerservices.ContainerServices = ContainerServices; 23537 23538 return ContainerServices; 23539 }); 23540 23541 /** 23542 * FinesseToaster is a utility class to show toaster notification in Finesse. 23543 * FinesseToaster leverages HTML5 Notification API to display Toaster 23544 * Notification. 23545 * 23546 */ 23547 23548 define('containerservices/FinesseToaster',[],function() { 23549 23550 var FinesseToaster = (function() { 23551 /** @lends finesse.containerservices.FinesseToaster.prototype */ 23552 23553 var 23554 23555 /** How long the toaster will be displayed by default. Default timeout is 8 seconds */ 23556 AUTO_CLOSE_TIME = 8000, 23557 23558 /** PERMISSION_GRANTED constant for granted string */ 23559 PERMISSION_GRANTED = 'granted', 23560 23561 /** PERMISSION_DEFAULT constant for default string */ 23562 PERMISSION_DEFAULT = 'default', 23563 23564 /** PERMISSION_DENIED constant for denied string */ 23565 PERMISSION_DENIED = 'denied', 23566 23567 /** ICON_PATH constant for holding path icon images */ 23568 ICON_PATH = '/desktop/theme/finesse/images/modules/', 23569 23570 /** 23571 * Shortcut reference to finesse.cslogger.ClientLogger singleton This 23572 * will be set by init(), it should already be initialized by 23573 * PageServices 23574 * 23575 * @private 23576 */ 23577 _logger, 23578 23579 /** 23580 * Boolean variable to determine if finesse toaster is enabled 23581 * @private 23582 */ 23583 _isToasterDisabled = false, 23584 23585 /** 23586 * Boolean variable to determine if finesse toaster is initialized 23587 * @private 23588 */ 23589 _isToasterInitialized, 23590 23591 /** 23592 * this function check of provided parameter is a javascript function. 23593 * 23594 * @private 23595 */ 23596 isFunction = function(param) { 23597 if (typeof param === "function") { 23598 return true; 23599 } 23600 return false; 23601 }, 23602 23603 /** 23604 * _createNotification creates Notification instance 23605 * 23606 * @param {String} 23607 * title title string should be displayed in the Toaster 23608 * @param {Object} 23609 * options JSON object for notification options. 23610 */ 23611 _createNotification = function(title, options) { 23612 var notification = new window.Notification(title, options); 23613 return notification; 23614 }, 23615 23616 /** 23617 * _setAutoClose set the auto close time for toaster, it checks if 23618 * client code passed custom time out for the toaster, otherwise it set 23619 * the AUTO_CLOSE_TIME 23620 * 23621 * @param {Object} 23622 * notification window.Notification that creates Html5 23623 * Notification. 23624 * @param {String} 23625 * autoClose autoClose time of the Toaster 23626 * @return toasterTimeout Toaster Timeout 23627 */ 23628 _setAutoClose = function(notification, autoClose) { 23629 23630 // check if custom close time passed other wise set 23631 // DEFAULT_AUTO_CLOSE 23632 var autoCloseTime = (autoClose && !isNaN(autoClose)) ? autoClose 23633 : AUTO_CLOSE_TIME, 23634 // set the time out for notification toaster 23635 toasterTimer = setTimeout(function() { 23636 notification.close(); 23637 }, autoCloseTime); 23638 23639 return toasterTimer; 23640 23641 }, 23642 23643 /** This method will request permission to display Toaster. */ 23644 _requestPermission = function() { 23645 // If they are not denied (i.e. default) 23646 if (window.Notification 23647 && window.Notification.permission !== PERMISSION_DENIED) { 23648 // Request permission 23649 window.Notification 23650 .requestPermission(function(status) { 23651 23652 // Change based on user's decision 23653 if (window.Notification.permission !== status) { 23654 window.Notification.permission = status; 23655 } 23656 _logger 23657 .log("FinesseToaster.requestPermission(): request permission status " 23658 + status); 23659 23660 }); 23661 23662 } else { 23663 _logger 23664 .log("FinesseToaster.requestPermission(): Notification not supported or permission denied."); 23665 } 23666 23667 }, 23668 23669 /** 23670 * This method will add onclick and onerror listener to Notification. 23671 * on click of toaster the gadget which originally had focus may loose 23672 * the focus. To get the back the focus on any element inside the gadget 23673 * use the on click callback handler. 23674 * 23675 * @param {Object} 23676 * notification window.Notification that creates Html5 23677 * Notification. 23678 * @param {Object} 23679 * options JSON object for notification options. 23680 */ 23681 _addToasterListeners = function(notification, options, toasterTimer) { 23682 // this is onlcik handler of toaster. this handler will be invoked 23683 // on click of toaster 23684 notification.onclick = function() { 23685 // in case of manually closed toaster, stop the notification 23686 // auto close method to be invoked 23687 clearTimeout(toasterTimer); 23688 // This will maximize/activate chrome browser on click of 23689 // toaster. this handling required only in case of Chrome 23690 if (window.chrome) { 23691 parent.focus(); 23692 } 23693 23694 if (options && options.onclick) { 23695 if (isFunction(options.onclick)) { 23696 options.onclick(); 23697 } else { 23698 throw new Error("onclick callback must be a function"); 23699 } 23700 } 23701 23702 //close toaster upon click 23703 this.close(); 23704 23705 }; 23706 23707 // this is onerror handler of toaster, if there is any error while 23708 // loading toaster this hadnler will be invoked 23709 notification.onerror = function() { 23710 if (options && options.onerror) { 23711 if (isFunction(options.onerror)) { 23712 options.onerror(); 23713 } else { 23714 throw new Error("onerror callback must be a function"); 23715 } 23716 } 23717 }; 23718 }; 23719 23720 return { 23721 23722 /** 23723 * @class 23724 * FinesseToaster is a utility class to show toaster 23725 * notification in Finesse. FinesseToaster leverages <a 23726 * href="https://www.w3.org/TR/notifications/">HTML5 23727 * Notification</a> API to display Toaster Notification. 23728 * <p> <a 23729 * href="https://developer.mozilla.org/en/docs/Web/API/notification#Browser_compatibility">For 23730 * HTML5 Notification API and browser compatibility, please click 23731 * here.</a></p> 23732 * 23733 * @constructs 23734 */ 23735 _fakeConstuctor : function() { 23736 23737 }, 23738 /** 23739 * TOASTER_DEFAULT_ICONS constants has list of predefined icons (e.g INCOMING_CALL_ICON). 23740 * <p><b>Constant list</b></p> 23741 * <ul> 23742 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CALL_ICON</li> 23743 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CHAT_ICON</li> 23744 * <li>TOASTER_DEFAULT_ICONS.INCOMING_TEAM_MESSAGE</li> 23745 * </ul> 23746 */ 23747 TOASTER_DEFAULT_ICONS : { 23748 INCOMING_CALL_ICON : ICON_PATH + "incoming_call.png", 23749 INCOMING_CHAT_ICON : ICON_PATH + "incoming_chat.png", 23750 INCOMING_TEAM_MESSAGE : ICON_PATH + "incoming_team_message.png" 23751 }, 23752 23753 /** 23754 * <b>showToaster </b>: shows Toaster Notification. 23755 * 23756 * @param {String} 23757 * <b>title</b> : title string should be displayed in the Toaster 23758 * @param {Object} 23759 * options is JSON object for notification options. 23760 * <ul> 23761 * <li><b>options</b> = { </li> 23762 * <li><b>body</b> : The body string of the notification as 23763 * specified in the options parameter of the constructor.</li> 23764 * <li><b>icon</b>: The URL of the image used as an icon of the 23765 * notification as specified in the options parameter of 23766 * the constructor.</li> 23767 * <li><b>autoClose</b> : custom auto close time of the toaster</li> 23768 * <li><b>showWhenVisible</b> : 'true' toaster shows up even when page is 23769 * visible,'false' toaster shows up only when page is invisible </li> 23770 * <li> }</li> 23771 * </ul> 23772 * 23773 * @example 23774 * finesse.containerservices.FinesseToaster.showToaster( 23775 * 'Incoming Alert',{body:'There is new message'} 23776 * ); 23777 * 23778 */ 23779 showToaster : function(title, options) { 23780 23781 if(!_isToasterInitialized){ 23782 throw new Error("FinesseToaster.showToaster() : Finesse toaster is not initialized"); 23783 } 23784 23785 if(_isToasterDisabled){ 23786 _logger.log("FinesseToaster.showToaster() : FinesseToaster is disabled"); 23787 return; 23788 } 23789 23790 var notification, toasterTimer; 23791 23792 // If notifications are granted show the notification 23793 if (window.Notification 23794 && window.Notification.permission === PERMISSION_GRANTED) { 23795 23796 // document.hasFocus() used over document.hidden to keep the consistent behavior across mozilla/chrome 23797 if (document.hasFocus() === false 23798 || options.showWhenVisible) { 23799 if (_logger && AUTO_CLOSE_TIME > -1) { 23800 notification = _createNotification(title, options); 23801 23802 // set the auto close time out of the toaster 23803 toasterTimer = _setAutoClose(notification, 23804 options.autoClose); 23805 23806 // and Toaster Event listeners. eg. onclick , onerror. 23807 _addToasterListeners(notification, options, 23808 toasterTimer); 23809 } 23810 } else { 23811 _logger 23812 .log("FinesseToaster supressed : Page is visible and FineeseToaster.options.showWhenVisible is false"); 23813 } 23814 23815 } 23816 23817 return notification; 23818 }, 23819 23820 /** 23821 * initialize FininseToaster and inject dependencies. this method 23822 * will also request permission in browser from user to display 23823 * Toaster Notification. 23824 * 23825 * @param {Object} 23826 * finesse.container.Config or finesse.gadget.Config object based on where it is getting initialized from. 23827 * 23828 * @param {Object} 23829 * finesse.cslogger.ClientLogger 23830 * @return finesse.containerservices.FinesseToaster 23831 */ 23832 init : function(config, logger) { 23833 23834 _isToasterInitialized = true; 23835 23836 // This is for injecting mocked logger. 23837 if (logger) { 23838 _logger = logger; 23839 } else { 23840 _logger = finesse.cslogger.ClientLogger; 23841 } 23842 23843 //set default toaster notification timeout 23844 if (config && config.toasterNotificationTimeout !== undefined) { 23845 AUTO_CLOSE_TIME = Number(config.toasterNotificationTimeout) * 1000; 23846 23847 if(AUTO_CLOSE_TIME === 0){ 23848 //Finesse toaster has been disabled 23849 _isToasterDisabled = true; 23850 } 23851 } 23852 23853 // Request permission 23854 _requestPermission(); 23855 return finesse.containerservices.FinesseToaster; 23856 } 23857 }; 23858 23859 }()); 23860 23861 window.finesse = window.finesse || {}; 23862 window.finesse.containerservices = window.finesse.containerservices || {}; 23863 window.finesse.containerservices.FinesseToaster = FinesseToaster; 23864 23865 return FinesseToaster; 23866 }); 23867 23868 /** 23869 * This "interface" is just a way to easily jsdoc the Object callback handlers. 23870 * 23871 * @requires finesse.clientservices.ClientServices 23872 * @requires Class 23873 */ 23874 /** @private */ 23875 define('interfaces/RestObjectHandlers',[ 23876 "FinesseBase", 23877 "utilities/Utilities", 23878 "restservices/Notifier", 23879 "clientservices/ClientServices", 23880 "clientservices/Topics" 23881 ], 23882 function () { 23883 23884 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 23885 23886 return { 23887 23888 /** 23889 * @class 23890 * This "interface" defines REST Object callback handlers, passed as an argument to 23891 * Object getter methods in cases where the Object is going to be created. 23892 * 23893 * @param {Object} [handlers] 23894 * An object containing callback handlers for instantiation and runtime 23895 * Callback to invoke upon successful instantiation, passes in REST object. 23896 * @param {Function} [handlers.onLoad(this)] 23897 * Callback to invoke upon loading the data for the first time. 23898 * @param {Function} [handlers.onChange(this)] 23899 * Callback to invoke upon successful update object (PUT) 23900 * @param {Function} [handlers.onAdd(this)] 23901 * Callback to invoke upon successful update to add object (POST) 23902 * @param {Function} [handlers.onDelete(this)] 23903 * Callback to invoke upon successful update to delete object (DELETE) 23904 * @param {Function} [handlers.onError(rsp)] 23905 * Callback to invoke on update error (refresh or event) 23906 * as passed by finesse.restservices.RestBase.restRequest()<br> 23907 * {<br> 23908 * status: {Number} The HTTP status code returned<br> 23909 * content: {String} Raw string of response<br> 23910 * object: {Object} Parsed object of response<br> 23911 * error: {Object} Wrapped exception that was caught<br> 23912 * error.errorType: {String} Type of error that was caught<br> 23913 * error.errorMessage: {String} Message associated with error<br> 23914 * }<br> 23915 * <br> 23916 * Note that RestCollections have two additional callback handlers:<br> 23917 * <br> 23918 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 23919 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 23920 23921 * @constructs 23922 */ 23923 _fakeConstuctor: function () { 23924 /* This is here to enable jsdoc to document this as a class. */ 23925 } 23926 }; 23927 }()); 23928 23929 window.finesse = window.finesse || {}; 23930 window.finesse.interfaces = window.finesse.interfaces || {}; 23931 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 23932 23933 return RestObjectHandlers; 23934 23935 }); 23936 23937 23938 /** 23939 * This "interface" is just a way to easily jsdoc the REST request handlers. 23940 * 23941 * @requires finesse.clientservices.ClientServices 23942 * @requires Class 23943 */ 23944 /** @private */ 23945 define('interfaces/RequestHandlers',[ 23946 "FinesseBase", 23947 "utilities/Utilities", 23948 "restservices/Notifier", 23949 "clientservices/ClientServices", 23950 "clientservices/Topics" 23951 ], 23952 function () { 23953 23954 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 23955 23956 return { 23957 23958 /** 23959 * @class 23960 * This "interface" defines REST Object callback handlers, passed as an argument to 23961 * Object getter methods in cases where the Object is going to be created. 23962 * 23963 * @param {Object} handlers 23964 * An object containing the following (optional) handlers for the request:<ul> 23965 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 23966 * response object as its only parameter:<ul> 23967 * <li><b>status:</b> {Number} The HTTP status code returned</li> 23968 * <li><b>content:</b> {String} Raw string of response</li> 23969 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 23970 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 23971 * error response object as its only parameter:<ul> 23972 * <li><b>status:</b> {Number} The HTTP status code returned</li> 23973 * <li><b>content:</b> {String} Raw string of response</li> 23974 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 23975 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 23976 * <li><b>errorType:</b> {String} Type of error that was caught</li> 23977 * <li><b>errorMessage:</b> {String} Message associated with error</li> 23978 * </ul></li> 23979 * </ul> 23980 23981 * @constructs 23982 */ 23983 _fakeConstuctor: function () { 23984 /* This is here to enable jsdoc to document this as a class. */ 23985 } 23986 }; 23987 }()); 23988 23989 window.finesse = window.finesse || {}; 23990 window.finesse.interfaces = window.finesse.interfaces || {}; 23991 window.finesse.interfaces.RequestHandlers = RequestHandlers; 23992 23993 finesse = finesse || {}; 23994 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 23995 finesse.interfaces = finesse.interfaces || {}; 23996 23997 return RequestHandlers; 23998 23999 }); 24000 24001 24002 24003 define('gadget/Config',[ 24004 "utilities/Utilities" 24005 ], function (Utilities) { 24006 var Config = (function () { /** @lends finesse.gadget.Config.prototype */ 24007 24008 if (gadgets && gadgets.Prefs) { 24009 24010 var _prefs = new gadgets.Prefs(); 24011 24012 return { 24013 /** 24014 * The base64 encoded "id:password" string used for authentication. 24015 * In case of SPOG container, the credentials will not be there in browser session storage so it will be taken from the gadget prefs. 24016 * 24017 * Since 12.5 (CSCvs38506): SPOG will be using 'credentials' as user pref. Keeping 'authorization' for backward compatibility 24018 */ 24019 authorization: Utilities.getUserAuthString() || _prefs.getString("credentials") || _prefs.getString("authorization"), 24020 24021 /** 24022 * The auth token string used for authentication in SSO deployments. 24023 */ 24024 authToken: Utilities.getToken(), 24025 24026 /** 24027 * The country code of the client (derived from locale). 24028 */ 24029 country: _prefs.getString("country"), 24030 24031 /** 24032 * The language code of the client (derived from locale). 24033 */ 24034 language: _prefs.getString("language"), 24035 24036 /** 24037 * The locale of the client. 24038 */ 24039 locale: _prefs.getString("locale"), 24040 24041 /** 24042 * The Finesse server IP/host as reachable from the browser. 24043 */ 24044 host: _prefs.getString("host"), 24045 24046 /** 24047 * The Finesse server host's port reachable from the browser. 24048 */ 24049 hostPort: _prefs.getString("hostPort"), 24050 24051 /** 24052 * The extension of the user. 24053 */ 24054 extension: _prefs.getString("extension"), 24055 24056 /** 24057 * One of the work modes found in {@link finesse.restservices.User.WorkMode}, or something false (undefined) for a normal login. 24058 */ 24059 mobileAgentMode: _prefs.getString("mobileAgentMode"), 24060 24061 /** 24062 * The dial number to use for mobile agent, or something false (undefined) for a normal login. 24063 */ 24064 mobileAgentDialNumber: _prefs.getString("mobileAgentDialNumber"), 24065 24066 /** 24067 * The domain of the XMPP server. 24068 */ 24069 xmppDomain: _prefs.getString("xmppDomain"), 24070 24071 /** 24072 * The pub sub domain where the pub sub service is running. 24073 */ 24074 pubsubDomain: _prefs.getString("pubsubDomain"), 24075 24076 /** 24077 * The Finesse API IP/host as reachable from the gadget container. 24078 */ 24079 restHost: _prefs.getString("restHost"), 24080 24081 /** 24082 * The type of HTTP protocol (http or https). 24083 */ 24084 scheme: _prefs.getString("scheme"), 24085 24086 /** 24087 * The localhost fully qualified domain name. 24088 */ 24089 localhostFQDN: _prefs.getString("localhostFQDN"), 24090 24091 /** 24092 * The localhost port. 24093 */ 24094 localhostPort: _prefs.getString("localhostPort"), 24095 24096 /** 24097 * The id of the team the user belongs to. 24098 */ 24099 teamId: _prefs.getString("teamId"), 24100 24101 /** 24102 * The name of the team the user belongs to. 24103 */ 24104 teamName: _prefs.getString("teamName"), 24105 24106 /** 24107 * The drift time between the client and the server in milliseconds. 24108 */ 24109 clientDriftInMillis: _prefs.getInt("clientDriftInMillis"), 24110 24111 /** 24112 * The client compatibility mode configuration (true if it is or false otherwise). 24113 */ 24114 compatibilityMode: _prefs.getString("compatibilityMode"), 24115 24116 /** 24117 * The peripheral Id that Finesse is connected to. 24118 */ 24119 peripheralId: _prefs.getString("peripheralId"), 24120 24121 /** 24122 * The auth mode of the finesse deployment. 24123 */ 24124 systemAuthMode: _prefs.getString("systemAuthMode"), 24125 24126 24127 /** 24128 * The time for which fineese toaster stay on the browser. 24129 */ 24130 toasterNotificationTimeout: _prefs.getString("toasterNotificationTimeout"), 24131 24132 navItemRoute: _prefs.getString("navItemRoute"), 24133 24134 speechRecognitionHighlights: _prefs.getString("speechRecognitionHighlights"), 24135 /** 24136 * @class 24137 * The Config object for gadgets within the Finesse desktop container which 24138 * contains configuration data provided by the container page. 24139 * @constructs 24140 */ 24141 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 24142 24143 }; 24144 } else { 24145 return {}; 24146 } 24147 }()); 24148 24149 /** Assign to container and gadget namespace to have config available in both */ 24150 window.finesse = window.finesse || {}; 24151 window.finesse.container = window.finesse.container || {}; 24152 window.finesse.container.Config = window.finesse.container.Config || Config; 24153 24154 window.finesse.gadget = window.finesse.gadget || {}; 24155 window.finesse.gadget.Config = Config; 24156 24157 return Config; 24158 }); 24159 /** 24160 * Digital Channel uses a JSON payload for communication with DigitalChannelManager. 24161 * That payload has to conform to this schema. 24162 * This schema has been defined as per http://json-schema.org/ 24163 * 24164 * @see utilities.JsonValidator 24165 */ 24166 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 24167 /*global window:true, define:true*/ 24168 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 24169 define('digital/ChannelSchema',['require','exports','module'],function (require, exports, module) { 24170 24171 var ChannelSchema = (function () { /** @lends finesse.digital.ChannelSchema.prototype */ 24172 24173 var _menuConfigSchema = { 24174 "$schema": "http://json-schema.org/draft-04/schema#", 24175 "type": "object", 24176 "properties": { 24177 "label": { 24178 "type": "string" 24179 }, 24180 "menuItems": { 24181 "type": "array", 24182 "uniqueItems": true, 24183 "items": [{ 24184 "type": "object", 24185 "properties": { 24186 "id": { 24187 "type": "string" 24188 }, 24189 "label": { 24190 "type": "string" 24191 }, 24192 "iconColor": { 24193 "type": "string", 24194 "enum": ["available", "unavailable", "busy"] 24195 } 24196 }, 24197 "required": ["id", "label", "iconColor"], 24198 "additionalProperties": false 24199 }] 24200 } 24201 }, 24202 "required": ["label", "menuItems"], 24203 "additionalProperties": false 24204 }, 24205 24206 _channelConfigSchema = { 24207 "$schema": "http://json-schema.org/draft-04/schema#", 24208 "type": "object", 24209 "properties": { 24210 "actionTimeoutInSec": { 24211 "type": "integer", 24212 "minimum": 1, 24213 "maximum": 30 24214 }, 24215 "icons": { 24216 "type": "array", 24217 "minItems": 1, 24218 "items": [ 24219 { 24220 "type": "object", 24221 "properties": { 24222 "type": { 24223 "type": "string", 24224 "enum": ["collab-icon", "url"] 24225 }, 24226 "value": { 24227 "type": "string" 24228 } 24229 }, 24230 "required": [ 24231 "type", 24232 "value" 24233 ], 24234 "additionalProperties": false 24235 } 24236 ] 24237 } 24238 }, 24239 "required": [ 24240 "actionTimeoutInSec", 24241 "icons" 24242 ], 24243 "additionalProperties": false 24244 }, 24245 24246 _channelStateSchema = { 24247 "$schema": "http://json-schema.org/draft-04/schema#", 24248 "type": "object", 24249 "properties": { 24250 "label": { 24251 "type": "string" 24252 }, 24253 "currentState": { 24254 "type": "string" 24255 }, 24256 "iconColor": { 24257 "type": "string" 24258 }, 24259 "enable": { 24260 "type": "boolean" 24261 }, 24262 "logoutDisabled": { 24263 "type": "boolean" 24264 }, 24265 "logoutDisabledText": { 24266 "type": "string" 24267 }, 24268 "iconBadge": { 24269 "type": "string", 24270 "enum": [ 24271 "error", 24272 "info", 24273 "warning", 24274 "none" 24275 ] 24276 }, 24277 "hoverText": { 24278 "type": "string" 24279 } 24280 }, 24281 "required": [ 24282 "label", 24283 "currentState", 24284 "iconColor", 24285 "enable", 24286 "logoutDisabled", 24287 "iconBadge" 24288 ], 24289 "additionalProperties": false 24290 }; 24291 24292 return { 24293 24294 /** 24295 * @class 24296 * <b>F</b>i<b>n</b>esse digital <b>c</b>hannel state control (referred to as FNC elsewhere in this document) 24297 * is a programmable desktop component that was introduced in Finesse 12.0. 24298 * This API provides the schema that is used in {@link finesse.digital.ChannelService} for various channel operations. 24299 * 24300 * This schema has been defined as per http://json-schema.org/ 24301 * <style> 24302 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 24303 * .schemaTable th { background-color: #999999; } 24304 * .schemaTable th, td { border: none; } 24305 * .pad30 {padding-left: 30px;} 24306 * .inset { 24307 * padding: 5px; 24308 * border-style: inset; 24309 * background-color: #DDDDDD; 24310 * width: auto; 24311 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 24312 * font: 12px arial, sans-serif; 24313 * color:rebeccapurple; 24314 * } 24315 * 24316 * </style> 24317 * 24318 * @example 24319 * <h3 id='cdIcons'>Cisco Common Desktop Stock Icon names with image</h3> 24320 * 24321 * The channel configuration schema has options to take 24322 * Cisco Common Desktop icon (CD-icon) name as value. 24323 * 24324 * To get to know the list of CD-UI icon names and its visual design, 24325 * paste the below JavaScript code in javascript editor part of your 24326 * browser developer console after Finesse login. This script will clear off the 24327 * Finesse web-page and will display icon-name and its rendering in a HTML table. 24328 * To get back to the page, just refresh the browser. 24329 * Note: You can also set this up in a gadget for reference. 24330 * <pre class='inset'> 24331 var showIcons = function () { 24332 $('body').html(''); 24333 24334 $('body').append("<table border='1' background-color:#a0c0a0;'>" 24335 + "<thead style='display: none;'><th>Icon Name</th>" 24336 + "<th>Icon</th></thead><tbody " 24337 + "style='display: block; overflow-y: auto; height: 600px'>" 24338 + "</tbody></table>"); 24339 24340 var icons = window.top.cd.core.cdIcon; 24341 24342 var addIcon = function (name, iconJson) { 24343 24344 var width = (iconJson.width) ? iconJson.width : 1000; 24345 var height = (iconJson.height) ? iconJson.height : 1000; 24346 24347 var iconBuilt = "<tr><td>" + name 24348 + "</td><td><svg width='" + width 24349 + "' height='" + height 24350 + "' style='height: 30px; width: 30px;' viewBox='" 24351 + iconJson.viewBox + "'>" 24352 + iconJson.value + "</svg></td></tr>"; 24353 24354 24355 try { 24356 $('tbody').append(iconBuilt); 24357 } catch (e) { 24358 console.error("Error when adding " + name, e); 24359 } 24360 } 24361 24362 for (var icon in icons) { 24363 if (icons[icon].viewBox) addIcon(icon, icons[icon]) 24364 } 24365 } 24366 24367 showIcons(); 24368 * </pre> 24369 * 24370 * @constructs 24371 */ 24372 _fakeConstuctor: function () { 24373 /* This is here so we can document init() as a method rather than as a constructor. */ 24374 }, 24375 24376 /** 24377 * @example 24378 * <BR><BR><b>Example JSON for <i>MenuConfig</i> data:</b> 24379 * <pre class='inset'> 24380 { 24381 "label" : "Chat", 24382 "menuItems" : 24383 [ 24384 { 24385 "id": "ready-menu-item", 24386 "label": "Ready", 24387 "iconColor": "available" 24388 }, 24389 { 24390 "id": "not-ready-menu-item", 24391 "label": "Not Ready", 24392 "iconColor": "unavailable" 24393 } 24394 ] 24395 } 24396 * </pre> 24397 * @returns 24398 * Schema for validation of the below JSON definition: 24399 * <table class="schemaTable"> 24400 * <thead> 24401 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24402 * </thead> 24403 * <tbody> 24404 * <tr><td>label</td><td>String</td><td>Chat</td><td>This will be the top level menu name for the channel menu</td></tr> 24405 * <tr><td>menuItems</td><td colspan='2'>Array</td><td>These menus will be listed under the top level menu. A single item is defined below</td></tr> 24406 * <tr><td class='pad30'>id</td><td>String</td><td>ready-menu-item</td><td>This id needs to be unique for a channel. 24407 * When there is a user action on the channel menu, this id will be returned back via parameter {@link finesse.digital.ChannelService#selectedMenuItemId}</td></tr> 24408 * <tr><td class='pad30'>label</td><td>String</td><td>Ready</td><td>The text of menu item</td></tr> 24409 * <tr><td class='pad30'>iconColor</td><td>Enum</td><td>available</td><td>available - shows up as green; unavailable - shows up as red;busy - shows up as orange</td></tr> 24410 * </tbody> 24411 * </table> 24412 * 24413 * 24414 */ 24415 getMenuConfigSchema: function () { 24416 return _menuConfigSchema; 24417 }, 24418 24419 /** 24420 * @example 24421 * <BR><BR><b>Example JSON for <i>ChannelConfig</i> data:</b> 24422 * <pre class='inset'> 24423 { 24424 "actionTimeoutInSec": 5, 24425 "icons" : [ 24426 { 24427 "type": "collab-icon", 24428 "value": "Chat" 24429 }, 24430 { 24431 "type": "url", 24432 "value": "../../thirdparty/gadget3/channel-icon.png" 24433 } 24434 ] 24435 } 24436 * </pre> 24437 * @returns 24438 * Schema for validation of the below JSON definition: 24439 * <table class="schemaTable"> 24440 * <thead> 24441 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24442 * </thead> 24443 * <tbody> 24444 * <tr><td>actionTimeoutInSec</td><td>Integer</td><td>5</td><td>Value in seconds. Represents the max time the FNC will wait after sending the menu 24445 * selection request to the gadget. 24446 * Upper limit is 30 seconds. It is recommended that this limit be kept as 24447 * low as possible, since no other operation can be performed 24448 * on FNC during this period</td></tr> 24449 * <tr><td>icons</td><td colspan='2'>Array</td><td>JSON array of icons to be composed and displayed in the Header 24450 * to visually represent a channel. This should ideally never change. 24451 * A single item is defined below</td></tr> 24452 * <tr><td class='pad30'>type</td><td>Enum</td><td>collab-icon</td> 24453 * <td>Takes either <i>collab-icon</i> or <i>url</i> as value. <BR><i>collab-icon</i> would apply a stock icon. Refer to stock icon names over <a href="#cdIcons">here</a> 24454 * <BR><i>url</i> applies a custom icon supplied by gadget. The icon could be located as part of gadget files in Finesse.</td></tr> 24455 * <tr><td class='pad30'>value</td><td>String</td><td>Chat</td><td>The <a href="#cdIcons">stock icon name</a> 24456 * or the url of the custom icon</td></tr> 24457 * </tbody> 24458 * </table> 24459 */ 24460 getChannelConfigSchema: function () { 24461 return _channelConfigSchema; 24462 }, 24463 24464 24465 /** 24466 * @example 24467 * <BR><BR><b>Example JSON for <i>ChannelState</i> data:</b> 24468 * <pre class='inset'> 24469 { 24470 "label" : "Chat & Email", 24471 "currentState" : "ready", 24472 "iconColor" : "available", 24473 "enable" : true, 24474 "logoutDisabled" : true, 24475 "logoutDisabledText" : "Please go unavailable on chat before logout", 24476 "iconBadge" : "none" 24477 "hoverText" : "Tooltip text" 24478 } 24479 * </pre> 24480 * 24481 * @returns 24482 * Schema for validation of the below JSON definition: 24483 * 24484 * <table class="schemaTable"> 24485 * <thead> 24486 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24487 * </thead> 24488 * <tbody> 24489 * <tr><td>label</td><td>String</td><td>Chat & Email</td><td>The name used for the channel on hover</td></tr> 24490 * <tr><td>currentState</td><td>String</td><td>ready</td><td>Text for the current state of the channel. This text will be appended to the label & will be displayed on the channel icon as a hover message</td></tr> 24491 * <tr><td>iconColor</td><td>Enum</td><td>available</td> 24492 * <td>Takes one of these values; available - shows up as green; 24493 * unavailable - shows up as red; busy - shows up as orange. 24494 * The icon colors will be used by the FNC in composing the final icon for the channel, 24495 * that indicates the current state of the channel.</td></tr> 24496 * <tr><td>enable</td><td>boolean</td><td>true</td><td>true indicates the channel is active. false indicates the channel is not active and none of the menu should be shown.</td></tr> 24497 * <tr><td>logoutDisabled</td><td>boolean</td><td>true</td><td>Define as true if the logout menu in the user identity component has to be disabled. 24498 * For e.g. during Agent READY or BUSY state, it makes sense to disable the logout menu and enable it again when the state changes to NOT READY. 24499 * <BR>Note: The true setting in any of the Channel across all Channels will cause the Logout menu to be disabled.</td></tr> 24500 * <tr><td>logoutDisabledText</td><td>String</td><td>Please go unavailable on chat before logout</td> 24501 * <td>The text which will be shown to the user if the logout is disabled</td></tr> 24502 * <tr><td>iconBadge</td><td>Enum</td><td>none</td><td>info - Info badge will be displayed; error - Error badge will be displayed; warning - Warning badge will be displayed; none - No badge will be displayed </td> </tr> 24503 * <tr><td>hoverText</td><td>String</td><td>Tooltip text</td><td>If this is specified it will override the tooltip that shows up by default. Use this to indicate errors or disabled message etc. If the underlying issue is resolved then clear the string </td> /tr> 24504 * </tbody> 24505 * </table> 24506 */ 24507 getChannelStateSchema: function () { 24508 return _channelStateSchema; 24509 } 24510 }; 24511 }()); 24512 24513 window.finesse = window.finesse || {}; 24514 window.finesse.digital = window.finesse.digital || {}; 24515 window.finesse.digital.ChannelSchema = ChannelSchema; 24516 24517 // CommonJS 24518 if (typeof module === "object" && module.exports) { 24519 module.exports = ChannelSchema; 24520 } 24521 24522 return ChannelSchema; 24523 }); 24524 24525 /** 24526 * API for managing Finesse Digital Channels. 24527 */ 24528 /** @private */ 24529 define('digital/ChannelService',['require','exports','module','./ChannelSchema','../utilities/JsonValidator','../utilities/Utilities','../clientservices/ClientServices'],function (require, exports, module) { 24530 24531 var ChannelService = (function () { /** @lends finesse.digital.ChannelService.prototype */ 24532 24533 var SchemaMgr = require('./ChannelSchema'), 24534 SchemaValidator = require('../utilities/JsonValidator'), 24535 Utilities = require('../utilities/Utilities'), 24536 ClientService = require('../clientservices/ClientServices'), 24537 /** 24538 * References to ContainerService 24539 * @private 24540 */ 24541 _containerService, 24542 24543 /** 24544 * Whether the ChannelService have been initialized or not. 24545 * @private 24546 */ 24547 _inited = false, 24548 24549 /** 24550 * References to ClientServices logger 24551 * @private 24552 */ 24553 _logger = { 24554 log: ClientService.log 24555 }, 24556 24557 /** 24558 * Open Ajax topic for the API to publish requests for register, de-register and update on the 24559 * NonVoice Channels. The Finesse NonVoice Component (FNC) will listen to this topic and process 24560 * the request. 24561 * @private 24562 */ 24563 _fncTopic, 24564 24565 /** 24566 * Lists the various channel actions taken by gadget. 24567 * @private 24568 */ 24569 ACTIONS = Object.freeze({ 24570 ADD: 'ADD', 24571 REMOVE: 'REMOVE', 24572 UPDATE: 'UPDATE', 24573 UPDATE_MENU: 'UPDATE_MENU', 24574 UPDATE_CHANNEL_STATE: 'UPDATE_CHANNEL_STATE' 24575 }), 24576 24577 /** 24578 * Operation status enum 24579 * @private 24580 */ 24581 STATUS = Object.freeze({ 24582 SUCCESS: 'success', 24583 FAILURE: 'failure' 24584 }), 24585 24586 /** 24587 * State Status enum 24588 * @private 24589 */ 24590 STATE_STATUS = Object.freeze({ 24591 AVAILABLE: 'available', 24592 UNAVAILABLE: 'unavailable', 24593 BUSY: 'busy' 24594 }), 24595 24596 /** 24597 * Icon Color mapping for state status 24598 */ 24599 ICON_COLOR = Object.freeze({ 24600 available: '#2CB14C', 24601 unavailable: '#CF4237', 24602 busy: '#D79208' 24603 }), 24604 24605 /** 24606 * Channel Icon Type 24607 * @private 24608 */ 24609 ICON_TYPE = Object.freeze({ 24610 COLLAB_ICON: "collab-icon", 24611 URL: "url" 24612 }), 24613 24614 /** 24615 * Icon Badge Type 24616 * @private 24617 */ 24618 BADGE_TYPE = Object.freeze({ 24619 NONE: "none", 24620 INFO: "info", 24621 WARNING: "warning", 24622 ERROR: "error" 24623 }), 24624 24625 /* 24626 * Dynamic registry object, which keeps a map of success and error callbacks for requests whose 24627 * responses are expected asynchronously. Error callback will be invoked on operation timeout. 24628 * @private 24629 */ 24630 _requestCallBackRegistry = (function () { 24631 24632 var OPERATION_TIMEOUT = 60000, 24633 _map = {}, 24634 24635 _clear = function (_uid) { 24636 if (_map[_uid]) { 24637 if (_map[_uid].pendingTimer) { 24638 clearTimeout(_map[_uid].pendingTimer); 24639 } 24640 delete _map[_uid]; 24641 } 24642 }, 24643 24644 _requestTimedOutHandler = function (_uid) { 24645 var err = { 24646 status: 'failure', 24647 error: { 24648 errorCode: '408', 24649 errorDesc: 'Request Timed Out' 24650 } 24651 }; 24652 if (_map[_uid] && typeof _map[_uid].error === 'function') { 24653 _logger.log("ChannelService: Request Timeout. Request Id : " + _uid); 24654 _map[_uid].error(err); 24655 } 24656 _clear(_uid); 24657 }, 24658 24659 _initTimerForTimeout = function (_uid) { 24660 _map[_uid].pendingTimer = setTimeout(function () { 24661 _requestTimedOutHandler(_uid); 24662 }, OPERATION_TIMEOUT); 24663 }, 24664 24665 _add = function (_uid, _chId, _success, _error) { 24666 _map[_uid] = { 24667 channelId: _chId, 24668 success: _success, 24669 error: _error 24670 }; 24671 _initTimerForTimeout(_uid); 24672 }; 24673 24674 return { 24675 register: _add, 24676 clear: _clear, 24677 success: function (uid, data) { 24678 if (_map[uid] && typeof _map[uid].success === 'function') { 24679 data.channelId = _map[uid].channelId; 24680 var response = _map[uid].success(data); 24681 _clear(uid); 24682 return response; 24683 } 24684 }, 24685 error: function (uid, data) { 24686 if (_map[uid] && typeof _map[uid].error === 'function') { 24687 data.channelId = _map[uid].channelId; 24688 var response = _map[uid].error(data); 24689 _clear(uid); 24690 return response; 24691 } 24692 } 24693 }; 24694 }()), 24695 24696 /* 24697 * Dynamic registry object, which keeps a map of channel id and its menu handler function reference. 24698 * This handler will be invoked on user interaction with its channel menu. 24699 * @private 24700 */ 24701 _menuHandlerRegistry = (function () { 24702 var _map = {}, 24703 24704 _add = function (_id, _chId, _menuHandler) { 24705 _map[_id] = { 24706 channelId: _chId, 24707 menuHandler: _menuHandler 24708 }; 24709 }, 24710 24711 _clear = function (_id) { 24712 if (_map[_id]) { 24713 delete _map[_id]; 24714 } 24715 }; 24716 24717 return { 24718 register: _add, 24719 clear: _clear, 24720 menuSelection: function (_id, _menuItemId, _success, _error) { 24721 if (_map[_id] && _map[_id].menuHandler) { 24722 return _map[_id].menuHandler(_map[_id].channelId, _menuItemId, 24723 _success, _error); 24724 } 24725 } 24726 }; 24727 }()), 24728 24729 /** 24730 * Ensure that ChannelService have been inited. 24731 * @private 24732 */ 24733 _isInited = function () { 24734 if (!_inited) { 24735 throw new Error("ChannelService needs to be inited."); 24736 } 24737 }, 24738 24739 /** 24740 * Gets the id of the gadget. 24741 * @returns {number} the id of the gadget 24742 * @private 24743 */ 24744 _getMyGadgetId = function () { 24745 var gadgetId = _containerService.getMyGadgetId(); 24746 return gadgetId || ''; 24747 }, 24748 24749 /** 24750 * Validate the menuConfig structure in channelData payload. 24751 * 24752 * @param {Object} data 24753 * menuConfig structure. 24754 * @throws {Error} if the data is not in menuConfig defined format. 24755 * @private 24756 */ 24757 _validateMenuConfig = function (data) { 24758 var result = SchemaValidator.validateJson(data, SchemaMgr.getMenuConfigSchema()); 24759 /* if result.valid is false, then additional details about failure are contained in 24760 result.error which contains json like below: 24761 24762 { 24763 "code": 0, 24764 "message": "Invalid type: string", 24765 "dataPath": "/intKey", 24766 "schemaPath": "/properties/intKey/type" 24767 } 24768 */ 24769 if (!result.valid) { 24770 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 24771 throw new Error("menuConfig structure is not in expected format. Refer finesse client logs for more details."); 24772 } 24773 }, 24774 24775 /** 24776 * Validate the channelConfig structure in channelData payload. 24777 * 24778 * @param {Object} data 24779 * channelConfig structure. 24780 * @throws {Error} if the data is not in channelConfig defined format. 24781 * @private 24782 */ 24783 _validateChannelConfig = function (data) { 24784 var result = SchemaValidator.validateJson(data, SchemaMgr.getChannelConfigSchema()); 24785 if (!result.valid) { 24786 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 24787 throw new Error("channelConfig structure is not in expected format. Refer finesse client logs for more details."); 24788 } 24789 }, 24790 24791 /** 24792 * Validate the channelState structure in channelData payload. 24793 * 24794 * @param {Object} data 24795 * channelState structure. 24796 * @throws {Error} if the data is not in channelState defined format. 24797 * @private 24798 */ 24799 _validateChannelState = function (data) { 24800 var result = SchemaValidator.validateJson(data, SchemaMgr.getChannelStateSchema()); 24801 if (!result.valid) { 24802 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 24803 throw new Error("channelState structure is not in expected format. Refer finesse client logs for more details."); 24804 } 24805 }, 24806 24807 /** 24808 * Validate the entire channelData structure in payload. 24809 * 24810 * @param {Object} data 24811 * channelData structure. 24812 * @throws {Error} if the data is not in channelData defined format. 24813 * @private 24814 */ 24815 _validateAddChannelPayload = function (data) { 24816 var err = ""; 24817 if (!data.hasOwnProperty("menuConfig")) { 24818 err = "menuConfig property missing in Channel Data"; 24819 } else if (!data.hasOwnProperty("channelConfig")) { 24820 err = "channelConfig property missing in Channel Data"; 24821 } else if (!data.hasOwnProperty("channelState")) { 24822 err = "channelState property missing in Channel Data"; 24823 } 24824 24825 if (err) { 24826 throw new Error(err); 24827 } 24828 _validateMenuConfig(data.menuConfig); 24829 _validateChannelConfig(data.channelConfig); 24830 _validateChannelState(data.channelState); 24831 }, 24832 24833 /** 24834 * Validate the available structure in payload. 24835 * 24836 * @param {Object} data 24837 * channelData structure. 24838 * @throws {Error} if the data is not in channelData defined format. 24839 * @private 24840 */ 24841 _validateUpdateChannelPayload = function (data) { 24842 if (data.hasOwnProperty("menuConfig")) { 24843 _validateMenuConfig(data.menuConfig); 24844 } 24845 if (data.hasOwnProperty("channelConfig")) { 24846 _validateChannelConfig(data.channelConfig); 24847 } 24848 if (data.hasOwnProperty("channelState")) { 24849 _validateChannelState(data.channelState); 24850 } 24851 }, 24852 24853 /** 24854 * Validate the gadget passed JSON structure against the schema definition. 24855 * 24856 * @param {Object} data 24857 * channelData structure. 24858 * @throws {Error} if the data is not in channelData defined format. 24859 * @private 24860 */ 24861 _validateData = function (data, action) { 24862 switch (action) { 24863 case ACTIONS.ADD: 24864 _validateAddChannelPayload(data); 24865 break; 24866 case ACTIONS.UPDATE: 24867 _validateUpdateChannelPayload(data); 24868 break; 24869 case ACTIONS.UPDATE_CHANNEL_STATE: 24870 _validateChannelState(data); 24871 break; 24872 case ACTIONS.UPDATE_MENU: 24873 _validateMenuConfig(data); 24874 break; 24875 } 24876 }, 24877 24878 /** 24879 * Prepares the FNC required payload based on the action and the supplied JSON data. 24880 * 24881 * @param {Object} data 24882 * json structure used for channel request. 24883 * @returns {Object} data in FNC required payload format. 24884 * @private 24885 */ 24886 _prepareFNCChannelData = function (data, action) { 24887 switch (action) { 24888 case ACTIONS.ADD: 24889 if (data.hasOwnProperty("channelState")) { 24890 data.channelState.iconColor = ICON_COLOR[data.channelState.iconColor]; 24891 } 24892 data.menuConfig.menuItems.forEach(function (item) { 24893 item.iconColor = ICON_COLOR[item.iconColor]; 24894 }); 24895 break; 24896 case ACTIONS.UPDATE: 24897 if (data.hasOwnProperty("channelState")) { 24898 data.channelState.iconColor = ICON_COLOR[data.channelState.iconColor]; 24899 } 24900 if (data.hasOwnProperty("menuConfig")) { 24901 data.menuConfig.menuItems.forEach(function (item) { 24902 item.iconColor = ICON_COLOR[item.iconColor]; 24903 }); 24904 } 24905 break; 24906 case ACTIONS.UPDATE_CHANNEL_STATE: 24907 data.iconColor = ICON_COLOR[data.iconColor]; 24908 data = { 24909 channelState: data 24910 }; 24911 break; 24912 case ACTIONS.UPDATE_MENU: 24913 data.menuItems.forEach(function (item) { 24914 item.iconColor = ICON_COLOR[item.iconColor]; 24915 }); 24916 data = { 24917 menuConfig: data 24918 }; 24919 break; 24920 } 24921 if(data.channelState){ 24922 data.channelState.isAgentReady = data.channelState.iconColor === ICON_COLOR.available; 24923 } 24924 24925 return data; 24926 }, 24927 24928 /** 24929 * Utility function to make a subscription to a particular topic. Only one 24930 * callback function is registered to a particular topic at any time. 24931 * 24932 * @param {String} topic 24933 * The full topic name. The topic name should follow the OpenAjax 24934 * convention using dot notation (ex: finesse.api.User.1000). 24935 * @param {Function} handler 24936 * The function that should be invoked with the data when an event 24937 * is delivered to the specific topic. 24938 * @returns {Boolean} 24939 * True if the subscription was made successfully and the callback was 24940 * been registered. False if the subscription already exist, the 24941 * callback was not overwritten. 24942 * @private 24943 */ 24944 _subscribe = function (topic, handler) { 24945 try { 24946 return _containerService.addHandler(topic, handler); 24947 } catch (error) { 24948 _logger.log('ChannelService: Error while subscribing to open ajax topic : ' + topic + ', Exception:' + error.toString()); 24949 throw new Error('ChannelService: Unable to subscribe the channel topic with Open Ajax Hub : ' + error); 24950 } 24951 }, 24952 24953 /** 24954 * Function which processes the menu selection request from FNC. 24955 * 24956 * @param {Object} payload 24957 * The payload containing the menu selection request details. 24958 * @private 24959 */ 24960 _processMenuChangeRequest = function (payload) { 24961 _menuHandlerRegistry.menuSelection(payload.id, payload.selection, function (successPayload) { 24962 var response = { 24963 id: payload.id, 24964 requestId: payload.requestId, 24965 status: successPayload.hasOwnProperty('status') ? successPayload.status : STATUS.SUCCESS 24966 }; 24967 _containerService.publish(_fncTopic, response); 24968 }, function (failurePayload) { 24969 var response = { 24970 id: payload.id, 24971 requestId: payload.requestId, 24972 status: failurePayload.hasOwnProperty('status') ? failurePayload.status : STATUS.FAILURE 24973 }; 24974 if (failurePayload.hasOwnProperty('error')) { 24975 response.error = failurePayload.error; 24976 } 24977 _containerService.publish(_fncTopic, response); 24978 }); 24979 }, 24980 24981 _processChannelOperationResult = function (payload) { 24982 var response = { 24983 status: payload.status 24984 }; 24985 if (payload.status === STATUS.FAILURE) { 24986 // error response 24987 if (payload.hasOwnProperty('error')) { 24988 response.error = payload.error; 24989 } 24990 _logger.log('ChannelService: Failure response for requestId: ' + payload.requestId); 24991 _requestCallBackRegistry.error(payload.requestId, response); 24992 } else { 24993 // success response 24994 _requestCallBackRegistry.success(payload.requestId, response); 24995 } 24996 }, 24997 24998 /** 24999 * Function which processes the messages from Finesse NonVoice Component. 25000 * The messages received mainly for below cases, 25001 * <ul> 25002 * <li> Operation responses for ADD, REMOVE and UPDATE. 25003 * <li> User menu selection request. 25004 * <ul> 25005 * The registered callbacks are invoked based on the data. 25006 * 25007 * @param {String} payload 25008 * The actual message sent by FNC via open ajax hub. 25009 * @private 25010 */ 25011 _processFNCMessage = function (payload) { 25012 _logger.log('ChannelService: Received Message from FNC: ' + JSON.stringify(payload)); 25013 try { 25014 // if the received data from topic is in text format, then parse it to JSON format 25015 payload = typeof payload === 'string' ? JSON.parse(payload) : payload; 25016 } catch (error) { 25017 _logger.log('ChannelService: Error while parsing the FNC message' + payload); 25018 return; 25019 } 25020 25021 if (payload.hasOwnProperty('selection')) { 25022 // menu selection request from FNC 25023 _processMenuChangeRequest(payload); 25024 } else { 25025 // ADD or REMOVE or UPDATE operation status from FNC 25026 _processChannelOperationResult(payload); 25027 } 25028 }, 25029 25030 /* 25031 * Validate the data and process the channel API request based on the action. 25032 * 25033 * @param {String} channelId 25034 * Digtial channel id, should be unique within the gadget. 25035 * @param {Actions Enum} action 25036 * Any one of the channel action defined in ACTIONS enum. 25037 * @param {Function} success 25038 * Callback function invoked upon request successful. 25039 * @param {Function} error 25040 * Callback function invoked upon request errored. 25041 * @param {JSON} data 25042 * ADD or UPDATE operation data object as per the defined format. 25043 * @param {Function} menuHandler 25044 * Callback function invoked when the user interacts with the registered channel menu. 25045 * @throws Error 25046 * Throws error when the passed in data is not in defined format. 25047 * @private 25048 */ 25049 _processChannelRequest = function (channelId, action, success, error, data, menuHandler) { 25050 _logger.log('ChannelService: Received digital channel request. ChannelId ' + channelId + ', Action ' + action + ', Data ' + JSON.stringify(data)); 25051 25052 _isInited(); 25053 25054 var id = _getMyGadgetId() + '/' + channelId, 25055 topic = _fncTopic + '.' + id, 25056 requestUID, payload; 25057 25058 if (action === ACTIONS.REMOVE) { 25059 // clear the menuHandler for this channel from menu registry 25060 _menuHandlerRegistry.clear(id); 25061 25062 // remove the hub listener for the channel topic 25063 _containerService.removeHandler(topic, _processFNCMessage); 25064 } else { 25065 if (!data) { 25066 throw new Error("ChannelService: Channel Data cannot be empty."); 25067 } 25068 try { 25069 data = typeof data === 'string' ? JSON.parse(data) : data; 25070 } catch (err) { 25071 throw new Error("ChannelService: Data Parsing Failed. ADD or UPDTAE payload not in expected format. Error: " + err.toString); 25072 } 25073 25074 // validate the data 25075 _validateData(data, action); 25076 25077 // prepare FNC payload 25078 data = _prepareFNCChannelData(data, action); 25079 25080 if (action === ACTIONS.ADD) { 25081 // onMenuClick must be supplied and of type function 25082 if (!menuHandler || typeof menuHandler !== 'function') { 25083 throw new Error("ChannelService: onMenuClick parameter in addChannel() must be a function."); 25084 } 25085 25086 // register the menuHandler for this channel with menu registry for later use 25087 _menuHandlerRegistry.register(id, channelId, menuHandler); 25088 25089 // subscribe this channel topic for messages from Finesse NV Header Component 25090 _subscribe(topic, _processFNCMessage); 25091 } 25092 25093 // derive the FNC action 25094 action = action !== ACTIONS.ADD ? ACTIONS.UPDATE : action; 25095 } 25096 25097 25098 requestUID = Utilities.generateUUID(); 25099 payload = { 25100 id: id, 25101 topic: topic, 25102 requestId: requestUID, 25103 action: action, 25104 data: data 25105 }; 25106 25107 /* 25108 * register the request with request registry for asynchronously 25109 * invoking success and failure callbacks about the operation 25110 * status. 25111 */ 25112 _requestCallBackRegistry.register(requestUID, channelId, success, error); 25113 25114 _logger.log('ChannelService: Sending channel request to FNC: ' + JSON.stringify(payload)); 25115 25116 _containerService.publish(_fncTopic, payload); 25117 }; 25118 25119 return { 25120 25121 25122 25123 25124 /** 25125 * @class 25126 * <b>F</b>i<b>n</b>esse digital <b>c</b>hannel state control (referred to as FNC elsewhere in this document) 25127 * is a programmable desktop component that was introduced in Finesse 12.0. 25128 * The ChannelService API provides hooks to FNC that can be leveraged by gadget hosting channels. 25129 * 25130 * <h3> FNC API </h3> 25131 * 25132 * The FNC API provides methods that can be leveraged by gadgets serving channels to 25133 * register, update or modify channel specific display information and corresponding menu action behavior 25134 * in Agent State Control Menu (referred to as FNC Menu component). 25135 * 25136 * <BR> 25137 * These APIs are available natively to the gadget through the finesse.js import. Examples of how to write a sample gadget 25138 * can be found <a href="https://github.com/CiscoDevNet/finesse-sample-code/tree/master/LearningSampleGadget">here</a>. 25139 * <BR> 25140 * The FNC API is encapsulated in a module named ChannelService within finesse.js and it can be accessed 25141 * via the namespace <i><b>finesse.digital.ChannelService</b></i>. 25142 * 25143 * <style> 25144 * .channelTable tr:nth-child(even) { background-color:#EEEEEE; } 25145 * .channelTable th { background-color: #999999; } 25146 * .channelTable th, td { border: none; } 25147 * .pad30 {padding-left: 30px;} 25148 * .inset { 25149 * padding: 5px; 25150 * border-style: inset; 25151 * background-color: #DDDDDD; 25152 * width: auto; 25153 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 25154 * font: 12px arial, sans-serif; 25155 * color:rebeccapurple; 25156 * } 25157 * 25158 * </style> 25159 * 25160 * @example 25161 * 25162 * <h3> Example demonstrating initialization </h3> 25163 * 25164 * <pre class='inset'> 25165 containerServices = finesse.containerservices.ContainerServices.init(); 25166 channelService = finesse.digital.ChannelService.init(containerServices); 25167 channelService.addChannel(channelId, channelData, onMenuClick, onSuccess, onError); 25168 * </pre> 25169 25170 * @constructs 25171 */ 25172 _fakeConstuctor: function () { 25173 /* This is here so we can document init() as a method rather than as a constructor. */ 25174 }, 25175 25176 /** 25177 * Initialize ChannelService for use in gadget. 25178 * 25179 * @param {finesse.containerservices.ContainerServices} ContainerServices instance. 25180 * @returns {finesse.digital.ChannelService} instance. 25181 */ 25182 init: function (containerServices) { 25183 if (!_inited) { 25184 if (!containerServices) { 25185 throw new Error("ChannelService: Invalid ContainerService reference."); 25186 } 25187 _inited = true; 25188 _containerService = containerServices; 25189 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 25190 _fncTopic = containerServices.Topics.FINEXT_NON_VOICE_GADGET_EVENT; 25191 } 25192 return this; 25193 }, 25194 25195 /** 25196 * 25197 * This API starts the gadget interaction with FNC by adding the channel to the FNC Menu component. 25198 * The complete channel state is required in the form of JSON payload as part of this API. 25199 * It is advised developers pre-validate the JSON that is passed as parameter against its corresponding schema 25200 * by testing it through {@link finesse.utilities.JsonValidator.validateJson}. 25201 * The result of the add operation will be returned via the given success or error callback. 25202 * 25203 * @param {String} channelId 25204 * Digital channel id, should be unique within the gadget. 25205 * The API developer can supply any string that uniquely identifies the Digital Channel for registration with FNC. 25206 * This id would be returned in callbacks by FNC when there is a user action on menus that belongs to a channel. 25207 * @param {Object} channelData 25208 * It is a composition of the following KEY-VALUE pair as JSON. The value part would be the object that conforms to 'key' specific schema. 25209 * <table class="channelTable"> 25210 * <thead><tr><th>key</th><th>Value</th></tr><thead> 25211 * <tbody> 25212 * <tr><td>menuconfig</td><td>Refer {@link finesse.digital.ChannelSchema#getMenuConfigSchema} for description about this JSON payload</td></tr> 25213 * <tr><td>channelConfig</td><td>Refer {@link finesse.digital.ChannelSchema#getChannelConfigSchema} for description about this JSON payload</td></tr> 25214 * <tr><td>channelState</td><td>Refer {@link finesse.digital.ChannelSchema#getChannelStateSchema} for description about this JSON payload</td></tr> 25215 * </tbody> 25216 * </table> 25217 * 25218 * @param onMenuClick 25219 * Handler that is provided by the Gadget to the API during channel addition. 25220 * It is invoked whenever the user clicks a menu item on the FNC control. 25221 * <table class="channelTable"> 25222 * <thead><tr><th>Parameter</th><th>Description</th></tr></thead> 25223 * <tbody> 25224 * <tr><td><h4 id="selectedMenuItemId">selectedMenuItemId</h4></td><td>The selectedMenuItemId will contain the menuId as defined in 25225 * menuConfig {@link finesse.digital.ChannelSchema#getMenuConfigSchema} payload. 25226 * The gadget has to invoke onSuccess callback, if the state change is success or 25227 * onError callback if there is a failure performing state change operation 25228 * (refer actionTimeoutInSec in JSON payload for timeout) on the underlying channel service.</td></tr> 25229 * <tr><td><h5 id="success">onSuccess<h5></td><td>The success payload would be of the following format: 25230 * <pre> 25231 * { 25232 * "channelId" : "[ID of the Digital channel]", 25233 * "status" : "success" 25234 * } 25235 * </pre></td></tr> 25236 * <tr><td><h5 id="failure">onError<h5></td><td>The error payload would be of the following format: 25237 * <pre> 25238 * { 25239 * "channelId" : "[ID of the Digital channel]", 25240 * "status" : "failure", 25241 * "error" : { 25242 * "errorCode": "[Channel supplied error code that will be logged in Finesse client logs]", 25243 * "errorDesc": "An error occurred while processing request" 25244 * } 25245 * } 25246 * </pre></td></tr> 25247 * </tbody></table> 25248 * @param onSuccess 25249 * Callback function invoked upon add operation successful. Refer above for the returned JSON payload. 25250 * @param onError 25251 * Callback function invoked upon add operation is unsuccessful. Refer above for the returned JSON payload. 25252 * @throws 25253 * Throws error when the passed in channelData is not as per schema. 25254 */ 25255 addChannel: function (channelId, channelData, onMenuClick, onSuccess, onError) { 25256 _processChannelRequest(channelId, ACTIONS.ADD, onSuccess, onError, channelData, onMenuClick); 25257 }, 25258 25259 /** 25260 * Removes a channel representation from the FNC Menu component. 25261 * The result of the remove operation will be intimated via the given success and error callbacks. 25262 * 25263 * @param {String} channelId 25264 * Digtial channel id, should be unique within the gadget. 25265 * This id would be returned in the callbacks. 25266 * @param {Function} onSuccess 25267 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 25268 * @param {Function} onError 25269 * <a href="#failure">onError</a> function that is invoked for failed operation. 25270 */ 25271 removeChannel: function (channelId, onSuccess, onError) { 25272 _processChannelRequest(channelId, ACTIONS.REMOVE, onSuccess, onError); 25273 }, 25274 25275 /** 25276 * Updates the channels representation in FNC Menu component. 25277 * None of the data passed within the data payload <i>channelData</i> is mandatory. 25278 * This API provides an easy way to update the complete channel configuration together in one go or partially if required. 25279 * The result of the update operation will be intimated via the given success and error callbacks. 25280 * 25281 * @param {String} channelId 25282 * Digtial channel id, should be unique within the gadget 25283 * This id would be returned in the callbacks. 25284 * @param {Object} channelData 25285 * Channel data JSON object as per the spec. Refer {@link #addChannel} for this object description. Partial data sections allowed. 25286 * @param {Function} onSuccess 25287 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 25288 * @param {Function} onError 25289 * <a href="#failure">onError</a> function that is invoked for failed operation. 25290 */ 25291 updateChannel: function (channelId, channelData, onSuccess, onError) { 25292 _processChannelRequest(channelId, ACTIONS.UPDATE, onSuccess, onError, channelData); 25293 }, 25294 25295 /** 25296 * Updates the menu displayed against a channel. 25297 * The result of the update operation will be intimated via the given success and error callbacks. 25298 * 25299 * @param {String} channelId 25300 * Digtial channel id, should be unique within the gadget. 25301 * This id would be returned in the callbacks. 25302 * @param {menuItem[]} menuItems 25303 * Refer {@link finesse.digital.ChannelSchema#getMenuConfigSchema} for menuItem definition. 25304 * @param {Function} onSuccess 25305 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 25306 * @param {Function} onError 25307 * <a href="#failure">onError</a> function that is invoked for failed operation. 25308 */ 25309 updateChannelMenu: function (channelId, menuConfig, onSuccess, onError) { 25310 _processChannelRequest(channelId, ACTIONS.UPDATE_MENU, onSuccess, onError, menuConfig); 25311 }, 25312 25313 /** 25314 * Updates the channels current state. The result of 25315 * the update operation will be intimated via the given success and error callbacks. 25316 * 25317 * @param {String} channelId 25318 * Digtial channel id, should be unique within the gadget. 25319 * This id would be returned in the callbacks. 25320 * @param {Object} channelState 25321 * Refer {@link finesse.digital.ChannelSchema#getChannelStateSchema} for channel state definition. 25322 * @param {Function} onSuccess 25323 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 25324 * @param {Function} onError 25325 * <a href="#failure">onError</a> function that is invoked for failed operation. 25326 */ 25327 updateChannelState: function (channelId, channelState, onSuccess, onError) { 25328 _processChannelRequest(channelId, ACTIONS.UPDATE_CHANNEL_STATE, onSuccess, onError, channelState); 25329 }, 25330 25331 /** 25332 * ENUM: Operation status. 25333 * [SUCCESS, FAILURE] 25334 */ 25335 STATUS: STATUS, 25336 25337 /** 25338 * ENUM: State Status indicates the icon color in channel. 25339 * [AVAILABLE, UNAVAILABLE, BUSY]*/ 25340 STATE_STATUS: STATE_STATUS, 25341 25342 /** 25343 * ENUM: Channel Icon location type. 25344 * [COLLAB_ICON, URL] 25345 */ 25346 ICON_TYPE: ICON_TYPE, 25347 25348 /** 25349 * ENUM: Icon Badge Type. 25350 * [NONE, INFO, WARNING, ERROR] 25351 */ 25352 ICON_BADGE_TYPE: BADGE_TYPE 25353 }; 25354 }()); 25355 25356 25357 /** @namespace Namespace for JavaScript class objects and methods related to Digital Channel management.*/ 25358 finesse.digital = finesse.digital || {}; 25359 25360 window.finesse = window.finesse || {}; 25361 window.finesse.digital = window.finesse.digital || {}; 25362 window.finesse.digital.ChannelService = ChannelService; 25363 25364 // CommonJS 25365 if (typeof module === "object" && module.exports) { 25366 module.exports = ChannelService; 25367 } 25368 25369 25370 return ChannelService; 25371 }); 25372 25373 /** 25374 * Popover service uses a JSON payload for communication from gadget. 25375 * The aforesaid payload has to conform to this schema. 25376 * This schema has been defined as per <a href='http://json-schema.org/'> JSON schema </a> standard. 25377 * 25378 * @see utilities.JsonValidator 25379 */ 25380 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 25381 /*global window:true, define:true*/ 25382 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 25383 define('containerservices/PopoverSchema',['require','exports','module'],function (require, exports, module) { 25384 25385 var PopoverSchema = (function () { /** @lends finesse.containerservices.PopoverSchema.prototype */ 25386 25387 var _bannerDataSchema = { 25388 "$schema": "http://json-schema.org/draft-04/schema#", 25389 "type": "object", 25390 "properties": { 25391 "icon": { 25392 "type": "object", 25393 "properties": { 25394 "type": { 25395 "type": "string", 25396 "enum": ["collab-icon", "url"] 25397 }, 25398 "value": { 25399 "type": "string" 25400 } 25401 }, 25402 "required": [ 25403 "type", 25404 "value" 25405 ] 25406 }, 25407 "content": { 25408 "type": "array", 25409 "items": [{ 25410 "type": "object", 25411 "properties": { 25412 "name": { 25413 "type": "string" 25414 }, 25415 "value": { 25416 "type": "string" 25417 } 25418 }, 25419 "required": [ 25420 "name", 25421 "value" 25422 ] 25423 }] 25424 }, 25425 "headerContent": { 25426 "type": "object", 25427 "properties": { 25428 "maxHeaderTitle": { 25429 "type": "string" 25430 }, 25431 "minHeaderTitle": { 25432 "type": "string" 25433 } 25434 } 25435 } 25436 }, 25437 "required": [ 25438 "icon" 25439 ], 25440 "additionalProperties": false 25441 }, 25442 25443 _timeDataSchema = { 25444 "$schema": "http://json-schema.org/draft-04/schema#", 25445 "type": "object", 25446 "properties": { 25447 "displayTimeoutInSecs": { 25448 "type": "integer", 25449 "oneOf": [ 25450 { 25451 "minimum": 3, 25452 "maximum": 3600 25453 }, 25454 { 25455 "enum": [-1] /* displayTimeoutInSecs can be in range 3 and 3600, or -1. -1 indicates there is no upper limit for timer */ 25456 } 25457 ] 25458 }, 25459 "display": { 25460 "type": "boolean" 25461 }, 25462 "counterType": { 25463 "type": "string", 25464 "enum": ["COUNT_UP", "COUNT_DOWN"] 25465 } 25466 }, 25467 "oneOf": [{ 25468 "properties": { 25469 "display": { 25470 "type": "boolean", 25471 "enum": [ 25472 false 25473 ] 25474 } 25475 }, 25476 "required": [ 25477 "displayTimeoutInSecs", 25478 "display" 25479 ], 25480 "not": { 25481 "required": ["counterType"] 25482 }, 25483 }, 25484 { 25485 "properties": { 25486 "display": { 25487 "type": "boolean", 25488 "enum": [ 25489 true 25490 ] 25491 } 25492 }, 25493 "required": [ 25494 "displayTimeoutInSecs", 25495 "display", 25496 "counterType" 25497 ] 25498 } 25499 ], 25500 "additionalProperties": false 25501 }, 25502 25503 _actionDataSchema = { 25504 "$schema": "http://json-schema.org/draft-04/schema#", 25505 "type": "object", 25506 "properties": { 25507 "keepMaximised": { 25508 "type": "boolean" 25509 }, 25510 "dismissible": { 25511 "type": "boolean" 25512 }, 25513 "clientIdentifier": { 25514 "type": "string" 25515 }, 25516 "requiredActionText": { 25517 "type": "string" 25518 }, 25519 "buttons": { 25520 "type": "array", 25521 "minItems": 1, 25522 "maxItems": 2, 25523 "uniqueItems": true, 25524 "items": [{ 25525 "type": "object", 25526 "properties": { 25527 "id": { 25528 "type": "string" 25529 }, 25530 "label": { 25531 "type": "string" 25532 }, 25533 "type": { 25534 "type": "string" 25535 }, 25536 "hoverText": { 25537 "type": "string" 25538 }, 25539 "confirmButtons": { 25540 "type": "array", 25541 "uniqueItems": true, 25542 "items": [{ 25543 "type": "object", 25544 "properties": { 25545 "id": { 25546 "type": "string" 25547 }, 25548 "label": { 25549 "type": "string" 25550 }, 25551 "hoverText": { 25552 "type": "string" 25553 } 25554 }, 25555 "required": [ 25556 "id", 25557 "label" 25558 ] 25559 }] 25560 } 25561 }, 25562 "required": [ 25563 "id", 25564 "label", 25565 "type" 25566 ] 25567 }] 25568 } 25569 }, 25570 "oneOf": [{ 25571 "additionalProperties": false, 25572 "properties": { 25573 "requiredActionText": { 25574 "type": "string" 25575 }, 25576 "clientIdentifier": { 25577 "type": "string" 25578 }, 25579 "dismissible": { 25580 "type": "boolean", 25581 "enum": [ 25582 false 25583 ] 25584 }, 25585 "keepMaximised": { 25586 "type": "boolean" 25587 }, 25588 "buttons": { 25589 "type": "array" 25590 } 25591 } 25592 }, 25593 { 25594 "additionalProperties": false, 25595 "properties": { 25596 "requiredActionText": { 25597 "type": "string" 25598 }, 25599 "clientIdentifier": { 25600 "type": "string" 25601 }, 25602 "dismissible": { 25603 "type": "boolean", 25604 "enum": [ 25605 true 25606 ] 25607 }, 25608 "keepMaximised": { 25609 "type": "boolean" 25610 } 25611 } 25612 } 25613 ], 25614 "required": [ 25615 "dismissible" 25616 ], 25617 "additionalProperties": false 25618 }, 25619 25620 _headerDataSchema = { 25621 "$schema": "http://json-schema.org/draft-04/schema#", 25622 "type": "object", 25623 "properties": { 25624 "maxHeaderTitle": { 25625 "type": "string" 25626 }, 25627 "minHeaderTitle": { 25628 "type": "string" 25629 } 25630 }, 25631 "additionalProperties": false 25632 }; 25633 25634 return { 25635 25636 /** 25637 * @class 25638 * Finesse Voice component and Gadget(s) hosting digital services require 25639 * the {@link finesse.containerservices.PopoverService} to display a popup 25640 * for incoming call and chat events. <BR> 25641 * This API provides the schema that is used in {@link finesse.containerservices.PopoverService} 25642 * for managing various operations in the popup. 25643 * 25644 * This schema has been defined as per http://json-schema.org/ 25645 * <style> 25646 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 25647 * .schemaTable th { background-color: #999999; } 25648 * .schemaTable th, td { border: none; } 25649 * .pad30 {padding-left: 30px;} 25650 * .pad60 {padding-left: 60px;} 25651 * .inset { 25652 * padding: 5px; 25653 * border-style: inset; 25654 * background-color: #DDDDDD; 25655 * width: auto; 25656 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 25657 * font: 12px arial, sans-serif; 25658 * color:rebeccapurple; 25659 * } 25660 * </style> 25661 * 25662 * @constructs 25663 */ 25664 _fakeConstuctor: function () { 25665 /* This is here so we can document init() as a method rather than as a constructor. */ 25666 }, 25667 25668 /** 25669 * 25670 * @example 25671 * <BR><BR><b>Example JSON for <i>BannerData</i>:</b> 25672 * <pre class='inset'> 25673 { 25674 "icon": { // Mandatory 25675 "type": "collab-icon", 25676 "value": "chat" 25677 }, 25678 "content": [ // Optional. first 6 name/value pairs is shown in popover 25679 { 25680 "name": "Customer Name", 25681 "value": "Michael Littlefoot" 25682 }, 25683 { 25684 "name": "Phone Number", 25685 "value": "+1-408-567-789" 25686 }, 25687 { 25688 "name": "Account Number", 25689 "value": "23874567923" 25690 }, 25691 { 25692 "name": "Issue", // For the below one, tool tip is displayed 25693 "value": "a very long text. a very long text.a very long text.a very long text.a very long text." 25694 } 25695 ] 25696 "headerContent" : { 25697 "maxHeaderTitle" : "Popover maximised title", 25698 "minHeaderTitle" : "Popover minimized title" 25699 } 25700 } 25701 * </pre> 25702 * 25703 * @returns 25704 * Schema for the validation of the below JSON definition 25705 * <table class="schemaTable"> 25706 * <thead> 25707 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 25708 * </thead> 25709 * <tbody> 25710 * <tr><td>icon</td><td colspan='2'>Object</td><td>A icon that should be displayed in the Popover. It should be defined as below:</td></tr> 25711 * <tr><td class='pad30'>type</td><td>Enum</td><td>collab-icon</td> 25712 * <td>Takes either <i>collab-icon</i> or <i>url</i> as a value. 25713 * <BR><i>collab-icon</i> applies a <a href="finesse.digital.ChannelSchema.html#cdIcons">stock icon</a> 25714 * <BR><i>url</i> applies a custom icon supplied by gadget. The icon could be located as part of gadget files in Finesse.</td></tr> 25715 * <tr><td class='pad30'>value</td><td>String</td><td>Chat</td><td>The <a href="finesse.digital.ChannelSchema.html#cdIcons">name</a> of the stock icon 25716 * or the url of the custom icon</td></tr> 25717 * <tr><td>content [Optional]</td><td colspan='2'>Array</td><td>First six name/value pairs is shown in popover. A single property should be defined as below:</td></tr> 25718 * <tr><td class='pad30'>name</td><td>String</td><td>Customer Name</td><td>The property name that is displayed on the left</td></tr> 25719 * <tr><td class='pad30'>value</td><td>String</td><td>Michael Littlefoot</td><td>The corresponding property value that is displayed on the right. 25720 * <BR>Note: For long property values, a tooltip is displayed.</td></tr> 25721 * <tr><td>headerContent</td><td colspan='2'>Object</td><td>The title of popover when it is shown or minimized. It should be defined as below</td></tr> 25722 * <tr><td class='pad30'>maxHeaderTitle</td><td>String</td><td>Chat from firstName lastName</td><td>Popover title when it is not minimized</td></tr> 25723 * <tr><td class='pad30'>minHeaderTitle</td><td>String</td><td>firstName</td><td>Popover title when it is minimized</td></tr> 25724 * 25725 * </tbody> 25726 * </table> 25727 * 25728 */ 25729 getBannerDataSchema: function () { 25730 return _bannerDataSchema; 25731 }, 25732 25733 /** 25734 * 25735 * @example 25736 * <BR><BR><b>Example JSON for <i>TimerData</i>:</b> 25737 * <pre class='inset'> 25738 { 25739 "displayTimeoutInSecs": 30, // mandatory. minimum is 3 and maximum is 3600. -1 indicates no upper limit 25740 "display": true, // false means no displayable UI for timer 25741 "counterType": COUNT_UP or COUNT_DOWN, 25742 } 25743 * </pre> 25744 * @returns 25745 * <table class="schemaTable"> 25746 * <thead> 25747 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 25748 * </thead> 25749 * <tbody> 25750 * <tr><td>displayTimeoutInSecs [Mandatory]</td><td>Integer</td><td>30</td><td>Minimum is 3 and maximum is 3600. -1 indicates no upper limit</td></tr> 25751 * <tr><td>display</td><td>boolean</td><td>true</td><td>false indicates not to display any timer </td></tr> 25752 * <tr><td>counterType</td><td>enum</td><td>COUNT_UP</td><td>Takes value COUNT_UP or COUNT_DOWN. For scenarios like how long the chat has been active would require a COUNT_UP timer. 25753 * On the other hand before chat is autoclosed for a agent RONA, it would be apt to use a COUNT_DOWN timer. </td></tr> 25754 * 25755 * </tbody> 25756 * </table> 25757 */ 25758 getTimerDataSchema: function () { 25759 return _timeDataSchema; 25760 }, 25761 25762 25763 /** 25764 * 25765 * 25766 * @example 25767 * <BR><BR><b>Example JSON for <i>ActionData</i>:</b> 25768 * <pre class='inset'> 25769 { 25770 "dismissible": true, // or "false" 25771 "clientIdentifier" : 'popup1', // A string to uniquely identify a specific popover 25772 "requiredActionText": "Please answer the call from your phone", 25773 "buttons": // Optional. Max 2 25774 [ 25775 { 25776 "id": "No", 25777 "label": "Decline", 25778 "type": "Decline", 25779 "hoverText": "", 25780 "confirmButtons": [ // confirmButtons is an optional property in actionData 25781 { 25782 "id": "Yes", 25783 "label": "Reject - Return to campaign", 25784 "hoverText": "" 25785 }, 25786 { 25787 "id": "No", 25788 "label": "Close - Remove from campaign", 25789 "hoverText": "" 25790 } 25791 ] 25792 } 25793 ] 25794 } 25795 * </pre> 25796 * 25797 * @returns 25798 * Schema for the validation of the below JSON definition 25799 * 25800 * <table class="schemaTable"> 25801 * <thead> 25802 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 25803 * </thead> 25804 * <tbody> 25805 * <tr><td>dismissible</td><td>boolean</td><td>true</td><td>True if button definition is optional. Flase if button definition is mandatory.</td></tr> 25806 * <tr><td>clientIdentifier</td><td>string</td><td>popover1</td><td>A unique identifier across all popovers. 25807 * This is used in the callback for popover events</td></tr> 25808 * <tr><td>requiredActionText [Optional]</td><td>String</td><td>Please answer the call from your phone</td><td>This text is at the bottom of the popover to inform what action the user has to take.</td></tr> 25809 * <tr><td>buttons [Optional]</td><td colspan='2'>Array</td><td>Buttons displayed in the popover. Maximum 2 buttons. It can be defined as below:</td></tr> 25810 * <tr><td class='pad30'>id</td><td>string</td><td>ok1</td><td> A unique ID that represents a button </td></tr> 25811 * <tr><td class='pad30'>label</td><td>string</td><td>Accept</td><td>The display text of the action button</td></tr> 25812 * <tr><td class='pad30'>type</td><td>enum</td><td>Affirm</td><td>Affirm for green button. Decline for red button</td></tr> 25813 * <tr><td class='pad30'>hoverText [Optional]</td><td>String</td><td>Click here to accept chat</td><td>The tooltip that is displayed on mouse hover</td></tr> 25814 * <tr><td class='pad30'>confirmButtons [Optional]</td><td colspan='2'>Object</td><td>An additional confirmation message with the buttons to be displayed when the user clicks on the above action button. It can be defined as below: </td></tr> 25815 * <tr><td class='pad60'>id</td><td>string</td><td>id</td><td>Id of the confirmation button</td></tr> 25816 * <tr><td class='pad60'>label</td><td>string</td><td>Reject - Return to campaign</td><td>Label to displayed on the button</td></tr> 25817 * <tr><td class='pad60'>hoverText</td><td>string</td><td>Click here to reject<td>Tooltip message on the button</td></tr> 25818 * </tbody> 25819 * </table> 25820 */ 25821 getActionDataSchema: function () { 25822 return _actionDataSchema; 25823 } 25824 }; 25825 }()); 25826 25827 window.finesse = window.finesse || {}; 25828 window.finesse.containerservices = window.finesse.containerservices || {}; 25829 window.finesse.containerservices.PopoverSchema = PopoverSchema; 25830 25831 // CommonJS 25832 if (typeof module === "object" && module.exports) { 25833 module.exports = PopoverSchema; 25834 } 25835 25836 return PopoverSchema; 25837 }); 25838 /** 25839 * PopoverService provides API consists of methods that would allow a gadget to request Finesse to show popovers. 25840 * 25841 * @example 25842 * containerServices = finesse.containerservices.ContainerServices.init(); 25843 * popoverService = finesse.containerservices.PopoverService.init(containerServices); 25844 * popoverService.showPopover(bannerData, timerData, actionData, actionHandler); 25845 */ 25846 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 25847 /*global window:true, define:true, finesse:true*/ 25848 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 25849 define('containerservices/PopoverService',['require','exports','module','../utilities/Utilities','../clientservices/ClientServices','./PopoverSchema','../utilities/JsonValidator'],function (require, exports, module) { 25850 25851 var PopoverService = (function () { /** @lends finesse.containerservices.PopoverService.prototype */ 25852 25853 const Utilities = require('../utilities/Utilities'); 25854 const ClientService = require('../clientservices/ClientServices'); 25855 const SchemaMgr = require('./PopoverSchema'); 25856 const SchemaValidator = require('../utilities/JsonValidator'); 25857 25858 var 25859 /** 25860 * Open Ajax topic for the API to publish popover requests to Finesse Popover Component. 25861 * @private 25862 */ 25863 _popoverTopic, 25864 25865 /** 25866 * References to gadget's ContainerService 25867 * @private 25868 */ 25869 _containerService, 25870 25871 /** 25872 * Whether the PopoverService have been initialized or not. 25873 * @private 25874 */ 25875 _inited = false, 25876 25877 /* 25878 * References to ClientServices logger 25879 * @private 25880 */ 25881 _logger = { 25882 log: ClientService.log 25883 }, 25884 25885 /** 25886 * Popover Action enum. 25887 * @private 25888 */ 25889 POPOVER_ACTION = Object.freeze({ 25890 SHOW: 'show', 25891 DISMISS: 'dismiss', 25892 UPDATE: 'update' 25893 }), 25894 25895 /** 25896 * Ensure that PopoverService have been inited. 25897 * @private 25898 */ 25899 _isInited = function () { 25900 if (!_inited) { 25901 throw new Error("PopoverService needs to be inited."); 25902 } 25903 return _inited; 25904 }, 25905 25906 /** 25907 * Dynamic registry object, which keeps a map of popover id and its action handler function reference. 25908 * This handler will be invoked on actions on the popover. 25909 * @private 25910 */ 25911 _popoverRegistry = (function () { 25912 var _map = {}, 25913 25914 _add = function (id, handler) { 25915 _map[id] = { 25916 handler: handler 25917 }; 25918 }, 25919 25920 _clear = function (id) { 25921 if (_map[id]) { 25922 delete _map[id]; 25923 } 25924 }; 25925 25926 return { 25927 add: _add, 25928 clear: _clear, 25929 isExist: function (id) { 25930 return _map[id] !== undefined; 25931 }, 25932 sendEvent: function (id, data) { 25933 if (_map[id]) { 25934 _map[id].handler(data); 25935 } 25936 } 25937 }; 25938 }()), 25939 25940 /** 25941 * Utility function to make a subscription to a particular topic. Only one 25942 * callback function is registered to a particular topic at any time. 25943 * 25944 * @param {String} topic 25945 * The full topic name. The topic name should follow the OpenAjax 25946 * convention using dot notation (ex: finesse.api.User.1000). 25947 * @param {Function} handler 25948 * The function that should be invoked with the data when an event 25949 * is delivered to the specific topic. 25950 * @returns {Boolean} 25951 * True if the subscription was made successfully and the callback was 25952 * been registered. False if the subscription already exist, the 25953 * callback was not overwritten. 25954 * @private 25955 */ 25956 _subscribe = function (topic, handler) { 25957 try { 25958 return _containerService.addHandler(topic, handler); 25959 } catch (error) { 25960 _logger.log('PopoverService: Error while subscribing to open ajax topic : ' + topic + ', Exception:' + error.toString()); 25961 throw new Error('PopoverService: Unable to subscribe the popover topic with Open Ajax Hub : ' + error); 25962 } 25963 }, 25964 25965 /** 25966 * Gets the id of the gadget. 25967 * @returns {number} the id of the gadget 25968 * @private 25969 */ 25970 _getMyGadgetId = function () { 25971 var gadgetId = _containerService.getMyGadgetId(); 25972 return gadgetId ? gadgetId : ''; 25973 }, 25974 25975 /** 25976 * Function which processes the messages from Finesse Popover Component. 25977 * The messages received mainly for below cases, 25978 * <ul> 25979 * <li> User Interaction with popover. 25980 * <li> Timeout. 25981 * <ul> 25982 * The registered callbacks are invoked based on the data. 25983 * 25984 * @param {String} response 25985 * The actual message sent by Finesse Popover Component via open ajax hub. 25986 * @private 25987 */ 25988 _processResponseFromPopover = function (response) { 25989 _logger.log('PopoverService: Received Message from PopoverComp: ' + JSON.stringify(response)); 25990 if (response) { 25991 try { 25992 // if the received data from topic is in text format, then parse it to JSON format 25993 response = typeof response === 'string' ? JSON.parse(response) : response; 25994 } catch (error) { 25995 _logger.log('PopoverService: Error while parsing the popover message: ' + error.toString()); 25996 return; 25997 } 25998 25999 // construct the response for gadget 26000 var data = {}; 26001 data.popoverId = response.id; 26002 data.source = response.source; 26003 26004 _logger.log('PopoverService: Calling gadget action handler'); 26005 26006 // invoke the gadget's action handler to process the action taken in popover 26007 _popoverRegistry.sendEvent(data.popoverId, data); 26008 26009 // clear the popover cached data, no more needed 26010 _popoverRegistry.clear(data.popoverId); 26011 } 26012 }, 26013 26014 /** 26015 * Validate the gadget passed JSON structure against the schema definition. 26016 * 26017 * @param {Object} bannerData 26018 * Banner data JSON object as per the spec. 26019 * @param {Object} timerData 26020 * Timer data JSON object as per the spec. 26021 * @param {Object} actionData 26022 * Action data JSON object as per the spec. 26023 * @throws {Error} if the data is not as per defined format. 26024 * @private 26025 */ 26026 _validateData = function (bannerData, timerData, actionData) { 26027 var bannerDataResult = SchemaValidator.validateJson(bannerData, SchemaMgr.getBannerDataSchema()); 26028 /* if result.valid is false, then additional details about failure are contained in 26029 result.error which contains json like below: 26030 26031 { 26032 "code": 0, 26033 "message": "Invalid type: string", 26034 "dataPath": "/intKey", 26035 "schemaPath": "/properties/intKey/type" 26036 } 26037 */ 26038 if (!bannerDataResult.valid) { 26039 _logger.log("PopoverService: Banner data validation bannerDataResult : " + JSON.stringify(bannerDataResult)); 26040 throw new Error("PopoverService: Banner data structure is not in expected format. Refer finesse client logs for more details."); 26041 } 26042 var timerDataResult = SchemaValidator.validateJson(timerData, SchemaMgr.getTimerDataSchema()); 26043 if (!timerDataResult.valid) { 26044 _logger.log("PopoverService: Timer data validation timerDataResult : " + JSON.stringify(timerDataResult)); 26045 throw new Error("PopoverService: Timer data structure is not in expected format. Refer finesse client logs for more details."); 26046 } 26047 var actionDataResult = SchemaValidator.validateJson(actionData, SchemaMgr.getActionDataSchema()); 26048 if (!actionDataResult.valid) { 26049 _logger.log("PopoverService: Action data validation actionDataResult : " + JSON.stringify(actionDataResult)); 26050 throw new Error("PopoverService: Action data structure is not in expected format. Refer finesse client logs for more details."); 26051 } 26052 }, 26053 26054 /** 26055 * Parse the data for JSON object. 26056 * 26057 * @param {String} data 26058 * popover data structure. 26059 * @throws {Error} if the JSON parsing fails. 26060 * @private 26061 */ 26062 _getJSONObject = function (data) { 26063 if (data) { 26064 try { 26065 // parse the data as user may pass in string or object format 26066 data = typeof data === 'string' ? JSON.parse(data) : data; 26067 } catch (err) { 26068 throw new Error("PopoverService: Data Parsing Failed. Popover data not in expected format. Error: " + err.toString()); 26069 } 26070 return data; 26071 } 26072 }, 26073 26074 /** 26075 * Requests Finesse to show notification popover with the given payload. The user interaction or timeout of the popover 26076 * will be notified to gadget through the registered action handler. 26077 * 26078 * @param {Boolean} isExistingPopover 26079 * If Update or new Show operation . 26080 * @param {String} popoverId 26081 * Popover Id 26082 * @param {Object} payload 26083 * Action data JSON object as per the spec. 26084 * @param {Function} actionHandler 26085 * Callback function invoked when the user interacts with the popover or popover times out. 26086 * @throws Error 26087 * Throws error when the passed in popoverData is not as per defined format. 26088 */ 26089 _display = function (isExistingPopover, popoverId, payload, actionHandler) { 26090 // subscribe this popover topic with open ajax hub for processing the response from popover component 26091 _subscribe(payload.topic, _processResponseFromPopover); 26092 26093 // cache the gadget action handler, so that it will be used when processing the popover response 26094 if (!isExistingPopover) _popoverRegistry.add(popoverId, actionHandler); 26095 26096 _logger.log('PopoverService: Sending popover show request to Finesse Popover Component: ' + JSON.stringify(payload)); 26097 26098 // publish the popover show request to popover UI component 26099 _containerService.publish(_popoverTopic, payload); 26100 }, 26101 26102 /** 26103 * Requests Finesse to dismiss the notification popover. 26104 * 26105 * @param {String} popoverId 26106 * Popover id which was returned from showPopover call 26107 * @throws Error 26108 * Throws error if the service is not yet initialized. 26109 */ 26110 _dismiss = function (popoverId) { 26111 // check whether the service is inited 26112 _isInited(); 26113 26114 _logger.log('PopoverService: Received popover dismiss request for popover id ' + popoverId); 26115 26116 if (popoverId && _popoverRegistry.isExist(popoverId)) { 26117 // construct the payload required for the popover component 26118 var payload = { 26119 id: popoverId, 26120 action: POPOVER_ACTION.DISMISS 26121 }; 26122 26123 // publish the popover dismiss request to popover UI component 26124 _containerService.publish(_popoverTopic, payload); 26125 26126 // clear the popover cached data, no more needed 26127 _popoverRegistry.clear(popoverId); 26128 } 26129 }; 26130 26131 return { 26132 /** 26133 * @class 26134 * Finesse Voice component and Gadget(s) hosting digital services require 26135 * the {@link finesse.containerservices.PopoverService} to display a popup 26136 * for incoming call and chat events. <BR> 26137 * This API provides makes use of the schema as defined in {@link finesse.containerservices.PopoverSchema} 26138 * and provides various operations for managing the popup. 26139 * 26140 * <style> 26141 * .popoverTable tr:nth-child(even) { background-color:#EEEEEE; } 26142 * .popoverTable th { background-color: #999999; } 26143 * .popoverTable th, td { border: none; } 26144 * .pad30 {padding-left: 30px;} 26145 * .inset { 26146 * padding: 5px; 26147 * border-style: inset; 26148 * background-color: #DDDDDD; 26149 * width: auto; 26150 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 26151 * font: 12px arial, sans-serif; 26152 * color:rebeccapurple; 26153 * } 26154 * 26155 * 26156 * </style> 26157 * 26158 * @example 26159 * <BR><BR><b>Example demonstrating initialization</b> 26160 * 26161 <pre class='inset'> 26162 containerServices = finesse.containerservices.ContainerServices.init(); 26163 popoverService = finesse.containerservices.PopoverService.init(containerServices); 26164 popoverService.showPopover(bannerData, timerData, actionData, actionHandler); 26165 </pre> 26166 * Details of the parameters are described in this API spec below. 26167 * 26168 * @constructs 26169 */ 26170 _fakeConstuctor: function () { 26171 /* This is here so we can document init() as a method rather than as a constructor. */ 26172 }, 26173 26174 /** 26175 * Initialize the PopoverService for use in gadget. 26176 * See example in <i> Class Detail </i> for initialization. 26177 * 26178 * @param {finesse.containerservices.ContainerServices} ContainerService instance. 26179 * @returns {finesse.containerservices.PopoverService} instance. 26180 */ 26181 init: function (containerService) { 26182 if (!_inited) { 26183 if (!containerService) { 26184 throw new Error("PopoverService: Invalid ContainerService reference."); 26185 } 26186 _containerService = containerService; 26187 _popoverTopic = containerService.Topics.FINEXT_POPOVER_EVENT; 26188 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 26189 _inited = true; 26190 } 26191 return this; 26192 }, 26193 26194 /** 26195 * Combines multiple parameters and generates a single payload for use by the popover service. 26196 * 26197 * @param {Boolean} isExistingPopover 26198 * True if this popover already exists. False if not. 26199 * @param {String} popoverId 26200 * The unique identifier for the popover. This id was returned from showPopover call. 26201 * @param {Object} bannerData 26202 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 26203 * @param {Object} timerData 26204 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 26205 * @param {Object} actionData 26206 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 26207 * @throws 26208 * Throws error when the passed in popoverData is not as per defined format. 26209 */ 26210 generatePayload: function (isExistingPopover, popoverId, bannerData, timerData, actionData) { 26211 // parse the data 26212 bannerData = _getJSONObject(bannerData); 26213 timerData = _getJSONObject(timerData); 26214 actionData = _getJSONObject(actionData); 26215 var topic = _popoverTopic + '.' + popoverId; 26216 26217 // validate the data 26218 _validateData(bannerData, timerData, actionData); 26219 26220 var action = isExistingPopover ? POPOVER_ACTION.UPDATE : POPOVER_ACTION.SHOW; 26221 // construct the payload required for the popover component 26222 var payload = { 26223 id: popoverId, 26224 topic: topic, 26225 action: action, 26226 data: { 26227 bannerData: bannerData, 26228 timerData: timerData 26229 } 26230 }; 26231 26232 if (actionData) { 26233 payload.data.actionData = actionData; 26234 } 26235 26236 return payload; 26237 }, 26238 26239 /** 26240 * 26241 * Display a popover with the given data. The user interaction or timeout of the popover 26242 * will be notified to gadget through the registered action handler. 26243 * 26244 * @param {Object} bannerData 26245 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 26246 * @param {Object} timerData 26247 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 26248 * @param {Object} actionData 26249 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 26250 * @param {Function} actionHandler 26251 * This is a Handler that gets called for events due to user interaction. Following are the params of this function. 26252 * <table class="popoverTable"> 26253 * <thead><tr><th>Parameter</th><th>Description</th></tr></thead> 26254 * <tbody> 26255 * <tr><td>popoverId</td><td>A unique popover id that was assigned to the new popover.</td></tr> 26256 * <tr><td>source</td><td>The id of the source which generated the event. Examples are 'btn_[id]_click' or 'dismissed' or 'timeout'</td></tr> 26257 * </table> 26258 * 26259 * @returns {String} 26260 * Generated Popover-id, can be used for subsequent interaction with the service. 26261 * @throws 26262 * Throws error when the passed in popoverData is not as per defined format. 26263 */ 26264 showPopover: function (bannerData, timerData, actionData, actionHandler) { 26265 // check whether the service is inited 26266 _isInited(); 26267 26268 var isExistingPopover = false; 26269 26270 // construct a unique id for the popover 26271 var popoverId = Utilities.generateUUID(), 26272 gadgetId = _getMyGadgetId(); 26273 26274 _logger.log('PopoverService: Received new popover show request from gadgetId ' + gadgetId); 26275 26276 var payload = this.generatePayload(isExistingPopover, popoverId, bannerData, timerData, actionData); 26277 26278 // check for action handler 26279 if (typeof actionHandler !== 'function') { 26280 throw new Error('PopoverService: Action handler should be a function'); 26281 } 26282 26283 _display(isExistingPopover, popoverId, payload, actionHandler); 26284 26285 return popoverId; 26286 }, 26287 26288 /** 26289 * 26290 * Modify an active popover's displayed content. 26291 * 26292 * @param {String} popoverId 26293 * A unique popover id that was returned in {@link #showPopover}. 26294 * @param {Object} bannerData 26295 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 26296 * @param {Object} timerData 26297 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 26298 * @param {Object} actionData 26299 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 26300 * @param {Function} actionHandler 26301 * Refer The callback function requirements as described in {@link #showPopover} 26302 * @throws 26303 * Throws error when the passed in popoverData is not as per defined format. 26304 */ 26305 updatePopover: function (popoverId, bannerData, timerData, actionData) { 26306 // check whether the service is inited 26307 _isInited(); 26308 26309 var isExistingPopover = true; 26310 26311 var gadgetId = _getMyGadgetId(); 26312 26313 _logger.log('PopoverService: Received an update popover request from gadgetId ' + gadgetId); 26314 26315 var payload = this.generatePayload(isExistingPopover, popoverId, bannerData, timerData, actionData); 26316 26317 _display(isExistingPopover, popoverId, payload, null); 26318 }, 26319 26320 /** 26321 * Dismisses the notification popover. 26322 * 26323 * @param {String} popoverId 26324 * The unique identifier for the popover. This id was returned from showPopover call. 26325 * @throws 26326 * Throws error if the service is not yet initialized. 26327 */ 26328 dismissPopover: function (popoverId) { 26329 _dismiss(popoverId); 26330 }, 26331 26332 /** 26333 * ENUM: Determines how the time in popover should update. 26334 * [COUNT_UP, COUNT_DOWN] 26335 */ 26336 COUNTER_TYPE: Object.freeze({ 26337 COUNT_UP: 'COUNT_UP', 26338 COUNT_DOWN: 'COUNT_DOWN' 26339 }) 26340 }; 26341 }()); 26342 26343 window.finesse = window.finesse || {}; 26344 window.finesse.containerservices = window.finesse.containerservices || {}; 26345 window.finesse.containerservices.PopoverService = PopoverService; 26346 26347 // CommonJS 26348 if (typeof module === "object" && module.exports) { 26349 module.exports = PopoverService; 26350 } 26351 26352 return PopoverService; 26353 }); 26354 /** 26355 * UCCX Email/Chat uses a JSON payload for to trigger workflow. 26356 * That payload has to conform to this schema. 26357 * This schema has been defined as per http://json-schema.org/ 26358 * 26359 * @see utilities.JsonValidator 26360 */ 26361 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 26362 /*global window:true, define:true*/ 26363 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 26364 define('workflow/WorkflowSchema',['require','exports','module'],function (require, exports, module) { 26365 26366 var WorkflowSchema = (function () { 26367 var _workflowParamSchema = { 26368 "$schema": "http://json-schema.org/draft-06/schema#", 26369 "additionalProperties": false, 26370 "properties": { 26371 "mediaType": { 26372 "type": "string", 26373 "title": "media type , eg. email, chat etc." 26374 }, 26375 "dialogId": { 26376 "type": "string", 26377 "title": "The Workflow requestId. this is will be used for logging purpose " 26378 }, 26379 "state": { 26380 "type": "string", 26381 "title": "The When to performa ction Schema e.g. EMAIL_PRESENTED, EMAIL_READ etc" 26382 }, 26383 "taskVariables": { 26384 "type": "object" 26385 } 26386 }, 26387 "required": [ 26388 "dialogId", 26389 "state", 26390 "mediaType" 26391 ] 26392 }; 26393 26394 return { 26395 _fakeConstuctor: function () { 26396 /* This is here so we can document init() as a method rather than as a constructor. */ 26397 }, 26398 26399 /** 26400 * Returns schema that is applicable for workflow param JSON Structure. 26401 */ 26402 getWorkflowParamSchema: function () { 26403 return _workflowParamSchema; 26404 } 26405 }; 26406 }()); 26407 26408 window.finesse = window.finesse || {}; 26409 window.finesse.workflow = window.finesse.workflow || {}; 26410 window.finesse.workflow.WorkflowSchema = WorkflowSchema; 26411 26412 // CommonJS 26413 if (typeof module === "object" && module.exports) { 26414 module.exports = WorkflowSchema; 26415 } 26416 26417 return WorkflowSchema; 26418 }); 26419 26420 /** 26421 * WorkflowService provides API consists of methods that would allow a gadgets to submit workflow task. 26422 * 26423 * @example 26424 * var containerServices = finesse.containerservices.ContainerServices.init(); 26425 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 26426 * var payload = { 26427 "dialogId": "email1", 26428 "mediaType": "email", 26429 "state": "EMAIL_READ", 26430 "taskVariables": { 26431 "from": "mme@cisco.com", 26432 "cc": "yyy@cisco.com" 26433 } 26434 } 26435 * workflowService.submitTask(payload); 26436 */ 26437 /** @private */ 26438 define('workflow/WorkflowService',['require','exports','module','./WorkflowSchema','../utilities/JsonValidator','../clientservices/ClientServices'],function (require, exports, module) { 26439 /** @lends finesse.workflow.WorkflowService.prototype */ 26440 26441 var WorkflowService = (function () { 26442 var WorkflowSchema = require('./WorkflowSchema'), 26443 SchemaValidator = require('../utilities/JsonValidator'), 26444 ClientService = require('../clientservices/ClientServices'), 26445 /** 26446 * References to ContainerService 26447 * @private 26448 */ 26449 _containerService, 26450 26451 /** 26452 * Whether the WorkflowService have been initialized or not. 26453 * @private 26454 */ 26455 _inited = false, 26456 26457 /** 26458 * References to ClientServices logger 26459 * @private 26460 */ 26461 _logger = { 26462 log: ClientService.log 26463 }, 26464 26465 /** 26466 * Ensure that WorkflowService have been initiated. 26467 * @private 26468 */ 26469 _isInitiated = function () { 26470 if (!_inited) { 26471 throw new Error("WorkflowService needs to be initiated."); 26472 } 26473 }, 26474 26475 /** 26476 * Gets the id of the gadget. 26477 * @returns {number} the id of the gadget 26478 * @private 26479 */ 26480 _getMyGadgetId = function () { 26481 var gadgetId = _containerService.getMyGadgetId(); 26482 return gadgetId || ''; 26483 }, 26484 26485 /** 26486 * Validate the workflow parameter payload structure.. 26487 * 26488 * @param {Object} payload 26489 * workflow parameter. 26490 * @throws {Error} if the payload is not in defined format. 26491 * @private 26492 */ 26493 _validateWorkflowParam = function (payload) { 26494 return SchemaValidator.validateJson(payload, WorkflowSchema.getWorkflowParamSchema()); 26495 }, 26496 26497 /** 26498 * Function which processes the the trigger workflow for digital channels. 26499 * 26500 * @param {Object} payload 26501 * The payload containing workflow submit parameter. 26502 * @private 26503 */ 26504 _processWorkflowRequest = function (payload) { 26505 var result = _validateWorkflowParam(payload), data; 26506 26507 if (!result.valid) { 26508 _logger.log("WorkflowService: Finesse workflow trigger API parameter Validation result : " + JSON.stringify(result)); 26509 throw new Error("workflow parameter structure is not in expected format. Refer finesse client logs for more details."); 26510 } 26511 26512 _logger.log("[WorkflowService] workflow task submitted for Gadget : " + _getMyGadgetId() + ' : Workflow dialogId: ' + payload.dialogId); 26513 data = {gadgetId: _getMyGadgetId(), payload: payload}; 26514 26515 _containerService.publish("finesse.workflow.digitalChannels", data); 26516 }; 26517 26518 return { 26519 26520 /** 26521 * Method to trigger workflow for digital channels. 26522 * 26523 * @example 26524 * var containerServices = finesse.containerservices.ContainerServices.init(); 26525 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 26526 * var payload = { 26527 "dialogId": "email1", 26528 "mediaType": "email", 26529 "state": "EMAIL_READ", 26530 "taskVariables": { 26531 "from": "mme@cisco.com", 26532 "cc": "yyy@cisco.com" 26533 } 26534 } 26535 * workflowService.submitTask(payload); 26536 * 26537 * @param {Object} payload 26538 * 26539 */ 26540 submitTask: function (payload) { 26541 _isInitiated(); 26542 _processWorkflowRequest(payload); 26543 }, 26544 /** 26545 * @class 26546 * WorkflowService provides API consists of methods that would allow a gadgets to submit workflow task. 26547 * 26548 * @example 26549 * var containerServices = finesse.containerservices.ContainerServices.init(); 26550 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 26551 * workflowService.submitTask(payload); 26552 * 26553 * @constructs 26554 */ 26555 _fakeConstuctor: function () { 26556 /* This is here so we can document init() as a method rather than as a constructor. */ 26557 }, 26558 26559 /** 26560 * 26561 * Initialize WorkflowService. 26562 * 26563 * @example 26564 * var containerServices = finesse.containerservices.ContainerServices.init(); 26565 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 26566 * 26567 * @returns {finesse.workflow.WorkflowService} instance. 26568 * 26569 */ 26570 init: function (containerServices) { 26571 _logger.log("WorkflowService is getting initiated....."); 26572 if (!_inited) { 26573 if (!containerServices) { 26574 throw new Error("WorkflowService: Invalid ContainerService reference."); 26575 } 26576 _inited = true; 26577 _containerService = containerServices; 26578 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 26579 } 26580 return this; 26581 } 26582 }; 26583 }()); 26584 26585 window.finesse = window.finesse || {}; 26586 window.finesse.workflow = window.finesse.workflow || {}; 26587 window.finesse.workflow.WorkflowService = WorkflowService; 26588 26589 // CommonJS 26590 if (typeof module === "object" && module.exports) { 26591 module.exports = WorkflowService; 26592 } 26593 26594 return WorkflowService; 26595 }); 26596 26597 26598 /** 26599 * JavaScript representation of the Finesse UserTeamMessages object for a Supervisor. 26600 * 26601 * @requires Class 26602 * @requires finesse.FinesseBase 26603 * @requires finesse.restservices.RestBase 26604 * @requires finesse.utilities.Utilities 26605 * @requires finesse.restservices.TeamMessage 26606 */ 26607 26608 /** The following comment is to prevent jslint errors about 26609 * using variables before they are defined. 26610 */ 26611 /*global Exception */ 26612 26613 /** @private */ 26614 define('restservices/UserTeamMessages',[ 26615 'restservices/RestCollectionBase', 26616 'restservices/RestBase', 26617 'utilities/Utilities', 26618 'restservices/TeamMessage', 26619 'restservices/TeamMessages' 26620 ], 26621 function (RestCollectionBase, RestBase,Utilities, TeamMessage, TeamMessages) { 26622 26623 var UserTeamMessages = TeamMessages.extend({ 26624 26625 /** 26626 * @class 26627 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 26628 * methods to operate on the object against the server. 26629 * 26630 * @param {Object} options 26631 * An object with the following properties:<ul> 26632 * <li><b>id:</b> The id of the object being constructed</li> 26633 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 26634 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 26635 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 26636 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 26637 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 26638 * <li><b>status:</b> {Number} The HTTP status code returned</li> 26639 * <li><b>content:</b> {String} Raw string of response</li> 26640 * <li><b>object:</b> {Object} Parsed object of response</li> 26641 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 26642 * <li><b>errorType:</b> {String} Type of error that was caught</li> 26643 * <li><b>errorMessage:</b> {String} Message associated with error</li> 26644 * </ul></li> 26645 * </ul></li> 26646 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 26647 * @constructs 26648 **/ 26649 init: function (options) { 26650 var options = options || {}; 26651 options.parentObj = this; 26652 this._super(options); 26653 }, 26654 26655 /** 26656 * @private 26657 * Gets the REST class for the current object - this is the TeamMessages class. 26658 * @returns {Object} The LayoutConfigs class. 26659 */ 26660 getRestClass: function () { 26661 return TeamMessages; 26662 }, 26663 26664 getRestItemClass: function () { 26665 return TeamMessage; 26666 }, 26667 26668 getRestType: function () { 26669 return "TeamMessages"; 26670 }, 26671 26672 /** 26673 * @private 26674 * Override default to indicate that this object doesn't support making 26675 * requests. 26676 */ 26677 supportsRequests: false, 26678 26679 /** 26680 * @private 26681 * Override default to indicate that this object doesn't support subscriptions. 26682 */ 26683 supportsSubscriptions: false, 26684 26685 supportsRestItemSubscriptions: false, 26686 26687 getTeamMessages: function (createdBy, handlers) { 26688 26689 var self = this, contentBody, reasonCode, url; 26690 contentBody = {}; 26691 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 26692 handlers = handlers || {}; 26693 this.restRequest('/finesse/api/'+this.getRestType()+'?createdBy='+createdBy, { 26694 method: 'GET', 26695 success: function(rsp) { 26696 var teamMessage = rsp.object.teamMessages? rsp.object.teamMessages.TeamMessage: null 26697 handlers.success(teamMessage); 26698 }, 26699 error: function (rsp) { 26700 handlers.error(rsp); 26701 }, 26702 content: contentBody 26703 }); 26704 26705 return this; // Allow cascading 26706 }, 26707 26708 /** 26709 * 26710 */ 26711 deleteTeamMessage: function (messageId, handlers) { 26712 handlers = handlers || {}; 26713 this.restRequest(this.getRestItemBaseUrl() +'/'+messageId, { 26714 method: 'DELETE', 26715 success: handlers.success, 26716 error: handlers.error, 26717 content: undefined 26718 }); 26719 return this; // Allow cascading 26720 } 26721 26722 }); 26723 26724 window.finesse = window.finesse || {}; 26725 window.finesse.restservices = window.finesse.restservices || {}; 26726 window.finesse.restservices.UserTeamMessages = UserTeamMessages; 26727 26728 return UserTeamMessages; 26729 }); 26730 /** 26731 * Utilities static class which provides utility methods for shortcut key . 26732 */ 26733 /** @private */ 26734 define('shortcutkey/Utilities',[],function () { 26735 26736 var Utilities = (function () { 26737 return { 26738 CONSTANTS: { 26739 MODIFIER_KEYS_CTRL_SHIFT: 'ctrlKey+shiftKey', 26740 MODIFIER_KEYS_ALT_SHIFT: 'altKey+shiftKey', 26741 MODIFIER_KEYS_CTRL_ALT: 'ctrlKey+altKey', 26742 MODIFIER_KEYS_SHIFT: 'shiftKey', 26743 MODIFIER_KEYS_CTRL: 'ctrlKey', 26744 MODIFIER_KEYS_ALT: 'altKey', 26745 ACTIVE_TAB: 'activeTab', 26746 ACTIVE_FRAME: 'activeFrame' 26747 }, 26748 26749 getModifierKeys: function (keyParam) { 26750 return keyParam.modifierKeys ? keyParam.modifierKeys.split(' ').join('') : Utilities.CONSTANTS.MODIFIER_KEYS_CTRL_SHIFT; 26751 }, 26752 constrcutSKStoreObjKey: function (keyParam) { 26753 var modifierKeys = this.getModifierKeys(keyParam); 26754 return modifierKeys.replace('+', '_') + '_' + keyParam.key; 26755 }, 26756 26757 /** 26758 * 26759 * @param {} keyEvent 26760 * @returns keyId 26761 * @example 'ctrlKey_shiftKey_60' 26762 */ 26763 constructIdFromKeyEvents: function (keyEvent) { 26764 26765 var matches = [], Const = Utilities.CONSTANTS; 26766 26767 if (keyEvent.ctrlKey && keyEvent.shiftKey) { 26768 matches.push(Const.MODIFIER_KEYS_CTRL); 26769 matches.push(Const.MODIFIER_KEYS_SHIFT); 26770 } else if(keyEvent.ctrlKey && keyEvent.altKey){ 26771 matches.push(Const.MODIFIER_KEYS_CTRL); 26772 matches.push(Const.MODIFIER_KEYS_ALT); 26773 } else if (keyEvent.altKey && keyEvent.shiftKey) { 26774 matches.push(Const.MODIFIER_KEYS_ALT); 26775 matches.push(Const.MODIFIER_KEYS_SHIFT); 26776 } else if(keyEvent.ctrlKey && !keyEvent.shiftKey && !keyEvent.altKey) { 26777 matches.push(Const.MODIFIER_KEYS_CTRL); 26778 } else if(keyEvent.altKey && !keyEvent.shiftKey && !keyEvent.ctrlKey){ 26779 matches.push(Const.MODIFIER_KEYS_ALT); 26780 } else if(keyEvent.shiftKey && !keyEvent.altKey && !keyEvent.ctrlKey){ 26781 matches.push(Const.MODIFIER_KEYS_SHIFT); 26782 } 26783 var shortcutKeyId = ''; 26784 matches.forEach(function(match){ 26785 shortcutKeyId += match + '_' 26786 }) 26787 26788 return shortcutKeyId + this._characterFromEvent(keyEvent); 26789 }, 26790 /** 26791 * takes the event and returns the key character 26792 * 26793 * @param {Event} e 26794 * @return {string} 26795 */ 26796 _characterFromEvent: function (e) { 26797 return String.fromCharCode(e.keyCode).toLowerCase(); 26798 }, 26799 Logger: { 26800 log: function (params) { 26801 //eslint-disable-next-line 26802 if (window.finesse && window.finesse.cslogger) { 26803 finesse.cslogger.ClientLogger.log(' [ShortcutKeyService] ' + params); 26804 } else { 26805 console.log(params); 26806 } 26807 } 26808 }, 26809 isFinesseContainer: function(){ 26810 //eslint-disable-next-line 26811 var container = window.finesse? window.finesse.container: null; 26812 //eslint-disable-next-line 26813 var gadget = window.finesse? window.finesse.gadget: null; 26814 return container && container.Config || gadget && gadget.Config 26815 } 26816 }; 26817 26818 }()); 26819 return Utilities; 26820 }); 26821 /** 26822 * ShortcutKeyStore singleton class which provides API store and mange shortcut key payload . 26823 */ 26824 define('shortcutkey/ShortcutKeyStore',['require','./Utilities'],function (require) { 26825 26826 var ShortcutKeyStore = (function () { 26827 var KEY_STORE = [], 26828 Utilities = require('./Utilities'), 26829 Const = Utilities.CONSTANTS, 26830 Logger = Utilities.Logger, 26831 26832 _isKeyAlreadyExists = function (newKey) { 26833 return KEY_STORE.filter(function (key) { 26834 return key.accessKey === newKey; 26835 }); 26836 }, 26837 // When gadget is reloaded , then igore the duplicate registration. 26838 _ignoreDuplicateRegistrationOnRealodGadget = function (newKeys) { 26839 26840 var duplicateRegistrations = 0; 26841 newKeys.forEach(function (newKey) { 26842 26843 KEY_STORE.forEach(function (existingKey, index) { 26844 // if there is gadget 26845 if(newKey.iframeId){ 26846 // If registrtion for the same key coming from same gadget then ignore the registration 26847 if(existingKey.iframeId === newKey.iframeId && existingKey.id === newKey.id) { 26848 Logger.log('Duplicate key registration found for gadget :' + existingKey.iframeId + ' and Key: '+existingKey.id); 26849 duplicateRegistrations++; 26850 } 26851 } 26852 }); 26853 }) 26854 return duplicateRegistrations > 0; 26855 }, 26856 _getShortcutKeyById = function (keyId) { 26857 return KEY_STORE.filter(function (shortcutKey) { 26858 return shortcutKey.accessKey.toLowerCase() === keyId.toLowerCase() 26859 }); 26860 }, 26861 26862 _markAsConflicted = function (alreadyExistedKeys) { 26863 alreadyExistedKeys.map(function (existingKey) { 26864 existingKey.conflict = true; 26865 }); 26866 }, 26867 26868 _updateConflictField = function (accessKey, newKey) { 26869 26870 var alreadyExistedKeys = _isKeyAlreadyExists(accessKey); 26871 26872 //get al the keys with activeFrame executionScope 26873 var allActiveFrameKeys = alreadyExistedKeys.filter(function(duplicateKey){ 26874 return duplicateKey.executionScope === Const.ACTIVE_FRAME; 26875 }); 26876 26877 // if all duplicate keys are within active frames , then there should not be any conflict; 26878 if(allActiveFrameKeys.length === alreadyExistedKeys.length && newKey.executionScope === Const.ACTIVE_FRAME){ 26879 return; 26880 } 26881 26882 //get all gadgets which are tab level of page level 26883 var anyPageOrTabLevlGadget = alreadyExistedKeys.filter(function(duplicateKey){ 26884 return duplicateKey.executionScope === Const.ACTIVE_TAB; 26885 }) 26886 26887 if(anyPageOrTabLevlGadget.length > 0 && newKey.executionScope === Const.ACTIVE_FRAME){ 26888 newKey.conflict = true; 26889 // mark rest of the gadget key as conflict 26890 _markAsConflicted(alreadyExistedKeys); 26891 return; 26892 } 26893 26894 // check if key already exits 26895 if (alreadyExistedKeys.length > 0) { 26896 26897 // if there is more then one key and new registration is page level then all duplicate keys will be in conflict state 26898 if (newKey.isPageLevel) { 26899 newKey.conflict = true; 26900 // mark rest of the gadget key as conflict 26901 _markAsConflicted(alreadyExistedKeys); 26902 } else { 26903 // Any existing gadget is in page Level 26904 var anyExistingPageLevelKey = alreadyExistedKeys.filter(function (existingKeys) { 26905 return existingKeys.isPageLevel; 26906 }) 26907 26908 // if any already registered key gadget is page level then mark all the key as conflict including new key 26909 if (anyExistingPageLevelKey.length > 0) { 26910 newKey.conflict = true; 26911 _markAsConflicted(anyExistingPageLevelKey); 26912 } else { 26913 // in case there is no conflict with page level gadget, then find gadgets which are in same tab 26914 var sameTabGadgetKeys = alreadyExistedKeys.filter(function (existingKey) { 26915 return newKey.tabId === existingKey.tabId; 26916 }); 26917 26918 if (sameTabGadgetKeys.length > 0) { 26919 sameTabGadgetKeys.forEach(function(sameTabGadget){ 26920 // if keys are not registered from the same gadget. and have conflict with other gadget 26921 if(sameTabGadget.componentName.replace(/\s/g,'') !== newKey.componentName.replace(/\s/g,'')){ 26922 // if keys are same, then mark it as conflict 26923 if(sameTabGadget.accessKey === newKey.accessKey){ 26924 newKey.conflict = true; 26925 _markAsConflicted(sameTabGadgetKeys); 26926 } 26927 } else { 26928 // if keys are conflicting within same gadget then mark as conflict 26929 if(newKey.iframeId === sameTabGadget.iframeId){ 26930 newKey.conflict = true; 26931 _markAsConflicted(sameTabGadgetKeys); 26932 } 26933 // if even one of them has execution scope as activeTab, it should be marked as conflict 26934 else if(sameTabGadget.executionScope === Const.ACTIVE_TAB || newKey.executionScope === Const.ACTIVE_TAB) { 26935 newKey.conflict = true; 26936 _markAsConflicted(sameTabGadgetKeys); 26937 } 26938 } 26939 }) 26940 } 26941 } 26942 } 26943 } 26944 }, 26945 26946 _getComponentTabID = function (componentID) { 26947 if(window.cd.core.componentsMetaData[componentID]) { 26948 return window.cd.core.componentsMetaData[componentID].hashVl; 26949 } 26950 }, 26951 26952 _addShortcutKey = function (newKeys) { 26953 if (Array.isArray(newKeys)) { 26954 // When only the gadget is reloaded , then ignore the duplicate registration. 26955 if (_ignoreDuplicateRegistrationOnRealodGadget(newKeys)) { 26956 return; 26957 } 26958 newKeys.forEach(function (newKey) { 26959 var conflict = 0; 26960 if (newKey instanceof Object) { 26961 26962 var accessKey = Utilities.constrcutSKStoreObjKey(newKey); 26963 newKey.accessKey = accessKey; 26964 newKey.conflict = false; 26965 26966 if (Utilities.isFinesseContainer()) { 26967 newKey.type = newKey.type === 'gadget' ? newKey.type : 'component'; 26968 26969 // currently all the the finesse component which has shortcut key suports can be page level only 26970 if (newKey.type === 'component') { 26971 if(newKey.executionScope === Const.ACTIVE_FRAME) { 26972 newKey.conflict = true; 26973 Logger.log("Shortcut key for " + JSON.stringify(newKey) + " cannot have activeFrame executionScope since it is a component"); 26974 } 26975 if(newKey.componentId && _getComponentTabID(newKey.componentId)) { 26976 newKey.tabId = _getComponentTabID(newKey.componentId).substr(2); 26977 } else { 26978 newKey.isPageLevel = true; 26979 } 26980 } 26981 26982 if (newKey.tabId) { 26983 newKey.tabLabel = document.querySelector('#leftNavUl [href^="#/' + newKey.tabId + '"]').getAttribute('title') 26984 } 26985 } 26986 26987 // update the conflict attribute as true if there is conflict. 26988 _updateConflictField(accessKey, newKey); 26989 KEY_STORE.push(newKey); 26990 26991 // Logic particular to finesse 26992 if (newKey.conflict === true && typeof (window.finesse.shortcutkey.ShortcutKeyService.updateIdentityMenu) === "function") { 26993 window.finesse.shortcutkey.ShortcutKeyService.updateIdentityMenu(true); 26994 } 26995 26996 //eslint-disable-next-line 26997 if (window.frameElement) { 26998 //eslint-disable-next-line 26999 newKey.iframeId = window.frameElement.id; 27000 //eslint-disable-next-line 27001 if (window.gadgets && window.gadgets.util) { 27002 //eslint-disable-next-line 27003 var navItemRoute = window.gadgets.util.getUrlParameters()['up_navItemRoute']; 27004 newKey.tabId = navItemRoute !== 'undefined' ? navItemRoute.substring(2, navItemRoute.length) : ''; 27005 newKey.isPageLevel = navItemRoute === 'undefined' ? true : false; 27006 newKey.type = 'gadget'; 27007 } 27008 27009 } 27010 27011 } else { 27012 throw Error('keys should be list of object'); 27013 } 27014 }); 27015 } else { 27016 Logger.log('keys should be array of objects'); 27017 } 27018 return newKeys; 27019 }, 27020 _getShortcutKeys = function () { 27021 return KEY_STORE; 27022 }; 27023 27024 return { 27025 _fakeConstuctor: function () {}, 27026 addShortcutKey: _addShortcutKey, 27027 getShortcutKeys: _getShortcutKeys, 27028 getShortcutKeyById: _getShortcutKeyById, 27029 init: function () { 27030 return this; 27031 } 27032 }; 27033 27034 }()); 27035 return ShortcutKeyStore; 27036 }); 27037 27038 define('shortcutkey/RegistrationParamValidator',['require','./Utilities'],function(require) { 27039 27040 var RegistrationParamValidator = (function() { 27041 var KEY_STORE = {}, 27042 Utilities = require('./Utilities'), 27043 Const = Utilities.CONSTANTS, 27044 ALLOWED_MODIFIERS = [ 27045 Const.MODIFIER_KEYS_CTRL_SHIFT, 27046 Const.MODIFIER_KEYS_ALT_SHIFT, 27047 Const.MODIFIER_KEYS_CTRL_ALT, 27048 Const.MODIFIER_KEYS_CTRL, 27049 Const.MODIFIER_KEYS_SHIFT, 27050 Const.MODIFIER_KEYS_ALT], 27051 ALLOWED_EXECUTION_SCOPES = [ Const.ACTIVE_TAB,Const.ACTIVE_FRAME ], 27052 RequiredRegParamFieldsSchema = ["id", "componentName", "actionName", "key", "handler" ], 27053 validateRequiredFields = function(param) { 27054 RequiredRegParamFieldsSchema.forEach(function(field) { 27055 if (!param.hasOwnProperty(field)) { 27056 throw Error(field + " is reuired field"); 27057 } 27058 }); 27059 }; 27060 return { 27061 _fakeConstuctor : function() { 27062 }, 27063 validateRequiredParameter : function(param) { 27064 validateRequiredFields(param); 27065 var paramFields = Object.keys(param); 27066 paramFields.forEach(function(field) { 27067 var value = param[field]; 27068 if (typeof value === 'string' && value.trim() === '') { 27069 throw Error(field + " can not have empty value"); 27070 } 27071 27072 if (field === 'handler' && typeof value !== 'function') { 27073 throw Error("handler must be a function"); 27074 } 27075 }); 27076 }, 27077 validateModifireKeys : function(keyParam) { 27078 if (ALLOWED_MODIFIERS.indexOf(Utilities 27079 .getModifierKeys(keyParam)) === -1) { 27080 throw Error('Invalid modifier key: '+keyParam.modifierKeys+', Allowed modifier keys: ' 27081 + JSON.stringify(ALLOWED_MODIFIERS)); 27082 } 27083 }, 27084 validateExecutionScopes : function(keyParam) { 27085 if(Utilities.isFinesseContainer()){ 27086 if (ALLOWED_EXECUTION_SCOPES.indexOf(keyParam.executionScope) === -1) { 27087 throw Error('Invalid executionScope. Allowed executionScope: ' 27088 + JSON.stringify(ALLOWED_EXECUTION_SCOPES)); 27089 } 27090 } 27091 } 27092 }; 27093 27094 }()); 27095 return RegistrationParamValidator; 27096 }); 27097 27098 /** 27099 * ShortcutKeyService API allows component/gadget to create and execute shortcut key for any action. 27100 * 27101 */ 27102 /** 27103 * The following comment is to prevent jslint errors about using variables 27104 * before they are defined. 27105 */ 27106 /* global window:true, define:true */ 27107 /* jslint nomen: true, unparam: true, sloppy: true, white: true */ 27108 define('shortcutkey/ShortcutKeyService',['require','exports','module','./ShortcutKeyStore','./Utilities','./RegistrationParamValidator'],function (require, exports, module) { 27109 27110 var ShortcutKeyService = (function () { 27111 /** @lends finesse.shortcutkey.ShortcutKeyService.prototype */ 27112 27113 var ShortcutKeyStore = require('./ShortcutKeyStore'), 27114 Utilities = require('./Utilities'), 27115 Const = Utilities.CONSTANTS, 27116 Logger = Utilities.Logger, 27117 Validator = require('./RegistrationParamValidator'), 27118 _initiated = false, 27119 27120 _getNonConflictedShortcutKey = function (matchedRegisteredKeys) { 27121 // get all the non conflicting keys matched with key press event key 27122 return matchedRegisteredKeys.filter(function (shortcutKey) { 27123 return shortcutKey.conflict !== true 27124 }); 27125 }, 27126 _validateInputParam = function (keys, Validator) { 27127 if(!Array.isArray(keys)){ 27128 throw Error('keys should be list of object'); 27129 } 27130 keys.forEach(function (key) { 27131 if (key instanceof Object) { 27132 if (!key.modifierKeys || key.modifierKeys.trim() === '') { 27133 key.modifierKeys = Const.MODIFIER_KEYS_CTRL_SHIFT; 27134 } 27135 27136 if (Utilities.isFinesseContainer()) { 27137 if (!key.executionScope || key.executionScope.trim() === '') { 27138 key.executionScope = Const.ACTIVE_TAB; 27139 } 27140 } 27141 27142 Validator.validateRequiredParameter(key); 27143 Validator.validateModifireKeys(key); 27144 Validator.validateExecutionScopes(key); 27145 } else { 27146 throw Error('keys should be list of object'); 27147 } 27148 27149 }); 27150 }, 27151 _cloneEvent = function (keyEvent) { 27152 return { 27153 ctrlKey: keyEvent.ctrlKey, 27154 altKey: keyEvent.altKey, 27155 shiftKey: keyEvent.shiftKey, 27156 keyCode: keyEvent.keyCode, 27157 key: String.fromCharCode(keyEvent.keyCode), 27158 which: keyEvent.which 27159 }; 27160 }, 27161 _getGadget = function (iframeId) { 27162 return document.getElementById(iframeId).contentWindow; 27163 }, 27164 /** 27165 * Executes the handler for particular keyevent 27166 * @param keyEvent 27167 */ 27168 _executeShortcutKey = function (keyEvent) { 27169 // Do not execute if the shortcut key feature is disabled 27170 if (Utilities.isFinesseContainer() && window.finesse.container.Config.enableShortCutKeys === 'false') { 27171 Logger.log("Shortcut Keys feature is disabled."); 27172 return; 27173 }; 27174 var keyId = Utilities.constructIdFromKeyEvents(keyEvent), 27175 iframeId, validShortcutKeyObj, 27176 matchedRegisteredKeys = ShortcutKeyStore.getShortcutKeyById(keyId); 27177 27178 if (matchedRegisteredKeys.length === 0) { 27179 Logger.log("No key registered for " + keyId ); 27180 return; 27181 } 27182 27183 // get non conflicted key only 27184 var nonConflictedKeys = _getNonConflictedShortcutKey(matchedRegisteredKeys); 27185 // if there is no key found without any conflict , log error. 27186 if (nonConflictedKeys.length === 0) { 27187 Logger.log('There are conflicts of shortcut keys: Following shortcut key will not work: ' + JSON.stringify(matchedRegisteredKeys)); 27188 return; 27189 } 27190 27191 // executionScope is only applicable for finesse based container 27192 if (finesse.container && finesse.container.Tabs) { 27193 const currentTab = finesse.container.Tabs.getActiveTab(); 27194 /** 27195 * currentTabKeys: Stores list of shortcut keys in the current tab. 27196 * 3 possibilities: 27197 * - current tab has one shortcut key registered with activeTab execution scope 27198 * - current tab has one shortcut key registered with activeFrame execution scope 27199 * - current tab has more than one gadgets with activeFrame execution scope 27200 * */ 27201 const currentTabKeys = nonConflictedKeys.filter(function(shortcutKey) { 27202 return shortcutKey.tabId === currentTab; 27203 }); 27204 /** 27205 * There could be more than one NON Conflicting keys if same gadget is included more than ones and 27206 * key executionScope is activeFrame. If there are more than one non conflicted keys then find the key of currently 27207 * focussed gadget IFrame key 27208 */ 27209 if(nonConflictedKeys.length > 1 && currentTabKeys.length > 1){ 27210 validShortcutKeyObj = nonConflictedKeys.filter(function(matched){ 27211 return matched.iframeId === document.activeElement.id; 27212 })[0]; 27213 27214 } else { 27215 if(currentTabKeys[0]) { 27216 validShortcutKeyObj = currentTabKeys[0]; 27217 } else { 27218 validShortcutKeyObj = nonConflictedKeys[0]; 27219 } 27220 } 27221 } else { 27222 /** 27223 * There could be more than one NON Conflicting keys if same gadget is included more than ones and 27224 * key executionScope is activeFrame. If there are more than one non conflicted keys then find the key of currently 27225 * focussed gadget IFrame key 27226 */ 27227 if(nonConflictedKeys.length > 1){ 27228 validShortcutKeyObj = nonConflictedKeys.filter(function(matched){ 27229 return matched.iframeId === document.activeElement.id; 27230 })[0]; 27231 } else { 27232 validShortcutKeyObj = nonConflictedKeys[0]; 27233 } 27234 } 27235 27236 if(!validShortcutKeyObj){ 27237 Logger.log("Shortcut key for " + JSON.stringify(keyEvent) + ", will only work if that gadget has active focus"); 27238 return; 27239 } 27240 iframeId = validShortcutKeyObj.iframeId; 27241 // executionScope is only applicable for finesse based container 27242 if (finesse.container && finesse.container.Tabs) { 27243 /** 27244 * Stop the execution of shortcut key if gadget has executionScope as activeFrame and 27245 * the particular gadget is not in focus 27246 * */ 27247 if (validShortcutKeyObj.executionScope === Const.ACTIVE_FRAME && validShortcutKeyObj.iframeId !== document.activeElement.id) { 27248 Logger.log("Shortcut key for " + JSON.stringify(validShortcutKeyObj) + ", will only work if that gadget has active focus"); 27249 return 27250 } else { 27251 /** 27252 * Stop the execution of shortcut key if gadget has executionScope as activeTab and 27253 * current active tab is not matching with the tab where gadget is placed 27254 * */ 27255 if (validShortcutKeyObj.tabId !== finesse.container.Tabs.getActiveTab() && !validShortcutKeyObj.isPageLevel) { 27256 Logger.log("Shortcut key for " + JSON.stringify(validShortcutKeyObj) + " gadget, will not work. The gadget is not present in current active tab"); 27257 return 27258 } 27259 } 27260 } 27261 27262 /** 27263 * If the shortcut key is defined for any gadget (i.e. iframe), 27264 * then post the message to that iframe only to invoke the callbak method inside the iframe only. 27265 */ 27266 if (iframeId) { 27267 var message = { 27268 type: 'execute_shotcutkey_handler', 27269 keyEvent: _cloneEvent(keyEvent) 27270 }; 27271 Logger.log('Asking gadget to execute shortcut key handler : ' + iframeId); 27272 _getGadget(iframeId).postMessage(message, '*'); 27273 } else { 27274 Logger.log('Container is executing shortcut key handler for ' + validShortcutKeyObj.componentName + ' : ' + validShortcutKeyObj.actionName); 27275 /** 27276 * If any shortcut key created at container level(e.g. component) then invoke the handler directly 27277 */ 27278 validShortcutKeyObj.handler(keyEvent); 27279 } 27280 }, 27281 _keyupEventHandler = function (keyEvent) { 27282 if (_validModifierKeysPressed(keyEvent)) { 27283 // If key is pressed with iframe in focus , send the message to parent 27284 //eslint-disable-next-line 27285 if (window !== top) { 27286 var message = { 27287 type: 'finesse_keyup_captured', 27288 keyEvent: _cloneEvent(keyEvent) 27289 }; 27290 //eslint-disable-next-line 27291 parent.postMessage(message, "*"); 27292 } else { 27293 _executeShortcutKey(keyEvent); 27294 } 27295 } 27296 }, 27297 _messageHandler = function (e) { 27298 var key = e.message ? "message" : "data"; 27299 var data = e[key]; 27300 if (data.type === 'register_shortcut_key') { 27301 ShortcutKeyStore.addShortcutKey(JSON.parse(data.keys)); 27302 } else if (data.type == 'finesse_keyup_captured') { 27303 _executeShortcutKey(data.keyEvent); 27304 } else if (data.type == 'execute_shotcutkey_handler') { 27305 var keyId = Utilities.constructIdFromKeyEvents(data.keyEvent); 27306 var matchedRegisteredKeys = ShortcutKeyStore.getShortcutKeyById(keyId); 27307 // get non conflicted key only 27308 var nonConflictedKeys = _getNonConflictedShortcutKey(matchedRegisteredKeys); 27309 if(nonConflictedKeys.length > 0){ 27310 validShortcutKeyObj = nonConflictedKeys[0]; 27311 Logger.log('Gadget is executing shortcut key handler for ' + validShortcutKeyObj.componentName + ' : ' + validShortcutKeyObj.actionName); 27312 validShortcutKeyObj.handler(data.keyEvent); 27313 } 27314 27315 } 27316 }, 27317 _validModifierKeysPressed = function (keyEvent) { 27318 return ((keyEvent.ctrlKey && keyEvent.shiftKey) || 27319 (keyEvent.altKey && keyEvent.shiftKey) || 27320 (keyEvent.ctrlKey && keyEvent.altKey) || 27321 (keyEvent.ctrlKey) || (keyEvent.altKey) || (keyEvent.shiftKey)) && 27322 (keyEvent.key !== 'Shift' && keyEvent.key !== 'Control' && keyEvent.key !== 'Alt') 27323 }, 27324 _sendKeyupEvent = function (keyEvent) { 27325 var message = { 27326 type: 'finesse_keyup_captured', // propagate the event to finesse container on finesse_keyup_captured topic 27327 keyEvent: { 27328 ctrlKey: keyEvent.ctrlKey, 27329 altKey: keyEvent.altKey, 27330 shiftKey: keyEvent.shiftKey, 27331 keyCode: keyEvent.keyCode, 27332 key: String.fromCharCode(keyEvent.keyCode), 27333 which: keyEvent.which 27334 } 27335 }; 27336 //eslint-disable-next-line 27337 parent.postMessage(message, "*"); 27338 }; 27339 27340 return { 27341 27342 /** 27343 * @class ShortcutKeyService allows components or gadgets to create 27344 * shortcut keys for any component or gadget related actions. 27345 * <style> 27346 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 27347 * .schemaTable th { background-color: #999999; } 27348 * .schemaTable th, td { border: none; } 27349 * .pad30 {padding-left: 30px;} 27350 * .pad60 {padding-left: 60px;} 27351 * .inset { 27352 * padding: 5px; 27353 * border-style: inset; 27354 * background-color: #DDDDDD; 27355 * width: auto; 27356 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 27357 * font: 12px arial, sans-serif; 27358 * color:rebeccapurple; 27359 * } 27360 * </style> 27361 * 27362 * @constructs 27363 */ 27364 _fakeConstuctor: function () { 27365 /* 27366 * This is here so we can document init() as a method rather 27367 * than as a constructor. 27368 */ 27369 }, 27370 27371 /** 27372 * This method is used to register the shortcut keys for the components or the gadgets 27373 * 27374 * @param keys - 27375 * Array of keys object: 27376 * @example 27377 * var arr = [ { 27378 * "id": "cisco_callhandling_endCall" 27379 * "componentName": "Call Control", 27380 * "actionName": "End Call", 27381 * "modifierKeys": "ctrlKey + shiftKey", 27382 * "key": "e", 27383 * "handler": function () {} 27384 * }]; 27385 27386 * finesse.shortcutkey.ShortcutKeyService.registerShortcutKey(arr); 27387 * </pre> 27388 * <br/><br/> 27389 * 27390 * <h3>Shortcut Key registration payload details</h3> 27391 * 27392 * <table class="schemaTable"> 27393 * <thead> 27394 * <tr><th>Attribute</th><th>Type</th><th>Description</th><th>Required</th></tr> 27395 * </thead> 27396 * <tbody> 27397 * <tr><td>id</td><td>String</td><td>A unique id generated by gadget. The pattern for this id can be companyName_gadgetId_operation. </td><td>Required</td></tr> 27398 * <tr><td>componentName</td><td>String</td><td>Name of the functionality, component, or the gadget.</td><td>Required</td></tr> 27399 * <tr><td>actionName</td><td>String</td><td>Name of the action or operation performed by the assigned shortcut keys.</td><td>Required</td></tr> 27400 * <tr><td>key</td><td>String</td><td>Actual key combined with the modifier keys. For example, Ctrl + Shift + e where Ctrl and Shift are the modifiers keys and e is the actual key.</td><td>Required</td></tr> 27401 * <tr><td>executionScope</td><td>ENUM</td><td><ul> 27402 <li><i><b>ShortcutKeyService.CONSTANTS.EXECUTION_SCOPE.ACTIVE_TAB</b></i>: If the gadget is in the currently active tab, only then the shortcut keys of that gadget are executed.</li> 27403 <li><i><b>ShortcutKeyService.CONSTANTS.EXECUTION_SCOPE.ACTIVE_FRAME</b></i>: If the gadget is in focus, only then the shortcut keys of that gadget are executed.</li> 27404 </ul></td> 27405 <td>Required</td> 27406 * </tr> 27407 * <tr><td>modifierKeys</td><td>ENUM</td><td> 27408 * Keybaord modifier key combinations. 27409 * <br/>finesse.shortcutkey.ShortcutKeyService.CONSTANTS.MODIFIER_KEYS.* <ul><li>CTRL_SHIFT</li><li>ALT_SHIFT</li><li>CTRL_ALT</li><li>CTRL</li><li>SHIFT</li><li>ALT</li></ul></td><td>Optional</td></tr> 27410 * <tr><td>callback</td><td>function</td><td>Callback function that is invoked when the shortcut keys are pressed.</td><td>Required</td></tr> 27411 * </tbody> 27412 * </table> 27413 */ 27414 registerShortcutKey: function (keys) { 27415 27416 if (!_initiated) { 27417 throw Error('ShortcutKeyService not intialized. Invoke init method to initialize'); 27418 } 27419 27420 // added to prevent double registration of shortcut keys during failover from autopilot iframe 27421 if(window.frameElement && window.frameElement.id === "autopilot-frame") { 27422 return; 27423 } 27424 27425 _validateInputParam(keys, Validator); 27426 keys = ShortcutKeyStore.addShortcutKey(keys); 27427 // Called from an iframe 27428 //eslint-disable-next-line 27429 if (window !== top) { 27430 var message = { 27431 type: 'register_shortcut_key', 27432 keys: JSON.stringify(keys) 27433 }; 27434 //eslint-disable-next-line 27435 parent.postMessage(message, "*"); 27436 } 27437 27438 }, 27439 /** 27440 * This method allows you to get all the registered shortcut keys. 27441 * @returns {Array} array of objects 27442 */ 27443 getShorcutKeys: function () { 27444 var registerKeys = ShortcutKeyStore.getShortcutKeys(); 27445 return registerKeys.slice(0); 27446 }, 27447 27448 /** 27449 * @private 27450 * @returns {Array} of conflict keys 27451 */ 27452 getConflictShortcutKeys: function() { 27453 return ShortcutKeyService.getShorcutKeys().filter(function(shortcutKey) { 27454 return shortcutKey.conflict === true; 27455 }); 27456 }, 27457 27458 /** 27459 * This method initializes the ShortcutKeyService for the Container or the gadgets. 27460 */ 27461 init: function () { 27462 if (!_initiated) { 27463 // Attaching keydown event listener only in case if shortcut key is used in non finesse environment 27464 //eslint-disable-next-line 27465 if (window.sessionStorage.getItem('finesseSkeyInitiated') !== 'true') { 27466 // The reason to use keydown event is that, keyborad event object keyCode and charCodeAt(0) always matches 27467 document.addEventListener('keyup', _keyupEventHandler, true); 27468 } 27469 //eslint-disable-next-line 27470 window.addEventListener("message", _messageHandler, false); 27471 _initiated = true; 27472 } 27473 return _initiated; 27474 }, 27475 /** 27476 * If there is any custom iframe created by the gadget that is not controlled by Finesse, 27477 * the Finesse shortcut key framework cannot capture the keyup event from that custom iframe to execute the shortcut keys. 27478 * This method can be used to send the Keyup event object to the Finesse container. 27479 * The keyup event object has to be captured inside the child iFrame and propagated to its immediate parent. 27480 * The parent again has to propagate the event to its immediate parent until the event reaches the Finesse container. 27481 * Once the immediate parent is the Finesse container, then use sendKeyupEvent to propagate the event to Finesse container. 27482 * Param object has to be serializable and cannot contain any functions. 27483 * 27484 * @param keyEvent - Object 27485 * 27486 * @example 27487 * var keyEvent = { 27488 * ctrlKey: event.ctrlKey, 27489 * altKey: event.altKey, 27490 * shiftKey: event.shiftKey, 27491 * keyCode: event.keyCode, 27492 * key: event.key 27493 * } 27494 * 27495 */ 27496 sendKeyupEvent: function(keyEvent){ 27497 _sendKeyupEvent(keyEvent) 27498 }, 27499 CONSTANTS: { 27500 MODIFIER_KEYS: { 27501 CTRL_SHIFT: Const.MODIFIER_KEYS_CTRL_SHIFT, 27502 ALT_SHIFT: Const.MODIFIER_KEYS_ALT_SHIFT, 27503 CTRL_ALT: Const.MODIFIER_KEYS_CTRL_ALT, 27504 CTRL: Const.MODIFIER_KEYS_CTRL, 27505 SHIFT: Const.MODIFIER_KEYS_SHIFT, 27506 ALT: Const.MODIFIER_KEYS_ALT 27507 }, 27508 EXECUTION_SCOPE: { 27509 ACTIVE_TAB: Const.ACTIVE_TAB, 27510 ACTIVE_FRAME: Const.ACTIVE_FRAME 27511 } 27512 } 27513 27514 }; 27515 }()); 27516 //eslint-disable-next-line 27517 window.finesse = window.finesse || {}; 27518 //eslint-disable-next-line 27519 window.finesse.shortcutkey = window.finesse.shortcutkey || {}; 27520 //eslint-disable-next-line 27521 window.finesse.shortcutkey.ShortcutKeyService = ShortcutKeyService; 27522 27523 // CommonJS 27524 if (typeof module === "object" && module.exports) { 27525 module.exports = ShortcutKeyService; 27526 } 27527 27528 return ShortcutKeyService; 27529 }); 27530 27531 /** 27532 * @fileOverview This service will be initialized by the container and thereafter will be 27533 * available to the gadgets to interact with the IndexedDB cache. 27534 */ 27535 27536 define('utilities/RestCacheDataService',[], function () { 27537 var RestCacheDataService = (function () { 27538 /** @lends finesse.utilities.DesktopCache.prototype */ 27539 var logger = window.finesse.cslogger.ClientLogger; 27540 var listeners = {}; 27541 var group = null; 27542 var utils = window.finesse.utilities.Utilities; 27543 var IDB_ACTIONS = utils.IDB_ACTIONS; 27544 var IDB_TOPICS = utils.IDB_TOPICS; 27545 27546 // returns the gadgetId to use as a reference 27547 var _getGadetId = function () { 27548 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 27549 return gadgets.util.getUrlParameters().mid; 27550 } 27551 } 27552 27553 /** 27554 * Sends a post message from the gadget to the container to interact with the IDB cache. 27555 * @param requestId is a random ID generated by the gadget to identify a request to interact with the IDB cache. 27556 * @param action is the type of interaction, check window.finesse.utilities.Utilities.IDB_ACTIONS for a list of available actions. 27557 * @data is the payload to send with the request. 27558 */ 27559 var _sendMessage = function (requestId, action, data) { 27560 logger.log('Sending request to gadget with gadgetid: ' + _getGadetId() + ' and requestid: ' + requestId); 27561 var message = {}; 27562 message[IDB_TOPICS.REQUEST] = { 27563 group: group ? group : _getGadetId(), 27564 requestId: requestId, 27565 gadgetId: _getGadetId(), 27566 data: data, 27567 action: action 27568 }; 27569 parent.postMessage(message, '*'); 27570 } 27571 // listens to the resonse sent by the container to the request sent by the gadgets and notifies the listeners. 27572 window.addEventListener('message', function (e) { 27573 if (e.data.hasOwnProperty(IDB_TOPICS.RESPONSE) && listeners[e.data[IDB_TOPICS.RESPONSE].requestId]){ 27574 var response = e.data[IDB_TOPICS.RESPONSE]; 27575 logger.log('Recieved response from gadget with gadgetid: ' + _getGadetId() + ' and requestid: ' + response.requestId); 27576 listeners[response.requestId](response.error, response.data); 27577 delete listeners[response.requestId]; 27578 } 27579 }); 27580 return { 27581 /** 27582 * @class RestCacheDataService allows gadgets to cache there data locally 27583 * in IndexedDB that can be available across the failover on the other side without even making 27584 * a REST call. 27585 * <style> 27586 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 27587 * .schemaTable th { background-color: #999999; } 27588 * .schemaTable th, td { border: none; } 27589 * .pad30 {padding-left: 30px;} 27590 * .pad60 {padding-left: 60px;} 27591 * .inset { 27592 * padding: 5px; 27593 * border-style: inset; 27594 * background-color: #DDDDDD; 27595 * width: auto; 27596 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 27597 * font: 12px arial, sans-serif; 27598 * color:rebeccapurple; 27599 * } 27600 * </style> 27601 * 27602 * @constructs 27603 */ 27604 _fakeConstuctor: function () { 27605 /* 27606 * This is here so we can document init() as a method rather 27607 * than as a constructor. 27608 */ 27609 }, 27610 27611 /** 27612 * Different gadgets from the same vendor can choose to keep the same group to reduce the redundancy in the data stored. 27613 * The group name set here will be used as a prefix to the key while saving that record. 27614 * @param groupName is the name which would prefix the keys while saving in the cache. 27615 */ 27616 setGroup: function (groupName) { 27617 if(!group) { 27618 group = groupName; 27619 } else { 27620 logger.log('Group already set to ' + group + '. Cannot set again.'); 27621 } 27622 }, 27623 /** 27624 * Save or update a single or multiple records in the DB. 27625 * @param {Array} records is an array containing the data to be saved in the DB. It will always 27626 * be an array even if there is just one record that has to be saved/updated. 27627 * @param callback is a callback function to be passed as all the indexeddb operations are 27628 * asynchronous in nature. 27629 * @example 27630 * finesse.utilities.DesktopCache.saveOrUpdateData({[ 27631 * key: 'someKey' 27632 * data: 'someData' 27633 * ]}, function (err, data){ 27634 * if (!err) // do something 27635 * }); 27636 */ 27637 saveOrUpdateData: function saveOrUpdateData(records, callback) { 27638 var requestId = utils.generateUUID(); 27639 listeners[requestId] = callback; 27640 _sendMessage(requestId, IDB_ACTIONS.SAVE_OR_UPDATE, records); 27641 }, 27642 27643 /** 27644 * Fetch a single or all records from DB. 27645 * @param key is through which one can retrieve a single record. Key should be unique, it is like a primary key. 27646 * @param callback is the callback to be called in case of successful/error fetching of the data 27647 * @example 27648 * finesse.utilities.DesktopCache.fetchData('someKey', function (err, data){ 27649 * if (!err) // do something 27650 * }); 27651 */ 27652 fetchData: function fetchData(key, callback) { 27653 var requestId = utils.generateUUID(); 27654 listeners[requestId] = callback; 27655 _sendMessage(requestId, IDB_ACTIONS.FETCH, key); 27656 }, 27657 27658 /** 27659 * Delete a record from DB. 27660 * @param key is the Rest name which is used to identify the record to be deleted. 27661 * @param callback is a callback function to be passed as all the indexeddb operations are 27662 * asynchronous in nature. 27663 */ 27664 deleteData: function _delete(key, callback) { 27665 var requestId = utils.generateUUID(); 27666 listeners[requestId] = callback; 27667 _sendMessage(requestId, IDB_ACTIONS.DELETE, key); 27668 }, 27669 27670 /** 27671 * Clear all the records in the DB. 27672 * @param callback is a callback function to be passed as all the indexeddb operations are 27673 * asynchronous in nature. 27674 */ 27675 clearData: function clearData(callback) { 27676 var requestId = utils.generateUUID(); 27677 listeners[requestId] = callback; 27678 _sendMessage(requestId, IDB_ACTIONS.CLEAR_ALL); 27679 } 27680 } 27681 27682 }()); 27683 27684 window.finesse = window.finesse || {}; 27685 window.finesse.utilities = window.finesse.utilities || {}; 27686 window.finesse.utilities.DesktopCache = RestCacheDataService; 27687 return RestCacheDataService; 27688 }); 27689 27690 define('finesse',[ 27691 'restservices/Users', 27692 'restservices/Teams', 27693 'restservices/SystemInfo', 27694 'restservices/Media', 27695 'restservices/MediaDialogs', 27696 'restservices/DialogLogoutActions', 27697 'restservices/InterruptActions', 27698 'restservices/ReasonCodeLookup', 27699 'restservices/ChatConfig', 27700 'restservices/ECCVariableConfig.js', 27701 'utilities/I18n', 27702 'utilities/Logger', 27703 'utilities/SaxParser', 27704 'utilities/BackSpaceHandler', 27705 'utilities/JsonValidator', 27706 'cslogger/ClientLogger', 27707 'cslogger/FinesseLogger', 27708 'containerservices/ContainerServices', 27709 'containerservices/FinesseToaster', 27710 'interfaces/RestObjectHandlers', 27711 'interfaces/RequestHandlers', 27712 'gadget/Config', 27713 'digital/ChannelSchema', 27714 'digital/ChannelService', 27715 'containerservices/PopoverService', 27716 'containerservices/PopoverSchema', 27717 'workflow/WorkflowSchema', 27718 'workflow/WorkflowService', 27719 'restservices/UserTeamMessages', 27720 'shortcutkey/ShortcutKeyService', 27721 'utilities/RestCacheDataService' 27722 ], 27723 function () { 27724 /** 27725 * below object is set to true only if desktop url has query parameter 'bypassServerCache' 27726 * This object is being used in RestBase.js. Based on this, GET request will have a query parameter 'bypassServerCache=true' so that webproxy pass the request to finesse server 27727 */ 27728 window.finesse.restservices.bypassServerCache = (function () { 27729 if ( window.document.referrer ) { 27730 return window.document.referrer.indexOf('bypassServerCache=true') > -1 ? true : false; 27731 } else { 27732 return window.location.href.indexOf('bypassServerCache=true') > -1 ? true : false; 27733 } 27734 return false; 27735 }()); 27736 return window.finesse; 27737 }); 27738 27739 require(["finesse"]); 27740 return require('finesse'); })); 27741 27742 // Prevent other JS files from wiping out window.finesse from the namespace 27743 var finesse = window.finesse;