1 /**
  2  * @fileOverview Exposes a set of API wrappers that will hide the dirty work of
  3  *     constructing Finesse API requests and consuming Finesse events.
  4  *
  5  * @name finesse.clientservices.ClientServices
  6  * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities
  7  */
  8 
  9 var finesse = finesse || {};
 10 finesse.version = '$version$';
 11 finesse.clientservices = finesse.clientservices  || {};
 12 /**
 13  * @class
 14  * Allow clients to make Finesse API requests and consume Finesse events by
 15  * calling a set of exposed functions. The Services layer will do the dirty
 16  * work of establishing a shared BOSH connection (for designated Master
 17  * modules), consuming events for client subscriptions, and constructing API
 18  * requests.
 19  */
 20 finesse.clientservices.ClientServices = (function () {
 21 
 22     var
 23 
 24     /**
 25      * Shortcut reference to the master tunnel
 26      * @private
 27      */
 28     _tunnel,
 29 
 30     _publisher,
 31 
 32     /**
 33      * Shortcut reference to the finesse.utilities.Utilities singleton
 34      * This will be set by init()
 35      * @private
 36      */
 37     _util,
 38 
 39     /**
 40      * Shortcut reference to the gadgets.io object.
 41      * This will be set by init()
 42      * @private
 43      */
 44     _io,
 45 
 46     /**
 47      * Shortcut reference to the gadget pubsub Hub instance.
 48      * This will be set by init()
 49      * @private
 50      */
 51     _hub,
 52 
 53     /**
 54      * Logger object set externally by setLogger, defaults to nothing.
 55      * @private
 56      */
 57 	_logger = {},
 58 
 59     /**
 60      * Shortcut reference to the Topics class.
 61      * This will be set by init()
 62      * @private
 63      */
 64     _topics,
 65 
 66     /**
 67      * Config object needed to initialize this library
 68      * This must be set by init()
 69      * @private
 70      */
 71     _config,
 72 
 73     /**
 74      * @private
 75      * Whether or not this ClientService instance is a Master.
 76      */
 77     _isMaster = false,
 78 
 79     /**
 80      * @private
 81      * Whether the Client Services have been initiated yet.
 82      */
 83     _inited = false,
 84 
 85     /**
 86      * Stores the list of subscription IDs for all subscriptions so that it
 87      * could be retrieve for unsubscriptions.
 88      * @private
 89      */
 90     _subscriptionID = {},
 91 
 92     /**
 93      * The possible states of the JabberWerx BOSH connection.
 94      * @private
 95      */
 96     _STATUS = {
 97         CONNECTING: "connecting",
 98         CONNECTED: "connected",
 99         DISCONNECTED: "disconnected",
100         DISCONNECTED_CONFLICT: "conflict",
101         DISCONNECTED_UNAUTHORIZED: "unauthorized",
102         DISCONNECTING: "disconnecting",
103         RECONNECTING: "reconnecting",
104         UNLOADING: "unloading"
105     },
106 
107     /**
108      * Handler function to be invoked when BOSH connection is connecting.
109      * @private
110      */
111     _onConnectingHandler,
112 
113     /**
114      * Handler function to be invoked when BOSH connection is connected
115      * @private
116      */
117     _onConnectHandler,
118 
119     /**
120      * Handler function to be invoked when BOSH connection is disconnecting.
121      * @private
122      */
123     _onDisconnectingHandler,
124 
125     /**
126      * Handler function to be invoked when the BOSH is disconnected.
127      * @private
128      */
129     _onDisconnectHandler,
130 
131     /**
132      * Handler function to be invoked when the BOSH is reconnecting.
133      * @private
134      */
135     _onReconnectingHandler,
136     
137     /**
138      * Handler function to be invoked when the BOSH is unloading.
139      * @private
140      */
141     _onUnloadingHandler,
142 
143     /**
144      * Contains a cache of the latest connection info containing the current
145      * state of the BOSH connection and the resource ID.
146      * @private
147      */
148     _connInfo,
149 
150     /**
151      * Keeps track of all the objects that need to be refreshed when we recover
152      * due to our resilient connection. Only objects that we subscribe to will
153      * be added to this list.
154      */
155     _refreshList = [],
156 
157     /**
158      * @private
159      * Centralized logger.log method for external logger
160      * @param {String} msg
161      *     Message to log
162      */
163 	_log = function (msg) {
164 		// If the external logger throws up, it stops here.
165 		try {
166 			if (_logger.log) {
167 				_logger.log("[ClientServices] " + msg);
168 			}
169 		} catch (e) { }
170 	},
171 
172     /**
173      * Go through each object in the _refreshList and call its refresh() function
174      */
175     _refreshObjects = function () {
176         var i;
177 
178         // wipe out the explicit subscription list before we refresh objects
179         if (_publisher) {
180             _publisher.wipeout();
181         }
182 
183         // refresh each item in the refresh list
184         for (i = _refreshList.length - 1; i >= 0; i -= 1) {
185             _log("Refreshing " + _refreshList[i].getRestUrl());
186             _refreshList[i].refresh();
187         }
188     },
189 
190     /**
191      * Handler to process connection info publishes.
192      * @param {Object} data
193      *     The connection info data object.
194      * @param {String} data.status
195      *     The BOSH connection status.
196      * @param {String} data.resourceID
197      *     The resource ID for the connection.
198      * @private
199      */
200     _connInfoHandler =  function (data) {
201 
202         //Invoke registered handler depending on status received. Due to the
203         //request topic where clients can make request for the Master to publish
204         //the connection info, there is a chance that duplicate connection info
205         //events may be sent, so ensure that there has been a state change
206         //before invoking the handlers.
207         if (_connInfo === undefined || _connInfo.status !== data.status) {
208             _connInfo = data;
209             switch (data.status) {
210             case _STATUS.CONNECTING:
211                 if (_onConnectingHandler) {
212                     _onConnectingHandler();
213                 }
214                 break;
215             case _STATUS.CONNECTED:
216                 // Whenever we are connected, we need to refresh any objects
217                 // that are stored.
218                 _refreshObjects();
219                 if (_onConnectHandler) {
220                     _onConnectHandler();
221                 }
222                 break;
223             case _STATUS.DISCONNECTED:
224                 if (_onDisconnectHandler) {
225                     _onDisconnectHandler();
226                 }
227                 break;
228             case _STATUS.DISCONNECTED_CONFLICT:
229                 if (_onDisconnectHandler) {
230                     _onDisconnectHandler("conflict");
231                 }
232                 break;
233             case _STATUS.DISCONNECTED_UNAUTHORIZED:
234                 if (_onDisconnectHandler) {
235                     _onDisconnectHandler("unauthorized");
236                 }
237                 break;
238             case _STATUS.DISCONNECTING:
239                 if (_onDisconnectingHandler) {
240                     _onDisconnectingHandler();
241                 }
242                 break;
243             case _STATUS.RECONNECTING:
244                 if (_onReconnectingHandler) {
245                     _onReconnectingHandler();
246                 }
247                 break;
248             case _STATUS.UNLOADING:
249                 if (_onUnloadingHandler) {
250                     _onUnloadingHandler();
251                 }
252                 break;
253             }
254         }
255     },
256 
257     /**
258      * Ensure that ClientServices have been inited.
259      * @private
260      */
261     _isInited = function () {
262         if (!_inited) {
263             throw new Error("ClientServices needs to be inited.");
264         }
265     },
266 
267     /**
268      * Have the client become the Master by initiating a tunnel to a shared
269      * event BOSH connection. The Master is responsible for publishing all
270      * events to the pubsub infrastructure.
271      * @private
272      */
273     _becomeMaster = function () {
274         _tunnel = new finesse.clientservices.MasterTunnel(_config.host, _config.scheme);
275         _publisher = new finesse.clientservices.MasterPublisher(_tunnel);
276         _tunnel.init(_config.id, _config.password, _config.xmppDomain, _config.pubsubDomain, _config.resource);
277         _isMaster = true;
278     },
279 
280     /**
281      * Make a request to the request channel to have the Master publish the
282      * connection info object.
283      * @private
284      */
285     _makeConnectionInfoReq = function () {
286         var data = {
287             type: "ConnectionInfoReq",
288             data: {},
289             invokeID: (new Date()).getTime()
290         };
291         _hub.publish(_topics.REQUESTS, data);
292     },
293 
294     /**
295      * Utility method to register a handler which is associated with a
296      * particular connection status.
297      * @param {String} status
298      *     The connection status string.
299      * @param {Function} handler
300      *     The handler to associate with a particular connection status.
301      * @throws {Error}
302      *     If the handler provided is not a function.
303      * @private
304      */
305     _registerHandler = function (status, handler) {
306         if (typeof handler === "function") {
307             if (_connInfo && _connInfo.status === status) {
308                 handler();
309             }
310             switch (status) {
311             case _STATUS.CONNECTING:
312                 _onConnectingHandler = handler;
313                 break;
314             case _STATUS.CONNECTED:
315                 _onConnectHandler = handler;
316                 break;
317             case _STATUS.DISCONNECTED:
318                 _onDisconnectHandler = handler;
319                 break;
320             case _STATUS.DISCONNECTING:
321                 _onDisconnectingHandler = handler;
322                 break;
323             case _STATUS.RECONNECTING:
324                 _onReconnectingHandler = handler;
325                 break;
326             case _STATUS.UNLOADING:
327                 _onUnloadingHandler = handler;
328                 break;
329             }
330 
331         } else {
332             throw new Error("Callback is not a function");
333         }
334     };
335 
336     return {
337 
338         /**
339          * Adds an item to the list to be refreshed upon reconnect
340          * @param {RestBase} object - rest object to be refreshed
341          */
342         addToRefreshList: function (object) {
343             _refreshList.push(object);
344         },
345 
346         /**
347          * Removes the given item from the refresh list
348          * @param  {RestBase} object - rest object to be removed
349          */
350         removeFromRefreshList: function (object) {
351             var i;
352             for (i = _refreshList.length - 1; i >= 0; i -= 1) {
353                 if (_refreshList[i] === object) {
354                     _refreshList.splice(i, 1);
355                     break;
356                 }
357             }
358         },
359 
360         /**
361          * The location of the tunnel HTML URL.
362          * @returns {String}
363          *     The location of the tunnel HTML URL.
364          */
365         getTunnelURL: function () {
366             return _tunnel.getTunnelURL();            
367         },
368         
369         /**
370          * @private
371          * Indicates whether the tunnel frame is loaded.
372          * @returns {Boolean}
373          *     True if the tunnel frame is loaded, false otherwise.
374          */
375         isTunnelLoaded: function () {
376             return _tunnel.isTunnelLoaded();            
377         },
378         
379         /**
380          * @private
381          * Indicates whether the ClientServices instance is a Master.
382          * @returns {Boolean}
383          *     True if this instance of ClientServices is a Master, false otherwise.
384          */
385         isMaster: function () {
386             return _isMaster;
387         },
388 
389         /**
390          * @private
391          * Get the resource ID. An ID is only available if the BOSH connection has
392          * been able to connect successfully.
393          * @returns {String}
394          *     The resource ID string. Null if the BOSH connection was never
395          *     successfully created and/or the resource ID has not been associated.
396          */
397         getResourceID: function () {
398             if (_connInfo !== undefined) {
399                 return _connInfo.resourceID;
400             }
401             return null;
402         },
403 		
404 		/*
405 		getHub: function () {
406 			return _hub;
407 		},
408 	*/
409         /**
410          * @private
411          * Add a callback to be invoked when the BOSH connection is attempting
412          * to connect. If the connection is already trying to connect, the
413          * callback will be invoked immediately.
414          * @param {Function} handler
415          *      An empty param function to be invoked on connecting. Only one
416          *      handler can be registered at a time. Handlers already registered
417          *      will be overwritten.
418          */
419         registerOnConnectingHandler: function (handler) {
420             _registerHandler(_STATUS.CONNECTING, handler);
421         },
422 
423         /**
424          * @private
425          * Removes the on connecting callback that was registered.
426          */
427         unregisterOnConnectingHandler: function () {
428             _onConnectingHandler = undefined;
429         },
430 
431         /**
432          * @private
433          * Add a callback to be invoked when the BOSH connection has been
434          * established. If the connection has already been established, the
435          * callback will be invoked immediately.
436          * @param {Function} handler
437          *      An empty param function to be invoked on connect. Only one handler
438          *      can be registered at a time. Handlers already registered will be
439          *      overwritten.
440          */
441         registerOnConnectHandler: function (handler) {
442             _registerHandler(_STATUS.CONNECTED, handler);
443         },
444 
445         /**
446          * @private
447          * Removes the on connect callback that was registered.
448          */
449         unregisterOnConnectHandler: function () {
450             _onConnectHandler = undefined;
451         },
452 
453         /**
454          * @private
455          * Add a callback to be invoked when the BOSH connection goes down. If
456          * the connection is already down, invoke the callback immediately.
457          * @param {Function} handler
458          *      An empty param function to be invoked on disconnected. Only one
459          *      handler can be registered at a time. Handlers already registered
460          *      will be overwritten.
461          */
462         registerOnDisconnectHandler: function (handler) {
463             _registerHandler(_STATUS.DISCONNECTED, handler);
464         },
465 
466         /**
467          * @private
468          * Removes the on disconnect callback that was registered.
469          */
470         unregisterOnDisconnectHandler: function () {
471             _onDisconnectHandler = undefined;
472         },
473 
474         /**
475          * @private
476          * Add a callback to be invoked when the BOSH is currently disconnecting. If
477          * the connection is already disconnecting, invoke the callback immediately.
478          * @param {Function} handler
479          *      An empty param function to be invoked on disconnected. Only one
480          *      handler can be registered at a time. Handlers already registered
481          *      will be overwritten.
482          */
483         registerOnDisconnectingHandler: function (handler) {
484             _registerHandler(_STATUS.DISCONNECTING, handler);
485         },
486 
487         /**
488          * @private
489          * Removes the on disconnecting callback that was registered.
490          */
491         unregisterOnDisconnectingHandler: function () {
492             _onDisconnectingHandler = undefined;
493         },
494 
495         /**
496          * @private
497          * Add a callback to be invoked when the BOSH connection is attempting
498          * to connect. If the connection is already trying to connect, the
499          * callback will be invoked immediately.
500          * @param {Function} handler
501          *      An empty param function to be invoked on connecting. Only one
502          *      handler can be registered at a time. Handlers already registered
503          *      will be overwritten.
504          */
505         registerOnReconnectingHandler: function (handler) {
506             _registerHandler(_STATUS.RECONNECTING, handler);
507         },
508 
509         /**
510          * @private
511          * Removes the on reconnecting callback that was registered.
512          */
513         unregisterOnReconnectingHandler: function () {
514             _onReconnectingHandler = undefined;
515         },
516         
517         /**
518          * @private
519          * Add a callback to be invoked when the BOSH connection is unloading
520          * 
521          * @param {Function} handler
522          *      An empty param function to be invoked on connecting. Only one
523          *      handler can be registered at a time. Handlers already registered
524          *      will be overwritten.
525          */
526         registerOnUnloadingHandler: function (handler) {
527             _registerHandler(_STATUS.UNLOADING, handler);
528         },
529         
530         /**
531          * @private
532          * Removes the on unloading callback that was registered.
533          */
534         unregisterOnUnloadingHandler: function () {
535             _onUnloadingHandler = undefined;
536         },
537 
538 	    /**
539          * @private
540 	     * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest
541          * ClientServices will mixin the BASIC Auth string, locale, and host, since the
542          * configuration is encapsulated in here anyways.
543          * This removes the dependency
544 	     * @param {String} url
545 	     *     The relative url to make the request to (the host from the passed in config will be
546          *     appended). It is expected that any encoding to the URL is already done.
547 	     * @param {Function} handler
548 	     *     Callback handler for makeRequest to invoke when the response returns.
549          *     Completely passed through to gadgets.io.makeRequest
550 	     * @param {Object} params
551 	     *     The params object that gadgets.io.makeRequest expects. Authorization and locale
552 	     *     headers are mixed in.
553          */
554 		makeRequest: function (url, handler, params) {
555 			// ClientServices needs to be initialized with a config for restHost, auth, and locale
556 			_isInited();
557 			
558 			// Allow mixin of auth and locale headers
559 			params = params || {};
560 			params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {};
561 			
562 			// Add Basic auth to request header
563 	        params[gadgets.io.RequestParameters.HEADERS].Authorization = "Basic " + _config.authorization;
564 	        //Locale
565             params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale;
566 
567 	        gadgets.io.makeRequest(encodeURI("http://" + _config.restHost) + url, handler, params);
568 		},
569 
570         /**
571          * @private
572          * Utility function to make a subscription to a particular topic. Only one
573          * callback function is registered to a particular topic at any time.
574          * @param {String} topic
575          *     The full topic name. The topic name should follow the OpenAjax
576          *     convention using dot notation (ex: finesse.api.User.1000).
577          * @param {Function} callback
578          *     The function that should be invoked with the data when an event
579          *     is delivered to the specific topic.
580          * @returns {Boolean}
581          *     True if the subscription was made successfully and the callback was
582          *     been registered. False if the subscription already exist, the
583          *     callback was not overwritten.
584          */
585         subscribe: function (topic, callback) {
586             _isInited();
587 
588             //Ensure that the same subscription isn't made twice.
589             if (!_subscriptionID[topic]) {
590                 //Store the subscription ID using the topic name as the key.
591                 _subscriptionID[topic] = _hub.subscribe(topic,
592                     //Invoke the callback just with the data object.
593                     function (topic, data) {
594                         callback(data);
595                     });
596                 return true;
597             }
598             return false;
599         },
600 
601         /**
602          * @private
603          * Unsubscribe from a particular topic.
604          * @param {String} topic
605          *     The full topic name.
606          */
607         unsubscribe: function (topic) {
608             _isInited();
609 
610             //Unsubscribe from the topic using the subscription ID recorded when
611             //the subscription was made, then delete the ID from data structure.
612             if (_subscriptionID[topic]) {
613                 _hub.unsubscribe(_subscriptionID[topic]);
614                 delete _subscriptionID[topic];
615             }
616         },
617 
618 	    /**
619          * @private
620 	     * Make a request to the request channel to have the Master subscribe
621 	     * to a node.
622          * @param {String} node
623          *     The node to subscribe to.
624 	     */
625 	    subscribeNode: function (node, handler) {
626 			if (handler && typeof handler !== "function") {
627 				throw new Error("ClientServices.subscribeNode: handler is not a function");
628 			}
629 			
630 			// Construct the request to send to MasterPublisher through the OpenAjax Hub
631 	        var data = {
632 	            type: "SubscribeNodeReq",
633 	            data: {node: node},
634 	            invokeID: _util.generateUUID()
635 	        },
636 			responseTopic = _topics.RESPONSES + "." + data.invokeID,
637 			_this = this;
638 
639 			// We need to first subscribe to the response channel
640 			this.subscribe(responseTopic, function (rsp) {
641 				// Since this channel is only used for this singular request,
642 				// we are not interested anymore.
643 				// This is also critical to not leaking memory by having OpenAjax
644 				// store a bunch of orphaned callback handlers that enclose on
645 				// our entire ClientServices singleton
646 				_this.unsubscribe(responseTopic);
647 				if (handler) {
648 					handler(data.invokeID, rsp);
649 				}
650 	        });
651 			// Then publish the request on the request channel
652 	        _hub.publish(_topics.REQUESTS, data);
653 	    },
654 
655 	    /**
656          * @private
657 	     * Make a request to the request channel to have the Master unsubscribe
658 	     * from a node.
659          * @param {String} node
660          *     The node to unsubscribe from.
661 	     */
662 	    unsubscribeNode: function (node, subid, handler) {
663 			if (handler && typeof handler !== "function") {
664 				throw new Error("ClientServices.unsubscribeNode: handler is not a function");
665 			}
666 			
667 			// Construct the request to send to MasterPublisher through the OpenAjax Hub
668 	        var data = {
669 	            type: "UnsubscribeNodeReq",
670 	            data: {
671 					node: node,
672 					subid: subid
673 				},
674 	            invokeID: _util.generateUUID()
675 	        },
676 			responseTopic = _topics.RESPONSES + "." + data.invokeID,
677 			_this = this;
678 
679 			// We need to first subscribe to the response channel
680 			this.subscribe(responseTopic, function (rsp) {
681 				// Since this channel is only used for this singular request,
682 				// we are not interested anymore.
683 				// This is also critical to not leaking memory by having OpenAjax
684 				// store a bunch of orphaned callback handlers that enclose on
685 				// our entire ClientServices singleton
686 				_this.unsubscribe(responseTopic);
687 				if (handler) {
688 					handler(rsp);
689 				}
690 	        });
691 			// Then publish the request on the request channel
692 	        _hub.publish(_topics.REQUESTS, data);
693 	    },
694 	    
695 	    /**
696          * @private
697          * Make a request to the request channel to have the Master connect to the XMPP server via BOSH
698          */
699 	    makeConnectionReq : function () {
700 	        var data = {
701 	            type: "ConnectionReq",
702 	            data: {
703 	                id: _config.id,
704 	                password: _config.password,
705 	                xmppDomain: _config.xmppDomain
706 	            },
707 	            invokeID: (new Date()).getTime()
708 	        };
709 	        _hub.publish(_topics.REQUESTS, data);
710 	    },
711 	
712         /**
713          * Set's the global logger for this Client Services instance.
714          * @param {Object} logger
715          *     Logger object with the following attributes defined:<ul>
716          *         <li><b>log:</b> function (msg) to simply log a message
717          *     </ul>
718          */
719 		setLogger: function (logger) {
720 			// We want to check the logger coming in so we don't have to check every time it is called.
721 			if (logger && typeof logger === "object" && typeof logger.log === "function") {
722 				_logger = logger;
723 			} else {
724 				// We are resetting it to an empty object so that _logger.log in .log is falsy.
725 				_logger = {};
726 			}
727 		},
728 		
729 	    /**
730          * @private
731          * Centralized logger.log method for external logger
732          * @param {String} msg
733          *     Message to log
734          */
735 		log: _log,
736 
737         /**
738          * Initiates the Client Services with the specified config parameters.
739          * Enabling the Client Services as Master will trigger the establishment
740          * of a BOSH event connection.
741          * @param {Object} config
742          *     Configuration object containing properties used for making REST requests:<ul>
743          *         <li><b>host:</b> The Finesse server IP/host as reachable from the browser
744          *         <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container
745          *         <li><b>id:</b> The ID of the user. This is an optional param as long as the
746          *         appropriate authorization string is provided, otherwise it is
747          *         required.</li>
748          *         <li><b>password:</b> The password belonging to the user. This is an optional param as
749          *         long as the appropriate authorization string is provided,
750          *         otherwise it is required.</li>
751          *         <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This
752          *         param is provided to allow the ability to hide the password
753          *         param. If provided, the id and the password extracted from this
754          *         string will be used over the config.id and config.password.</li>
755          *     </ul>
756          * @throws {Error} If required constructor parameter is missing.
757          */
758         init: function (config) {
759             if (!_inited) {
760                 //Validate the properties within the config object if one is provided.
761                 if (!(typeof config === "object" &&
762                      typeof config.host === "string" && config.host.length > 0 && config.restHost && 
763                      (typeof config.authorization === "string" ||
764                              (typeof config.id === "string" &&
765                                      typeof config.password === "string")))) {
766                     throw new Error("Config object contains invalid properties.");
767                 }
768 
769                 // Initialize configuration
770                 _config = config;
771 
772                 // Set shortcuts
773                 _util = finesse.utilities.Utilities;
774                 _hub = gadgets.Hub;
775                 _io = gadgets.io;
776                 _topics = finesse.clientservices.Topics;
777 
778                 //If the authorization string is provided, then use that to
779                 //extract the ID and the password. Otherwise use the ID and
780                 //password from the respective ID and password params.
781                 if (_config.authorization) {
782                     var creds = _util.getCredentials(_config.authorization);
783                     _config.id = creds.id;
784                     _config.password = creds.password;
785                 }
786                 else {
787                     _config.authorization = _util.b64Encode(
788                             _config.id + ":" + _config.password);
789                 }
790 
791                 _inited = true;
792 
793                 if (_hub) {
794                     //Subscribe to receive connection information. Since it is possible that
795                     //the client comes up after the Master comes up, the client will need
796                     //to make a request to have the Master send the latest connection info.
797                     //It would be possible that all clients get connection info again.
798                     this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler);
799                     _makeConnectionInfoReq();
800                 }
801             }
802 
803             //Return the CS object for object chaining.
804             return this;
805         },
806 
807         /**
808          * @private
809          * Initializes the BOSH component of this ClientServices instance. This establishes
810          * the BOSH connection and will trigger the registered handlers as the connection
811          * status changes respectively:<ul>
812          *     <li>registerOnConnectingHandler</li>
813          *     <li>registerOnConnectHandler</li>
814          *     <li>registerOnDisconnectHandler</li>
815          *     <li>registerOnDisconnectingHandler</li>
816          *     <li>registerOnReconnectingHandler</li>
817          *     <li>registerOnUnloadingHandler</li>
818          * <ul>
819          *
820          * @param {Object} config
821          *     An object containing the following (optional) handlers for the request:<ul>
822          *         <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object.
823          *         This is used to construct the JID: user@domain.com</li>
824          *         <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running.
825          *         Available from the SystemInfo object.
826          *         This is used for creating or removing subscriptions.</li>
827          *         <li><b>resource:</b> {String} The resource to connect to the notification server with.</li>
828          *     </ul>
829          */
830         initBosh: function (config) {
831             //Validate the properties within the config object if one is provided.
832             if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) {
833                 throw new Error("Config object contains invalid properties.");
834             }
835             
836             // Mixin the required information for establishing the BOSH connection
837             _config.xmppDomain = config.xmppDomain;
838             _config.pubsubDomain = config.pubsubDomain;
839             _config.resource = config.resource;
840             
841             //Initiate Master launch sequence
842             _becomeMaster(); 
843         },
844 
845         //BEGIN TEST CODE//
846         /**
847          * Test code added to expose private functions that are used by unit test
848          * framework. This section of code is removed during the build process
849          * before packaging production code. The [begin|end]TestSection are used
850          * by the build to identify the section to strip.
851          * @ignore
852          */
853         beginTestSection : 0,
854 
855         /**
856          * @ignore
857          */
858         getTestObject: function () {
859             //Load mock dependencies.
860             var _mock = new MockControl();
861             _hub = _mock.createMock(gadgets.Hub);
862             _io = _mock.createMock(gadgets.io);
863 
864             return {
865                 //Expose mock dependencies
866                 mock: _mock,
867                 hub: _hub,
868                 io: _io,
869 
870                 //Expose internal private functions
871                 subscriptionID: _subscriptionID,
872                 connInfoHandler: _connInfoHandler,
873 
874                 reset: function () {
875                     _inited = false;
876                     _onConnectingHandler = undefined;
877                     _onConnectHandler = undefined;
878                     _onDisconnectingHandler = undefined;
879                     _onDisconnectHandler = undefined;
880                     _onReconnectingHandler = undefined;
881                     _connInfo = undefined;
882                 },
883                 setUtil: function () {
884                     _util = finesse.utilities.Utilities;
885                 },
886                 setConfig: function (config) {
887                     _config = config;
888                 }
889             };
890         },
891 
892         /**
893          * @ignore
894          */
895         endTestSection: 0
896         //END TEST CODE//
897     };
898 }());
899