1 (function (globalObject) {
  2 //==============================================================================
  3 // PACKAGE MANAGEMENT
  4 //==============================================================================
  5 
  6 // JSDOC helpers
  7 
  8 /** @namespace 
  9     @name net
 10     @private */
 11 /** @namespace 
 12     @name net.user1
 13     @private */
 14 /** @namespace 
 15     @name net.user1.events
 16     @private */
 17 /** @namespace 
 18     @name net.user1.logger
 19     @private */
 20 /** @namespace 
 21     @name net.user1.orbiter
 22  */
 23 /** @namespace 
 24     @name net.user1.utils
 25  */
 26 
 27 // create utils package
 28 if (typeof globalObject.net == "undefined") {
 29   globalObject.net = {};
 30 }
 31 var net = globalObject.net;
 32 net.user1 = net.user1 ? net.user1 : {};
 33 net.user1.utils = net.user1.utils ? net.user1.utils : {};
 34 
 35 //  Convenience method to create packages
 36 /** @function */
 37 net.user1.utils.createPackage = function (packageName) {
 38   var parts = packageName.split(".");
 39   var part = globalObject;
 40   
 41   for (var i = 0; i < parts.length; i++) {
 42     part = part[parts[i]] === undefined ? (part[parts[i]] = {}) : part[parts[i]];
 43   }
 44 };
 45 //==============================================================================
 46 // PACKAGE DECLARATIONS
 47 //==============================================================================
 48 net.user1.utils.createPackage("net.user1.logger");
 49 net.user1.utils.createPackage("net.user1.events");
 50 net.user1.utils.createPackage("net.user1.orbiter");
 51 net.user1.utils.createPackage("net.user1.orbiter.filters");
 52 net.user1.utils.createPackage("net.user1.orbiter.snapshot");
 53 net.user1.utils.createPackage("net.user1.orbiter.upc");
 54 net.user1.utils.createPackage("net.user1.utils");
 55 /** @function */
 56 net.user1.utils.extend = function (subclass, superclass) {
 57   function superclassConstructor () {};
 58   superclassConstructor.prototype = superclass.prototype;
 59   subclass.superclass = superclass.prototype;
 60   subclass.prototype = new superclassConstructor();
 61   subclass.prototype.constructor = subclass;
 62 };
 63 //==============================================================================
 64 // ABSTRACT ERROR FUNCTION
 65 //==============================================================================
 66 
 67 // JSDOC helpers
 68 
 69 /** @private */
 70 net.user1.utils.abstractError = function () {
 71   throw new Error("Could not invoke abstract method. This method must be implemented by a subclass.");
 72 };
 73 //==============================================================================
 74 // CONNECTION REFUSAL REASON CONSTANTS
 75 //==============================================================================
 76 /** @class */
 77 net.user1.orbiter.ConnectionRefusalReason = new Object();
 78 /** @constant */
 79 net.user1.orbiter.ConnectionRefusalReason.BANNED = "BANNED";
 80 //==============================================================================
 81 // CLASS DECLARATION
 82 //==============================================================================
 83 /** @class */
 84 net.user1.orbiter.ConnectionRefusal = function (reason,
 85                                                 description) {
 86   /**
 87    * @field
 88    */
 89   this.bannedAt = NaN;
 90   /**
 91    * @field
 92    */
 93   this.banDuration = NaN;
 94   /**
 95    * @field
 96    */
 97   this.banReason = null;
 98   /**
 99    * @field
100    */
101   this.reason = reason;
102   /**
103    * @field
104    */
105   this.description = description;
106 
107   var banDetails;
108   switch (reason) {
109     case net.user1.orbiter.ConnectionRefusalReason.BANNED:
110       banDetails = description.split(net.user1.orbiter.Tokens.RS);
111       this.bannedAt = parseFloat(banDetails[0]);
112       this.banDuration = parseFloat(banDetails[1]);
113       this.banReason = banDetails[2];
114       break;
115   }
116 }
117 //==============================================================================
118 // CLASS DECLARATION
119 //==============================================================================
120 /** @class */
121 net.user1.orbiter.VersionNumber = function (major, minor, revision, build) {
122   this.major    = major;
123   this.minor    = minor;
124   this.revision = revision;
125   this.build    = build == undefined ? -1 : build;
126 };
127 
128 //==============================================================================
129 // INSTANCE METHODS
130 //==============================================================================  
131 net.user1.orbiter.VersionNumber.prototype.fromVersionString = function (value) {
132   var upcVersionParts = value.split(".");      
133   this.major    = upcVersionParts[0];
134   this.minor    = upcVersionParts[1];
135   this.revision = upcVersionParts[2];
136   this.build    = upcVersionParts.length == 4 ? upcVersionParts[4] : -1;
137 }
138 
139 net.user1.orbiter.VersionNumber.prototype.toStringVerbose = function () {
140   var versionString = this.major + "." + this.minor + "." + this.revision
141             + ((this.build == -1) ? "" : " (Build " + this.build + ")");
142   return versionString;
143 }
144     
145 net.user1.orbiter.VersionNumber.prototype.toString = function () {
146   var versionString = this.major + "." + this.minor + "." + this.revision
147             + ((this.build == -1) ? "" : "." + this.build);
148   return versionString;
149 }
150 //==============================================================================
151 // PRODUCT CONSTANTS
152 //==============================================================================
153 /** @class
154     @private */
155 net.user1.orbiter.Product = new Object();
156 
157 /** @private */
158 net.user1.orbiter.Product.clientType     = "OrbiterMicro";
159 net.user1.orbiter.Product.clientVersion  = new net.user1.orbiter.VersionNumber(2,1,2,21);
160 net.user1.orbiter.Product.upcVersion     = new net.user1.orbiter.VersionNumber(1,10,3);
161 //==============================================================================
162 // A COLLECTION OF ARRAY UTILITIES
163 //==============================================================================
164 /** @class */
165 net.user1.utils.ArrayUtil = new Object();
166 
167 net.user1.utils.ArrayUtil.indexOf = function (arr, obj) {
168   if (arr.indexOf ) {
169     return arr.indexOf(obj);
170   }
171 
172   for (var i = arr.length; --i >= 0; ) {
173     if (arr[i] === obj) {
174       return i;
175     }
176   }
177   
178   return -1;
179 };
180 
181 net.user1.utils.ArrayUtil.remove = function (array, item) {
182   var itemIndex;
183   
184   if (item == null) {
185     return false;
186   } else {
187     itemIndex = net.user1.utils.ArrayUtil.indexOf(array, item);
188     if (itemIndex == -1) {
189       return false;
190     } else {
191       array.splice(itemIndex, 1);
192       return true;
193     }
194   }
195 };
196 
197 net.user1.utils.ArrayUtil.isArray = function (value) {
198   return Object.prototype.toString.call(value) === '[object Array]';
199 };
200 //==============================================================================
201 // A COLLECTION OF OBJECT UTILITIES
202 //==============================================================================
203 /** @class */
204 net.user1.utils.ObjectUtil = new Object();
205 
206 net.user1.utils.ObjectUtil.combine = function () {
207   var source = arguments.length == 1 ? arguments[0] : arguments;
208   var master = new Object();
209   
210   var object;
211   for (var i = 0; i < source.length; i++) {
212     object = source[i];
213     for (var key in object) {
214       if (object.hasOwnProperty(key)) {
215         master[key] = object[key];
216       }
217     }
218   }
219   return master;
220 };
221 
222 net.user1.utils.ObjectUtil.length = function (object) {
223   var len = 0;
224   for (var p in object) {
225     len++;
226   } 
227   return len;
228 };
229 
230 //==============================================================================
231 // CLASS DECLARATION
232 //==============================================================================
233 /** @class A minimal in-memory storage map to mirror LocalData's persistent map. */
234 net.user1.utils.MemoryStore = function () {
235   this.clear();
236 };
237 
238 net.user1.utils.MemoryStore.prototype.write = function (record, field, value) {
239   if (typeof this.data[record] === "undefined") {
240     this.data[record] = new Object();
241   }
242   this.data[record][field] = value
243 };
244   
245 net.user1.utils.MemoryStore.prototype.read = function (record, field) {
246   if (typeof this.data[record] !== "undefined"
247       && typeof this.data[record][field] !== "undefined") {
248     return this.data[record][field];
249   } else {
250     return null;
251   }
252 };
253 
254 net.user1.utils.MemoryStore.prototype.remove = function (record, field) {
255   if (typeof this.data[record] !== "undefined") {
256     delete this.data[record][field];
257   }
258 };
259 
260 net.user1.utils.MemoryStore.prototype.clear = function () {
261   this.data = new Object();
262 };
263 //==============================================================================
264 // CLASS DECLARATION
265 //==============================================================================
266 /** @class 
267  * A minimal version of the browser localStorage object,
268  * for use in environments without native localStorage support.
269  * Provides in-memory storage only, with no persistence.
270  */
271 net.user1.utils.LocalStorage = function () {
272   this.data = new net.user1.utils.MemoryStore();
273 };
274 
275 net.user1.utils.LocalStorage.prototype.setItem = function (key, value) {
276   this.data.write("localStorage", key, value);
277 };
278   
279 net.user1.utils.LocalStorage.prototype.getItem = function (key) {
280   return this.data.read("localStorage", key);
281 };
282 
283 net.user1.utils.LocalStorage.prototype.removeItem = function (key) {
284   this.data.remove("localStorage", key);
285 };
286 //==============================================================================    
287 // CLASS DECLARATION
288 //==============================================================================
289 /** @class*/
290 net.user1.utils.LocalData = new Object();
291 
292 if (typeof localStorage === "undefined") {
293   net.user1.utils.LocalData.data = new net.user1.utils.LocalStorage();
294 } else {
295   net.user1.utils.LocalData.data = localStorage;
296 }
297 
298 net.user1.utils.LocalData.write = function (record, field, value) {
299   // localStorage can't store objects, so combine record and field for keys
300   net.user1.utils.LocalData.data.setItem(record+field, value);
301 };
302   
303 net.user1.utils.LocalData.read = function (record, field) {
304   var value = net.user1.utils.LocalData.data.getItem(record+field);
305   return value == null ? null : value;
306 };
307 
308 net.user1.utils.LocalData.remove = function (record, field) {
309   var value = net.user1.utils.LocalData.data.getItem(record+field);
310   if (value != null) {
311     this.data.removeItem(record+field);
312   }
313 };
314 //==============================================================================
315 // MESSAGE CONSTANTS
316 //==============================================================================
317 /** @class */
318 net.user1.orbiter.Messages = new Object();
319 /** @constant */
320 net.user1.orbiter.Messages.CLIENT_HEARTBEAT = "CLIENT_HEARTBEAT";
321 //==============================================================================
322 // RECEIVE MESSAGE BROADCAST TYPE CONSTANTS
323 //==============================================================================
324 /** @class
325     @private */
326 net.user1.orbiter.ReceiveMessageBroadcastType = new Object();
327 net.user1.orbiter.ReceiveMessageBroadcastType.TO_SERVER  = "0";
328 net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS   = "1";
329 net.user1.orbiter.ReceiveMessageBroadcastType.TO_CLIENTS = "2";
330 //==============================================================================
331 // ROOM ID PARSING UTILITIES
332 //==============================================================================
333 /** @class */
334 net.user1.orbiter.RoomIDParser = new Object();
335 
336 net.user1.orbiter.RoomIDParser.getSimpleRoomID = function (fullRoomID) {
337   if (fullRoomID.indexOf(".") == -1) {
338     return fullRoomID;
339   } else {
340     return fullRoomID.slice(fullRoomID.lastIndexOf(".")+1);
341   }
342 };
343 
344 net.user1.orbiter.RoomIDParser.getQualifier = function (fullRoomID) {
345   if (fullRoomID.indexOf(".") == -1) {
346     return "";
347   } else {
348     return fullRoomID.slice(0, fullRoomID.lastIndexOf("."));
349   }
350 };
351 
352 net.user1.orbiter.RoomIDParser.splitID = function (fullRoomID) {
353   return [getQualifier(fullRoomID), getSimpleRoomID(fullRoomID)];
354 };
355 //==============================================================================
356 // CLASS DECLARATION
357 //==============================================================================
358 /** @class */
359 net.user1.utils.UDictionary = function () {
360 };
361 //==============================================================================
362 // TOKEN CONSTANTS
363 //==============================================================================
364 /** @class
365     @private */
366 net.user1.orbiter.Tokens = new Object();
367 
368 /** @private */
369 net.user1.orbiter.Tokens.RS = "|";  
370 /** @private */
371 net.user1.orbiter.Tokens.WILDCARD = "*";
372 /** @private */
373 net.user1.orbiter.Tokens.GLOBAL_ATTR = "";
374 /** @private */
375 net.user1.orbiter.Tokens.CUSTOM_CLASS_ATTR = "_CLASS";
376 /** @private */
377 net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR = "_MAX_CLIENTS";
378 /** @private */
379 net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR = "_DIE_ON_EMPTY";
380 /** @private */
381 net.user1.orbiter.Tokens.PASSWORD_ATTR = "_PASSWORD";
382 /** @private */
383 net.user1.orbiter.Tokens.ROLES_ATTR = "_ROLES";
384 //==============================================================================
385 // CLASS DECLARATION
386 //==============================================================================
387 /** @class */
388 net.user1.orbiter.System = function (window) {
389   this.window = window;
390   this.clientType     = net.user1.orbiter.Product.clientType;
391   this.clientVersion  = net.user1.orbiter.Product.clientVersion;
392   this.upcVersion     = net.user1.orbiter.Product.upcVersion;
393 }
394 
395 //==============================================================================
396 // INSTANCE METHODS
397 //==============================================================================  
398 net.user1.orbiter.System.prototype.getClientType = function () {
399   return this.clientType;
400 }
401 
402 /** @returns net.user1.orbiter.VersionNumber */
403 net.user1.orbiter.System.prototype.getClientVersion = function () {
404   return this.clientVersion;
405 }
406     
407 /** @returns net.user1.orbiter.VersionNumber */
408 net.user1.orbiter.System.prototype.getUPCVersion = function () {
409   return this.upcVersion;
410 }
411     
412 /** @returns Boolean */
413 net.user1.orbiter.System.prototype.isJavaScriptCompatible = function () {
414   // Assume non-browser environments can do cross-origin XMLHttpRequests
415   if (this.window == null && typeof XMLHttpRequest != "undefined") {
416     return true;
417   }
418   
419   if (this.window != null) {
420     // Standards-based browsers that support cross-origin requests
421     if (typeof XMLHttpRequest != "undefined" 
422         && typeof new XMLHttpRequest().withCredentials != "undefined") {
423         return true;
424     }
425   
426     // Versions of IE that support proprietary cross-origin requests
427     if (typeof XDomainRequest != "undefined" 
428         && this.window.location.protocol != "file:") {
429       return true;
430     }
431 
432     // Browsers that can communicate between windows
433     if (this.window.postMessage != null) {
434       return true;
435     }
436   }
437   
438   // This environment has no way to connect to Union Server
439   return false;
440 }
441 
442 /** 
443  * <p>
444  * Returns true if the host environment supports direct cross-origin HTTP
445  * requests using CORS (see: <a href="http://www.w3.org/TR/cors/">http://www.w3.org/TR/cors/</a>).
446  * When hasHTTPDirectConnection() returns true, then Orbiter can safely use
447  * the HTTPDirectConnection class to communicate with Union Server over HTTP. When
448  * hasHTTPDirectConnection() returns false, Orbiter cannot use
449  * HTTPDirectConnection, and must instead use the HTTPIFrameConnection class to
450  * communicate with Union Server over HTTP. 
451  * </p>
452  * 
453  * <p>
454  * Note that Orbiter applications that use Orbiter's connect() or setServer()
455  * methods to connect to Union Server do not need to perform a capabilities check
456  * via hasHTTPDirectConnection(). The connect() and setServer() methods check
457  * the host environment's capabilities automatically, and choose the appropriate
458  * connection type for the environment. The hasHTTPDirectConnection() method is 
459  * required in one situation only: when the application explicitly wishes to 
460  * communicate over HTTP without trying a WebSocket connection first.
461  * </p>
462  * 
463  * @returns Boolean 
464  * 
465  * @see net.user1.orbiter.HTTPDirectConnection
466  * @see net.user1.orbiter.HTTPIFrameConnection
467  * @see net.user1.orbiter.Orbiter#connect
468  * @see net.user1.orbiter.Orbiter#setServer
469  **/
470 net.user1.orbiter.System.prototype.hasHTTPDirectConnection = function() {
471   // -If XHR has a "withCredentials" flag then CORS is supported.
472   // -In IE, if XDomainRequest is available, and the file wasn't loaded 
473   //    locally, then CORS is supported
474   // -In non-browser environments, assume cross-origin XMLHttpRequests are allowed
475   if ((typeof XMLHttpRequest != "undefined" && typeof new XMLHttpRequest().withCredentials != "undefined")
476        || (typeof XDomainRequest != "undefined" && this.window != null && this.window.location.protocol != "file:")
477        || (this.window == null && typeof XMLHttpRequest != "undefined")) {
478     return true;
479   } else {
480     return false;
481   }
482 }
483 
484 /** 
485  * <p>
486  * Returns true if the host environment supports WebSocket connections.
487  * When hasWebSocket() returns true, then Orbiter can safely use
488  * the WebSocketConnection class to communicate with Union Server over a 
489  * persistent TCP/IP socket. When hasWebSocket() returns false, Orbiter cannot use
490  * WebSocketConnection, and must instead use HTTP communications (via either the
491  * HTTPDirectConnection class or the HTTPIFrameConnection class). 
492  * </p>
493  * 
494  * <p>
495  * Note that Orbiter applications that use Orbiter's connect() or setServer()
496  * methods to connect to Union Server do not need to perform a capabilities check
497  * via hasWebSocket(). The connect() and setServer() methods check
498  * the host environment's capabilities automatically, and choose the appropriate
499  * connection type for the environment. The hasWebSocket() method is 
500  * required in one situation only: when the application explicitly wishes to 
501  * determine whether WebSocket is supported for the purpose of application flow
502  * or user feedback.
503  * </p>
504  * 
505  * @returns Boolean 
506  * 
507  * @see net.user1.orbiter.WebSocketConnection
508  * @see net.user1.orbiter.Orbiter#connect
509  **/
510 net.user1.orbiter.System.prototype.hasWebSocket = function() {
511   return (typeof WebSocket !== "undefined" || typeof MozWebSocket !== "undefined");
512 }
513 
514 net.user1.orbiter.System.prototype.toString = function () {
515   return "[object System]";
516 }  
517 //==============================================================================
518 // A COLLECTION OF NUMERIC FORMATTING FUNCTIONS
519 //==============================================================================
520 /** @class */
521 net.user1.utils.NumericFormatter = new Object();
522 
523 net.user1.utils.NumericFormatter.dateToLocalHrMinSec = function (date) {
524   var timeString = net.user1.utils.NumericFormatter.addLeadingZero(date.getHours()) + ":" 
525                  + net.user1.utils.NumericFormatter.addLeadingZero(date.getMinutes()) + ":" 
526                  + net.user1.utils.NumericFormatter.addLeadingZero(date.getSeconds());
527   return timeString;
528 }
529     
530 net.user1.utils.NumericFormatter.dateToLocalHrMinSecMs = function (date) {
531   return net.user1.utils.NumericFormatter.dateToLocalHrMinSec(date) + "." + net.user1.utils.NumericFormatter.addTrailingZeros(date.getMilliseconds());
532 }
533     
534 net.user1.utils.NumericFormatter.addLeadingZero = function (n) {
535   return ((n>9)?"":"0")+n;
536 }
537     
538 net.user1.utils.NumericFormatter.addTrailingZeros = function (n) {
539   var ns = n.toString();
540   
541   if (ns.length == 1) {
542     return ns + "00";
543   } else if (ns.length == 2) {
544     return ns + "0";
545   } else {
546     return ns;
547   }
548 }
549 
550 net.user1.utils.NumericFormatter.msToElapsedDayHrMinSec = function (ms) {
551   var sec = Math.floor(ms/1000);
552  
553   var min = Math.floor(sec/60);
554   sec = sec % 60;
555   var timeString = net.user1.utils.NumericFormatter.addLeadingZero(sec);
556   
557   var hr = Math.floor(min/60);
558   min = min % 60;
559   timeString = net.user1.utils.NumericFormatter.addLeadingZero(min) + ":" + timeString;
560   
561   var day = Math.floor(hr/24);
562   hr = hr % 24;
563   timeString = net.user1.utils.NumericFormatter.addLeadingZero(hr) + ":" + timeString;
564   
565   if (day > 0) {      
566     timeString = day + "d " + timeString;
567   }
568   
569   return timeString;
570 };
571 //==============================================================================
572 // CLASS DECLARATION
573 //==============================================================================
574 /** @class */
575 net.user1.events.EventListener = function (listener,
576                                            thisArg,
577                                            priority) {
578   this.listener   = listener;
579   this.thisArg    = thisArg;
580   this.priority   = priority;
581 };
582 
583 //==============================================================================
584 // INSTANCE METHODS
585 //==============================================================================
586 net.user1.events.EventListener.prototype.getListenerFunction = function () {
587   return this.listener;
588 };
589     
590 net.user1.events.EventListener.prototype.getThisArg = function () {
591   return this.thisArg;
592 };
593     
594 net.user1.events.EventListener.prototype.getPriority = function () {
595   return this.priority;
596 };
597 
598 net.user1.events.EventListener.prototype.toString = function () {
599   return "[object EventListener]";
600 };
601 //==============================================================================
602 // CLASS DECLARATION
603 //==============================================================================
604 /** @class */
605 net.user1.events.EventDispatcher = function (target) {
606   this.listeners = new Object();
607   
608   if (typeof target !== "undefined") {
609     this.target = target;
610   } else {
611     this.target = this;
612   }
613 };
614 
615 //==============================================================================
616 // INSTANCE METHODS
617 //==============================================================================
618 /**
619  * Registers a function or method to be invoked when the specified event type
620  * occurs.
621  *
622  * @param type The string name of the event (for example, "READY")
623  * @param listener A reference to the function or method to invoke.
624  * @param thisArg A reference to the object on which the listener will be invoked
625  *                (i.e., the value of "this" within the listener's function body).
626  * @param priority An integer indicating the listener's priority. Listeners with
627  *                 higher priority are invoked before listeners with lower priority.
628  *                 Listeners with equal priority are invoked in the order they were
629  *                 added. Listener priority defaults to 0.
630  * @return {Boolean} true if the listener was added; false if the listener was
631  *                        already registered for the event.
632  *
633  * @example
634  * <pre>
635  * // Invoke readyListener() on 'this' when READY occurs:
636  * orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, readyListener, this);
637  * </pre>
638  */
639 net.user1.events.EventDispatcher.prototype.addEventListener = function (type, 
640                                                                         listener,
641                                                                         thisArg,
642                                                                         priority) {
643   if (typeof this.listeners[type] === "undefined") {
644     this.listeners[type] = new Array();
645   } 
646   var listenerArray = this.listeners[type];
647   
648   if (this.hasListener(type, listener, thisArg)) {
649     return false;
650   }
651   priority = priority || 0;
652   
653   var newListener = new net.user1.events.EventListener(listener,
654                                                        thisArg,
655                                                        priority);
656   var added = false;
657   var thisListener;
658   for (var i = listenerArray.length; --i >= 0;) {
659     thisListener = listenerArray[i];
660     if (priority <= thisListener.getPriority()) {
661       listenerArray.splice(i+1, 0, newListener);
662       added = true;
663       break;
664     }
665   }
666   if (!added) {
667     listenerArray.unshift(newListener);
668   }
669   return true;      
670 };
671 
672 net.user1.events.EventDispatcher.prototype.removeEventListener = function (type,
673                                                                            listener,
674                                                                            thisArg) {
675   var listenerArray = this.listeners[type];
676   if (typeof listenerArray === "undefined") {
677     return false;
678   } 
679   
680   var foundListener = false;
681   for (var i = 0; i < listenerArray.length; i++) {
682     if (listenerArray[i].getListenerFunction() === listener
683         && listenerArray[i].getThisArg() === thisArg) {
684       foundListener = true;
685       listenerArray.splice(i, 1);
686       break;
687     }
688   }
689   
690   if (listenerArray.length == 0) {
691     delete this.listeners[type];
692   }
693   
694   return foundListener;      
695 };
696     
697 net.user1.events.EventDispatcher.prototype.hasListener = function (type, 
698                                                                    listener,
699                                                                    thisArg) {
700   var listenerArray = this.listeners[type];
701   if (typeof listenerArray === "undefined") {
702     return false;
703   } 
704       
705   for (var i = 0; i < listenerArray.length; i++) {
706     if (listenerArray[i].getListenerFunction() === listener
707         && listenerArray[i].getThisArg() === thisArg) {
708       return true;
709     }
710   }
711   return false;
712 };
713     
714 net.user1.events.EventDispatcher.prototype.getListeners = function (type) {
715   return this.listeners[type];
716 };
717 
718 net.user1.events.EventDispatcher.prototype.dispatchEvent = function (event) {
719   var listenerArray = this.listeners[event.type];
720   if (typeof listenerArray === "undefined") {
721     return;
722   }
723   if (typeof event.type === "undefined") {
724     throw new Error("Event dispatch failed. No event name specified by " + event);
725   }
726   event.target = this.target;
727   var numListeners = listenerArray.length;
728   for (var i = 0; i < numListeners; i++) {
729     listenerArray[i].getListenerFunction().apply(listenerArray[i].getThisArg(), [event]);
730   }
731 };
732 
733 //==============================================================================    
734 // TOSTRING
735 //==============================================================================
736 
737 net.user1.events.EventDispatcher.prototype.toString = function () {
738   return "[object EventDispatcher]";
739 };
740 //==============================================================================
741 // CLASS DECLARATION
742 //==============================================================================
743 /** @class */
744 net.user1.events.Event = function (type) {
745   if (type !== undefined) {
746     this.type = type;
747   } else {
748     throw new Error("Event creation failed. No type specified. Event: " + this);
749   }
750   this.target = null;
751 };
752     
753 net.user1.events.Event.prototype.toString = function () {
754   return "[object Event]";
755 };
756 //==============================================================================
757 // CLASS DECLARATION
758 //==============================================================================
759 /** @class
760 
761 The ConsoleLogger class outputs Orbiter's log to the host environment's console,
762 if a console is available.
763 
764 */
765 net.user1.logger.ConsoleLogger = function (log) {
766   this.log = log;
767   this.log.addEventListener(net.user1.logger.LogEvent.UPDATE, this.updateListener, this);
768   // Print all messages already in the log
769   var history = this.log.getHistory();
770   for (var i = 0; i < history.length; i++) {
771     this.out(history[i]);
772   }
773 };
774     
775 //==============================================================================
776 // INSTANCE METHODS
777 //==============================================================================
778 /** @private */ 
779 net.user1.logger.ConsoleLogger.prototype.updateListener = function (e) {
780   var timeStamp = e.getTimeStamp();
781   var level = e.getLevel();
782   var bufferSpace = (level == net.user1.logger.Logger.INFO 
783                      || level == net.user1.logger.Logger.WARN) ? " " : "";
784 
785   this.out(timeStamp + (timeStamp == "" ? "" : " ") 
786            + e.getLevel() + ": " + bufferSpace + e.getMessage());
787 };
788 
789 /** @private */ 
790 net.user1.logger.ConsoleLogger.prototype.out = function (value) {
791   if (typeof console === "undefined" || typeof console.log === "undefined") {
792     return;
793   }
794   console.log(value);
795 };
796 
797 /** @private */ 
798 net.user1.logger.ConsoleLogger.prototype.dispose = function () {
799   this.log.removeEventListener(net.user1.logger.LogEvent.UPDATE, this.updateListener, this);
800   this.log = log = null;
801 };
802 //==============================================================================
803 // CLASS DECLARATION
804 //==============================================================================
805 /** @class
806     @extends net.user1.events.Event
807 */
808 net.user1.logger.LogEvent = function (type, message, level, timeStamp) {
809   net.user1.events.Event.call(this, type);
810 
811   this.message = message;
812   this.level = level;
813   this.timeStamp = timeStamp;
814 };
815 
816 //==============================================================================
817 // INHERITANCE
818 //==============================================================================
819 net.user1.utils.extend(net.user1.logger.LogEvent, net.user1.events.Event);
820 
821 //==============================================================================
822 // STATIC VARIABLES
823 //==============================================================================
824 /** @constant */
825 net.user1.logger.LogEvent.UPDATE = "UPDATE";
826 /** @constant */
827 net.user1.logger.LogEvent.LEVEL_CHANGE = "LEVEL_CHANGE";
828   
829 //==============================================================================
830 // INSTANCE METHODS
831 //==============================================================================
832 net.user1.logger.LogEvent.prototype.getMessage = function () {
833   return this.message;
834 };
835   
836 net.user1.logger.LogEvent.prototype.getLevel = function () {
837   return this.level;
838 };
839   
840 net.user1.logger.LogEvent.prototype.getTimeStamp = function () {
841   return this.timeStamp;
842 };
843 
844 net.user1.logger.LogEvent.prototype.toString = function () {
845   return "[object LogEvent]";
846 };
847 
848 //==============================================================================
849 // CLASS DECLARATION
850 //==============================================================================
851 /** @class
852 
853 The Logger class dispatches the following events:
854 
855 <ul class="summary">
856 <li class="fixedFont">{@link net.user1.logger.LogEvent.LEVEL_CHANGE}</li>
857 <li class="fixedFont">{@link net.user1.logger.LogEvent.UPDATE}</li>
858 </ul>
859 
860 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
861 
862 
863     @extends net.user1.events.EventDispatcher
864 */
865 net.user1.logger.Logger = function (historyLength) {
866   // Invoke superclass constructor
867   net.user1.events.EventDispatcher.call(this);
868   
869   // Instance variables
870   this.suppressionTerms = new Array(); 
871   this.timeStampEnabled = false;
872   this.logLevel = 0;
873   this.messages = new Array();
874   this.historyLength = 0;
875 
876   // Initialization
877   this.setHistoryLength(historyLength == null ? 100 : historyLength);
878   this.enableTimeStamp(); 
879   this.setLevel(net.user1.logger.Logger.INFO);
880 };  
881 
882 //==============================================================================
883 // INHERITANCE
884 //==============================================================================
885 net.user1.utils.extend(net.user1.logger.Logger, net.user1.events.EventDispatcher);
886   
887 //==============================================================================
888 // STATIC VARIABLES
889 //==============================================================================
890 /** @constant */
891 net.user1.logger.Logger.FATAL = "FATAL"; 
892 /** @constant */
893 net.user1.logger.Logger.ERROR = "ERROR"; 
894 /** @constant */
895 net.user1.logger.Logger.WARN  = "WARN"; 
896 /** @constant */
897 net.user1.logger.Logger.INFO  = "INFO"; 
898 /** @constant */
899 net.user1.logger.Logger.DEBUG = "DEBUG";
900 net.user1.logger.Logger.logLevels = new Array(net.user1.logger.Logger.FATAL,
901                                               net.user1.logger.Logger.ERROR, 
902                                               net.user1.logger.Logger.WARN, 
903                                               net.user1.logger.Logger.INFO, 
904                                               net.user1.logger.Logger.DEBUG);
905 
906 //==============================================================================
907 // INSTANCE METHODS
908 //==============================================================================
909 net.user1.logger.Logger.prototype.setLevel = function (level) {
910   if (level !== undefined) {
911     for (var i = 0; i < net.user1.logger.Logger.logLevels.length; i++) {
912       if (net.user1.logger.Logger.logLevels[i].toLowerCase() == level.toLowerCase()) {
913         this.logLevel = i;
914         this.dispatchEvent(new net.user1.logger.LogEvent(net.user1.logger.LogEvent.LEVEL_CHANGE, null, level));
915         return;
916       }
917     }
918   }
919 
920   this.warn("Invalid log level specified: " + level);
921 };
922 
923 net.user1.logger.Logger.prototype.getLevel = function () {
924   return net.user1.logger.Logger.logLevels[this.logLevel];
925 };
926 
927 net.user1.logger.Logger.prototype.fatal = function (msg) {
928   this.addEntry(0, net.user1.logger.Logger.FATAL, msg);
929 };
930 
931 net.user1.logger.Logger.prototype.error = function (msg) {
932   this.addEntry(1, net.user1.logger.Logger.ERROR, msg);
933 };
934 
935 net.user1.logger.Logger.prototype.warn = function (msg) {
936   this.addEntry(2, net.user1.logger.Logger.WARN, msg);
937 };
938 
939 net.user1.logger.Logger.prototype.info = function (msg) {
940   this.addEntry(3, net.user1.logger.Logger.INFO, msg);
941 };
942 
943 net.user1.logger.Logger.prototype.debug = function (msg) {
944   this.addEntry(4, net.user1.logger.Logger.DEBUG, msg);
945 };
946 
947 net.user1.logger.Logger.prototype.addSuppressionTerm = function (term) {
948   this.debug("Added suppression term. Log messages containing '" 
949              + term + "' will now be ignored.");
950   this.suppressionTerms.push(term);
951 };
952 
953 net.user1.logger.Logger.prototype.removeSuppressionTerm = function (term) {
954   var termIndex = net.user1.utils.ArrayUtil.indexOf(this.suppressionTerms, term);
955   if (termIndex != -1) {
956     this.suppressionTerms.splice(termIndex, 1);
957     this.debug("Removed suppression term. Log messages containing '" 
958                + term + "' will now be shown.");
959     return true;
960   }
961   return false;
962 };
963 
964 /** @private */
965 net.user1.logger.Logger.prototype.addEntry = function (level, levelName, msg) {
966   var timeStamp = "";
967   var time;
968   
969   // Abort if the log's level is lower than the message's level.
970   if (this.logLevel < level) {
971     return;
972   }
973   
974   // Don't log messages if they contain any of the suppression terms.
975   for (var i = this.suppressionTerms.length; --i >= 0;) {
976     if (msg.indexOf(this.suppressionTerms[i]) != -1) {
977       return;
978     }
979   }
980 
981   if (this.timeStampEnabled) {
982     time = new Date();
983     timeStamp = time.getMonth()+1 + "/" + String(time.getDate())
984               + "/" + String(time.getFullYear()).substr(2)
985               + " " + net.user1.utils.NumericFormatter.dateToLocalHrMinSecMs(time) 
986               + " UTC" + (time.getTimezoneOffset() >= 0 ? "-" : "+") 
987               + Math.abs(time.getTimezoneOffset() / 60);
988   }
989   
990   // Log the message.
991   this.addToHistory(levelName, msg, timeStamp);
992 
993   var e = new net.user1.logger.LogEvent(net.user1.logger.LogEvent.UPDATE,
994                                         msg, levelName, timeStamp);
995   this.dispatchEvent(e);
996 };
997 
998 /** @private */ 
999 net.user1.logger.Logger.prototype.setHistoryLength = function (newHistoryLength) {
1000   this.historyLength = newHistoryLength;
1001   
1002   if (this.messages.length > this.historyLength) {
1003     this.messages.splice(this.historyLength);
1004   }
1005 };
1006 
1007 net.user1.logger.Logger.prototype.getHistoryLength = function () {
1008   return this.historyLength;
1009 };
1010 
1011 /** @private */ 
1012 net.user1.logger.Logger.prototype.addToHistory = function (level, msg, timeStamp) {
1013   this.messages.push(timeStamp + (timeStamp == "" ? "" : " ") + level + ": " + msg);
1014   if (this.messages.length > this.historyLength) {
1015     this.messages.shift();
1016   }
1017 };
1018 
1019 net.user1.logger.Logger.prototype.getHistory = function () {
1020   return this.messages.slice(0);
1021 };
1022 
1023 net.user1.logger.Logger.prototype.enableTimeStamp = function () {
1024   this.timeStampEnabled = true;
1025 };
1026 
1027 net.user1.logger.Logger.prototype.disableTimeStamp = function () {
1028   this.timeStampEnabled = false;
1029 };
1030 
1031 net.user1.logger.Logger.prototype.toString = function () {
1032   return "[object Logger]";
1033 };
1034 //==============================================================================
1035 // CLASS DECLARATION
1036 //==============================================================================
1037 /** @class
1038     @extends net.user1.events.Event
1039 */
1040 net.user1.orbiter.ConnectionManagerEvent = function (type, connection, status) {
1041   net.user1.events.Event.call(this, type);
1042   
1043   this.connection = connection
1044   this.status = status;
1045 };
1046 
1047 //==============================================================================
1048 // INHERITANCE
1049 //==============================================================================
1050 net.user1.utils.extend(net.user1.orbiter.ConnectionManagerEvent, net.user1.events.Event);
1051 
1052 //==============================================================================
1053 // STATIC VARIABLES
1054 //==============================================================================
1055 
1056 /** @constant */
1057 net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT = "BEGIN_CONNECT";
1058 /** @constant */
1059 net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION = "SELECT_CONNECTION";
1060 /** @constant */
1061 net.user1.orbiter.ConnectionManagerEvent.READY = "READY";
1062 /** @constant */
1063 net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE = "CONNECT_FAILURE";
1064 /** @constant */
1065 net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT = "CLIENT_KILL_CONNECT";
1066 /** @constant */
1067 net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT = "SERVER_KILL_CONNECT";
1068 /** @constant */
1069 net.user1.orbiter.ConnectionManagerEvent.DISCONNECT = "DISCONNECT";
1070 /** @constant */
1071 net.user1.orbiter.ConnectionManagerEvent.CONNECTION_STATE_CHANGE = "CONNECTION_STATE_CHANGE";
1072 /** @constant */
1073 net.user1.orbiter.ConnectionManagerEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
1074   
1075 //==============================================================================
1076 // INSTANCE METHODS
1077 //==============================================================================
1078 
1079 net.user1.orbiter.ConnectionManagerEvent.prototype.getConnection = function () {
1080   return this.connection;
1081 }
1082 
1083 net.user1.orbiter.ConnectionManagerEvent.prototype.getStatus = function () {
1084   return this.status;
1085 }
1086 
1087 net.user1.orbiter.ConnectionManagerEvent.prototype.toString = function () {
1088   return "[object ConnectionManagerEvent]";
1089 }  
1090 
1091 //==============================================================================
1092 // CLASS DECLARATION
1093 //==============================================================================
1094 /** @class
1095 
1096 The ConnectionManager class dispatches the following events:
1097 
1098 <ul class="summary">
1099 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT}</li>
1100 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION}</li>
1101 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE}</li>
1102 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.DISCONNECT}</li>
1103 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT}</li>
1104 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT}</li>
1105 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.READY}</li>
1106 </ul>
1107 
1108 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
1109 
1110     @extends net.user1.events.EventDispatcher
1111 
1112  * @see net.user1.orbiter.Orbiter#connect
1113  */
1114 net.user1.orbiter.ConnectionManager = function (orbiter) {
1115     // Call superconstructor
1116     net.user1.events.EventDispatcher.call(this);
1117     
1118     // Variables
1119     this.orbiter             = orbiter;
1120     this.connectAttemptCount = 0;
1121     this.connectAbortCount   = 0;
1122     this.readyCount          = 0;      
1123     this.connectFailedCount  = 0;
1124     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1125     this.readyTimeout        = 0;
1126     this.connections         = new Array();
1127     this.activeConnection    = null;
1128     this.inProgressConnection = null;
1129     this.currentConnectionIndex = 0;
1130     this.attemptedConnections = null;
1131     this.setReadyTimeout(net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT);
1132     
1133     // Initialization
1134     // Make all Orbiter instances in this VM share the same server affinity 
1135     this.setGlobalAffinity(true);  
1136 };
1137     
1138 //==============================================================================    
1139 // INHERITANCE
1140 //============================================================================== 
1141 net.user1.utils.extend(net.user1.orbiter.ConnectionManager, net.user1.events.EventDispatcher);
1142 
1143 //==============================================================================
1144 // STATIC VARIABLES
1145 //==============================================================================
1146 net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT = 10000;
1147 
1148 // =============================================================================
1149 // CONNECT AND DISCONNECT
1150 // =============================================================================
1151 net.user1.orbiter.ConnectionManager.prototype.connect = function () {
1152   if (this.connections.length == 0) {
1153     this.orbiter.getLog().error("[CONNECTION_MANAGER] No connections defined. Connection request ignored.");
1154     return;
1155   }
1156   
1157   this.connectAttemptCount++;
1158   this.attemptedConnections = new Array();
1159 
1160   switch (this.connectionState) {
1161     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1162       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection attempt already in " 
1163                             + "progress. Existing attempt must be aborted before"  
1164                             + " new connection attempt begins...");
1165       this.disconnect();
1166       break;
1167 
1168     case net.user1.orbiter.ConnectionState.READY:
1169       this.orbiter.getLog().info("[CONNECTION_MANAGER] Existing connection to Union" 
1170                             + " must be disconnected before new connection" 
1171                             + " attempt begins.");
1172       this.disconnect();
1173       break;
1174   }
1175   this.setConnectionState(net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS);
1176   
1177   this.orbiter.getLog().debug("[CONNECTION_MANAGER] Searching for most recent valid connection.");
1178   var originalConnectionIndex = this.currentConnectionIndex;
1179   while (!this.getCurrentConnection().isValid()) {
1180     this.advance();
1181     if (this.currentConnectionIndex == originalConnectionIndex) {
1182       // Couldn't find a valid connection, so start the connection with
1183       // the first connection in the connection list
1184       this.orbiter.getLog().debug("[CONNECTION_MANAGER] No valid connection found. Starting connection attempt with first connection.");
1185       this.currentConnectionIndex = 0;
1186       break;
1187     }
1188   }  
1189   
1190   this.dispatchBeginConnect();
1191   this.connectCurrentConnection();
1192 };
1193 
1194 net.user1.orbiter.ConnectionManager.prototype.disconnect = function () {
1195   if (this.connections.length == 0) {
1196     this.dispatchConnectFailure("No connections defined. Disconnection attempt failed.");
1197     return;
1198   }
1199   
1200   switch (this.connectionState) {
1201     // Currently connected
1202     case net.user1.orbiter.ConnectionState.READY:
1203       this.orbiter.getLog().info("[CONNECTION_MANAGER] Closing existing connection: "
1204                             + this.getActiveConnection().toString());
1205       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1206       this.disconnectConnection(this.getActiveConnection());
1207       break;
1208 
1209     // Currently attempting to connect
1210     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1211       this.orbiter.getLog().info("[CONNECTION_MANAGER] Aborting existing connection attempt: "
1212                             + this.getInProgressConnection().toString());
1213       this.connectAbortCount++;
1214       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1215       this.disconnectConnection(this.getInProgressConnection());
1216       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection abort complete.");
1217       break;
1218 
1219     // Currently attempting to disconnect
1220     case net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS:
1221       this.orbiter.getLog().info("[CONNECTION_MANAGER] Disconnection request ignored."
1222                             + " Already disconnecting.");
1223       break;
1224   }
1225 };
1226     
1227 /** @private */
1228 net.user1.orbiter.ConnectionManager.prototype.disconnectConnection = function (connection) {
1229   connection.disconnect();
1230 };
1231     
1232 /** @private */
1233 net.user1.orbiter.ConnectionManager.prototype.connectCurrentConnection = function () {
1234   // If there are no Connections defined, fail immediately 
1235   if (this.connections.length == 0) {
1236     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1237     this.connectFailedCount++;
1238     this.dispatchConnectFailure("No connections defined. Connection attempt failed.");
1239     return;
1240   }
1241   
1242   this.inProgressConnection = this.getCurrentConnection();
1243   
1244   // If the requested connection has already been attempted this round,
1245   // ignore it.
1246   if (net.user1.utils.ArrayUtil.indexOf(this.attemptedConnections, this.inProgressConnection) != -1) {
1247     this.advanceAndConnect();
1248     return;
1249   }
1250   
1251   this.dispatchSelectConnection(this.inProgressConnection);
1252   this.orbiter.getLog().info("[CONNECTION_MANAGER] Attempting connection via "
1253                         + this.inProgressConnection.toString() + ". (Connection "
1254                         + (this.attemptedConnections.length+1) + " of "
1255                         + this.connections.length + ". Attempt " + this.connectAttemptCount +" since last successful connection).");
1256   this.addConnectionListeners(this.inProgressConnection);
1257   this.inProgressConnection.connect();
1258 };
1259 
1260 /** @private */
1261 net.user1.orbiter.ConnectionManager.prototype.advanceAndConnect = function () {
1262   if (!this.connectAttemptComplete()) {
1263     this.advance();
1264     this.connectCurrentConnection();
1265   } else {
1266     // Tried all connections, so give up and dispatch CONNECT_FAILURE
1267     this.connectFailedCount++;
1268     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1269     this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for all specified hosts and ports.");
1270     this.dispatchConnectFailure("Connection failed for all specified hosts and ports.");
1271   }
1272 };
1273     
1274 /** @private */
1275 net.user1.orbiter.ConnectionManager.prototype.connectAttemptComplete = function () {
1276   return this.attemptedConnections.length == this.connections.length;
1277 };
1278 
1279 /** @private */
1280 net.user1.orbiter.ConnectionManager.prototype.advance = function () {
1281   this.currentConnectionIndex++;
1282   if (this.currentConnectionIndex == this.connections.length) {
1283     this.currentConnectionIndex = 0;
1284   }
1285 };
1286     
1287 // =============================================================================
1288 // CONNECTION OBJECT MANAGEMENT
1289 // =============================================================================
1290 net.user1.orbiter.ConnectionManager.prototype.addConnection = function (connection) {
1291   if (connection != null) {
1292     this.orbiter.getLog().info("[CONNECTION_MANAGER] New connection added. "
1293                           + connection.toString() + ".");
1294     connection.setOrbiter(this.orbiter);
1295     this.connections.push(connection);
1296   }
1297 };
1298     
1299 net.user1.orbiter.ConnectionManager.prototype.removeConnection = function (connection) {
1300   if (connection != null) {
1301     connection.disconnect();
1302     this.removeConnectionListeners(connection);
1303     return net.user1.utils.ArrayUtil.remove(this.connections, connection);
1304   } else {
1305     return false;
1306   }
1307 };
1308 
1309 net.user1.orbiter.ConnectionManager.prototype.removeAllConnections = function () {
1310   if (this.connections.length == 0) {
1311     this.orbiter.getLog().info("[CONNECTION_MANAGER] removeAllConnections() ignored. " +
1312                                " No connections to remove.");
1313     return;
1314   }
1315   
1316   this.orbiter.getLog().info("[CONNECTION_MANAGER] Removing all connections...");
1317   this.disconnect();
1318   while (this.connections.length > 0) {
1319     this.removeConnection(this.connections[0]);
1320   }
1321   this.currentConnectionIndex = 0;
1322   this.orbiter.getLog().info("[CONNECTION_MANAGER] All connections removed.");
1323 };
1324     
1325 // =============================================================================
1326 // CONNECTION ACCESS
1327 // =============================================================================
1328 net.user1.orbiter.ConnectionManager.prototype.getActiveConnection = function () {
1329   return this.activeConnection;
1330 };
1331     
1332 net.user1.orbiter.ConnectionManager.prototype.getInProgressConnection = function () {
1333   return this.inProgressConnection;
1334 };
1335     
1336 net.user1.orbiter.ConnectionManager.prototype.getConnections = function () {
1337   return this.connections.slice();
1338 };
1339 
1340 /** @private */    
1341 net.user1.orbiter.ConnectionManager.prototype.getCurrentConnection = function () {
1342   return this.connections[this.currentConnectionIndex];
1343 };
1344     
1345 // =============================================================================
1346 // CONNECTION LISTENER REGISTRATION
1347 // =============================================================================
1348 /** @private */
1349 net.user1.orbiter.ConnectionManager.prototype.addConnectionListeners = function(connection) {
1350   if (connection != null) {
1351     connection.addEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1352     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1353     connection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1354     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1355     connection.addEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1356   }
1357 };
1358     
1359 /** @private */    
1360 net.user1.orbiter.ConnectionManager.prototype.removeConnectionListeners = function (connection) {
1361   if (connection != null) {
1362     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1363     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1364     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1365     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1366     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1367   }
1368 };
1369     
1370 // =============================================================================
1371 // CONNECTION STATE ACCESS
1372 // =============================================================================
1373 net.user1.orbiter.ConnectionManager.prototype.isReady = function () {
1374   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
1375 }
1376 
1377 net.user1.orbiter.ConnectionManager.prototype.setConnectionState = function (state) {
1378   var changed = false;
1379   if (state != this.connectionState) {
1380     changed = true;
1381   }
1382   this.connectionState = state;
1383   if (changed) {
1384     this.dispatchConnectionStateChange();
1385   }
1386 };
1387 
1388 net.user1.orbiter.ConnectionManager.prototype.getConnectionState = function () {
1389   return this.connectionState;
1390 };
1391     
1392 // =============================================================================
1393 // CONNECTION COUNT MANAGEMENT
1394 // =============================================================================
1395 net.user1.orbiter.ConnectionManager.prototype.getReadyCount = function () {
1396   return this.readyCount;
1397 };
1398   
1399 net.user1.orbiter.ConnectionManager.prototype.getConnectFailedCount = function () {
1400   return this.connectFailedCount;
1401 };
1402   
1403 net.user1.orbiter.ConnectionManager.prototype.getConnectAttemptCount = function () {
1404   return this.connectAttemptCount;
1405 };
1406   
1407 net.user1.orbiter.ConnectionManager.prototype.getConnectAbortCount = function () {
1408   return this.connectAbortCount;
1409 };
1410     
1411 // =============================================================================
1412 // CURRENT CONNECTION LISTENERS
1413 // =============================================================================
1414 /** @private */
1415 net.user1.orbiter.ConnectionManager.prototype.readyListener = function (e) {
1416   this.setConnectionState(net.user1.orbiter.ConnectionState.READY);
1417   this.inProgressConnection = null;
1418   this.activeConnection = e.target;
1419   this.readyCount++;
1420   this.connectFailedCount = 0;
1421   this.connectAttemptCount = 0;
1422   this.connectAbortCount = 0;
1423   this.dispatchReady();
1424 };
1425 
1426 /** @private */
1427 net.user1.orbiter.ConnectionManager.prototype.connectFailureListener = function (e) {
1428   var failedConnection = e.target;
1429   this.orbiter.getLog().warn("[CONNECTION_MANAGER] Connection failed for "
1430                         + failedConnection.toString() 
1431                         + ". Status: [" + e.getStatus() + "]");
1432   
1433   this.removeConnectionListeners(failedConnection);
1434   this.inProgressConnection = null;
1435   
1436   if (this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
1437     this.dispatchConnectFailure("Connection closed by client.");
1438   } else {
1439     if (failedConnection.getHost() != failedConnection.getRequestedHost()) {
1440       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for affinity address [" + failedConnection.getHost() + "]. Removing affinity.");
1441       this.clearAffinity(failedConnection.getRequestedHost());
1442     }
1443 
1444     this.attemptedConnections.push(failedConnection);
1445     this.advanceAndConnect();
1446   }
1447 };
1448 
1449 /** @private */
1450 net.user1.orbiter.ConnectionManager.prototype.disconnectListener = function (e) {
1451   this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1452   this.removeConnectionListeners(e.target);
1453   this.activeConnection = null;
1454   this.dispatchDisconnect(e.target);
1455 };
1456 
1457 /** @private */
1458 net.user1.orbiter.ConnectionManager.prototype.clientKillConnectListener = function (e) {
1459   this.dispatchClientKillConnect(e.target);
1460   // This event is always followed by a DISCONNECT event
1461 };
1462 
1463 /** @private */
1464 net.user1.orbiter.ConnectionManager.prototype.serverKillConnectListener = function (e) {
1465   this.dispatchServerKillConnect(e.target);
1466   // This event is always followed by a DISCONNECT event
1467 };
1468 
1469 // =============================================================================
1470 // READY TIMEOUT MANAGEMENT
1471 // =============================================================================
1472     
1473 net.user1.orbiter.ConnectionManager.prototype.setReadyTimeout = function (milliseconds) {
1474   if (milliseconds > 0) {
1475     this.readyTimeout = milliseconds;
1476     this.orbiter.getLog().info("[CONNECTION_MANAGER] Ready timeout set to " + milliseconds + " ms.");
1477     if (milliseconds < 3000) {
1478       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Current ready timeout (" 
1479                            + milliseconds + ") may not allow sufficient time"
1480                            + " to connect to Union Server over a typical"
1481                            + " internet connection.");
1482     }
1483   } else {
1484     this.orbiter.getLog().warn("[CONNECTION_MANAGER] Invalid ready timeout specified: " 
1485              + milliseconds + ". Duration must be greater than zero.");
1486   }
1487 };
1488     
1489 net.user1.orbiter.ConnectionManager.prototype.getReadyTimeout = function () {
1490   return this.readyTimeout;
1491 };
1492 
1493 // =============================================================================
1494 // SERVER AFFINITY
1495 // =============================================================================
1496 net.user1.orbiter.ConnectionManager.prototype.getAffinity = function (host) {
1497   var address = this.affinityData.read("affinity", host+"address");
1498   var until = parseFloat(this.affinityData.read("affinity", host+"until"));
1499   
1500   if (address != null) {
1501     var now = new Date().getTime();
1502     if (now >= until) {
1503       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Affinity duration expired for address [" 
1504                                  + address + "], host [" + host + "]. Removing affinity.");
1505       this.clearAffinity(host);
1506     } else {
1507       return address;
1508     }
1509   }
1510 
1511   return host;
1512 };
1513 
1514 /**
1515  * @private
1516  */
1517 net.user1.orbiter.ConnectionManager.prototype.setAffinity = function (host, address, duration) {
1518   var until = new Date().getTime() + (duration*60*1000);
1519   // Don't use JSON stringify for affinity values because not all JavaScript
1520   // environments support JSON natively (e.g., non-browser VMs)
1521   this.affinityData.write("affinity", host+"address", address);
1522   this.affinityData.write("affinity", host+"until", until);
1523 
1524   this.orbiter.getLog().info("[CONNECTION_MANAGER] Assigning affinity address [" 
1525     + address + "] for supplied host [" +host + "]. Duration (minutes): "
1526     + duration);
1527 };
1528 
1529 /**
1530  * @private
1531  */
1532 net.user1.orbiter.ConnectionManager.prototype.clearAffinity = function (host) {
1533   this.affinityData.remove("affinity", host+"address");
1534   this.affinityData.remove("affinity", host+"until");
1535 };
1536     
1537 net.user1.orbiter.ConnectionManager.prototype.setGlobalAffinity = function (enabled) {
1538   if (enabled) {
1539     this.orbiter.getLog().info("[CONNECTION_MANAGER] Global server affinity selected."
1540      + " Using current environment's shared server affinity."); 
1541     this.affinityData = net.user1.utils.LocalData;
1542   } else {
1543     this.orbiter.getLog().info("[CONNECTION_MANAGER] Local server affinity selected."
1544      + " The current client will maintain its own, individual server affinity."); 
1545     this.affinityData = new net.user1.utils.MemoryStore();
1546   }
1547 };
1548 
1549 // =============================================================================
1550 // EVENT DISPATCHING
1551 // =============================================================================
1552     
1553 /** @private */
1554 net.user1.orbiter.ConnectionManager.prototype.dispatchBeginConnect = function () {
1555   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT));
1556 };
1557     
1558 /** @private */
1559 net.user1.orbiter.ConnectionManager.prototype.dispatchSelectConnection = function (connection) {
1560   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
1561       connection));
1562 };
1563     
1564 /** @private */
1565 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectFailure = function (status) {
1566   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE,
1567       null, status));
1568 };
1569     
1570 /** @private */
1571 net.user1.orbiter.ConnectionManager.prototype.dispatchDisconnect = function (connection) {
1572   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT,
1573       connection));
1574 };
1575     
1576 /** @private */
1577 net.user1.orbiter.ConnectionManager.prototype.dispatchServerKillConnect = function (connection) {
1578   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT,
1579       connection));
1580 };
1581     
1582 /** @private */
1583 net.user1.orbiter.ConnectionManager.prototype.dispatchClientKillConnect = function (connection) {
1584   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT,
1585       connection));
1586 };
1587     
1588 /** @private */
1589 net.user1.orbiter.ConnectionManager.prototype.dispatchReady = function () {
1590   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.READY));
1591 };
1592 
1593 /** @private */
1594 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectionStateChange = function () {
1595   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECTION_STATE_CHANGE));
1596 };
1597 
1598 /** @private */
1599 net.user1.orbiter.ConnectionManager.prototype.dispatchSessionTerminated = function () {
1600   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SESSION_TERMINATED));
1601 };
1602 
1603 // =============================================================================
1604 // DISPOSAL
1605 // =============================================================================    
1606 net.user1.orbiter.ConnectionManager.prototype.dispose = function () {
1607   this.removeAllConnections();
1608   this.attemptedConnections = null;
1609   this.activeConnection = null;
1610   this.inProgressConnection = null;
1611   this.connections = null;
1612 };
1613 
1614 
1615 
1616 
1617 
1618 
1619 
1620 
1621 
1622 
1623 
1624 
1625 
1626 
1627 
1628 
1629 
1630 
1631 
1632 
1633 
1634 
1635 //==============================================================================
1636 // CLASS DECLARATION
1637 //==============================================================================
1638 /** @class */
1639 net.user1.orbiter.ConnectionMonitor = function (orbiter) {
1640   // Instance variables
1641   this.connectionTimeout = 0;
1642   this.heartbeatIntervalID = -1;
1643   this.heartbeatCounter = 0;
1644   this.heartbeatEnabled = true;
1645   this.heartbeats = new net.user1.utils.UDictionary();
1646   
1647   this.oldestHeartbeat = 0;
1648   this.heartBeatFrequency = -1;
1649   
1650   this.sharedPing = false;
1651 
1652   this.autoReconnectMinMS = 0;
1653   this.autoReconnectMaxMS = 0;
1654   this.autoReconnectFrequency = -1;
1655   this.autoReconnectDelayFirstAttempt = false;
1656   this.autoReconnectTimeoutID = -1;
1657   this.autoReconnectAttemptLimit = -1;
1658   
1659   this.orbiter = orbiter;
1660   this.msgManager = orbiter.getMessageManager();
1661   this.log = orbiter.getLog();
1662   
1663   this.disposed = false;
1664   
1665   // Initialization
1666   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
1667   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
1668   this.disableHeartbeatLogging();
1669 };
1670 
1671 //==============================================================================
1672 // STATIC VARIABLES
1673 //==============================================================================
1674 net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY = 10000;
1675 net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY = 20;
1676 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY = -1;
1677 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT = -1;
1678 net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT = 60000;
1679 
1680 //==============================================================================
1681 // CONNECTION MONITORING
1682 //==============================================================================
1683 /** @private */
1684 net.user1.orbiter.ConnectionMonitor.prototype.connectReadyListener = function (e) {
1685   this.msgManager.addMessageListener(net.user1.orbiter.Messages.CLIENT_HEARTBEAT, this.heartbeatMessageListener, this);
1686   this.startHeartbeat();
1687   this.stopReconnect();
1688 }
1689 
1690 /** @private */
1691 net.user1.orbiter.ConnectionMonitor.prototype.connectCloseListener = function (e) {
1692   this.stopHeartbeat();
1693 
1694   var numAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1695   if (numAttempts == 0) {
1696     this.selectReconnectFrequency();
1697   }
1698 
1699   if (this.autoReconnectFrequency > -1) {
1700     if (this.autoReconnectTimeoutID != -1) {
1701       return;
1702     } else {
1703       // Defer reconnection until after all other listeners have processed the
1704       // CLOSE event
1705       var self = this;
1706       setTimeout(function () {
1707         // If another listener disposed of Orbiter, or disabled autoreconnect, quit
1708         if (!self.disposed && self.autoReconnectFrequency != -1) {
1709           self.log.warn("[CONNECTION_MONITOR] Disconnection detected.");
1710           if (self.autoReconnectDelayFirstAttempt
1711               && (
1712                   (numAttempts == 0)
1713                   ||
1714                   (numAttempts == 1 && self.orbiter.getConnectionManager().getReadyCount() == 0)
1715                  )
1716              ) {
1717             self.log.info("[CONNECTION_MONITOR] Delaying reconnection attempt"
1718               + " by " + self.autoReconnectFrequency + " ms...");
1719             self.scheduleReconnect(self.autoReconnectFrequency);
1720           } else {
1721             self.doReconnect();
1722           }
1723         }
1724       }, 1);
1725     }
1726   }
1727 }
1728     
1729 //==============================================================================
1730 // HEARTBEAT
1731 //==============================================================================
1732 
1733 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeat = function () {
1734   this.log.info("[CONNECTION_MONITOR] Heartbeat enabled.");
1735   this.heartbeatEnabled = true;
1736   this.startHeartbeat();
1737 }
1738 
1739 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeat = function () {
1740   this.log.info("[CONNECTION_MONITOR] Heartbeat disabled.");
1741   this.heartbeatEnabled = false;
1742   this.stopHeartbeat();
1743 }
1744 
1745 /** @private */
1746 net.user1.orbiter.ConnectionMonitor.prototype.startHeartbeat = function () {
1747   if (!this.heartbeatEnabled) {
1748     this.log.info("[CONNECTION_MONITOR] Heartbeat is currently disabled. Ignoring start request.");
1749     return;
1750   }
1751   
1752   this.stopHeartbeat();
1753   
1754   this.heartbeats = new net.user1.utils.UDictionary();
1755   
1756   var currentObj = this;
1757   var callback   = this.heartbeatTimerListener;
1758   this.heartbeatIntervalID = setInterval(function () {
1759     callback.call(currentObj);
1760   }, this.heartBeatFrequency);
1761   
1762 }
1763 
1764 /** @private */
1765 net.user1.orbiter.ConnectionMonitor.prototype.stopHeartbeat = function () {
1766   clearInterval(this.heartbeatIntervalID);
1767   this.heartbeats = null;
1768 }
1769 
1770 /** @private */
1771 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatTimerListener = function () {
1772   if (!this.orbiter.isReady()) {
1773     this.log.info("[CONNECTION_MONITOR] Orbiter is not connected. Stopping heartbeat.");
1774     this.stopHeartbeat();
1775     return;
1776   }
1777 
1778   var timeSinceOldestHeartbeat;
1779   var now = new Date().getTime();
1780   
1781   this.heartbeats[this.heartbeatCounter] = now;
1782   this.orbiter.getMessageManager().sendUPC("u2",
1783                                  net.user1.orbiter.Messages.CLIENT_HEARTBEAT, 
1784                                  this.orbiter.getClientID(),
1785                                  "",
1786                                  this.heartbeatCounter);
1787   this.heartbeatCounter++;
1788   
1789   // Assign the oldest heartbeat
1790   if (net.user1.utils.ObjectUtil.length(this.heartbeats) == 1) {
1791     this.oldestHeartbeat = now;
1792   } else { 
1793     this.oldestHeartbeat = Number.MAX_VALUE;
1794     for (var p in this.heartbeats) {
1795       if (this.heartbeats[p] < this.oldestHeartbeat) {
1796         this.oldestHeartbeat = this.heartbeats[p];
1797       }
1798     }
1799   }
1800   // Close connection if too much time has passed since the last response
1801   timeSinceOldestHeartbeat = now - this.oldestHeartbeat;
1802   if (timeSinceOldestHeartbeat > this.connectionTimeout) {
1803     this.log.warn("[CONNECTION_MONITOR] No response from server in " + 
1804                   timeSinceOldestHeartbeat + "ms. Starting automatic disconnect.");
1805     this.orbiter.disconnect();
1806   }
1807 }
1808 
1809 /** @private */
1810 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatMessageListener = function (fromClientID, id) {
1811   var ping = new Date().getTime() - this.heartbeats[parseInt(id)];
1812   if (typeof this.orbiter.self().setAttribute === "undefined") {
1813     // OrbiterMicro
1814     this.orbiter.self().ping = ping;
1815     this.orbiter.getMessageManager().sendUPC("u3",
1816                                              this.orbiter.getClientID(),
1817                                              "",
1818                                              "_PING",
1819                                              ping.toString(),
1820                                              "",
1821                                              this.sharedPing ? "4" : "0");
1822   } else {
1823     // Orbiter
1824     this.orbiter.self().setAttribute("_PING",
1825                                      ping.toString(),
1826                                      null,
1827                                      this.sharedPing);
1828   }
1829   delete this.heartbeats[parseInt(id)];
1830 }
1831 
1832 //==============================================================================
1833 // RECONNECTION
1834 //==============================================================================
1835 /** @private */
1836 net.user1.orbiter.ConnectionMonitor.prototype.reconnectTimerListener = function (e) {
1837   this.stopReconnect();
1838   if (this.orbiter.getConnectionManager().connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
1839     this.doReconnect();
1840   }
1841 }
1842 
1843 /** @private */
1844 net.user1.orbiter.ConnectionMonitor.prototype.stopReconnect = function () {
1845   clearTimeout(this.autoReconnectTimeoutID);
1846   this.autoReconnectTimeoutID = -1
1847 }
1848 
1849 /** @private */
1850 net.user1.orbiter.ConnectionMonitor.prototype.scheduleReconnect = function (milliseconds) {
1851   // Reset the timer
1852   this.stopReconnect();
1853   var currentObj = this;
1854   var callback   = this.reconnectTimerListener;
1855   this.autoReconnectTimeoutID = setTimeout(function () {
1856     currentObj.autoReconnectTimeoutID = -1;
1857     callback.call(currentObj);
1858   }, milliseconds);
1859 };
1860 
1861 /** @private */
1862 net.user1.orbiter.ConnectionMonitor.prototype.selectReconnectFrequency = function () {
1863   if (this.autoReconnectMinMS == -1) {
1864     this.autoReconnectFrequency = -1;
1865   } else if (this.autoReconnectMinMS == this.autoReconnectMaxMS) {
1866     this.autoReconnectFrequency = this.autoReconnectMinMS;
1867   } else {
1868     this.autoReconnectFrequency = getRandInt(this.autoReconnectMinMS, this.autoReconnectMaxMS);
1869     this.log.info("[CONNECTION_MONITOR] Random auto-reconnect frequency selected: [" +
1870                   this.autoReconnectFrequency + "] ms.");
1871   }
1872 
1873   function getRandInt (min, max) {
1874     return min + Math.floor(Math.random()*(max+1 - min));
1875   }
1876 };
1877 
1878 /** @private */
1879 net.user1.orbiter.ConnectionMonitor.prototype.doReconnect = function () {
1880   var numActualAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1881   var numReconnectAttempts;
1882 
1883   if (this.orbiter.getConnectionManager().getReadyCount() == 0) {
1884     numReconnectAttempts = numActualAttempts - 1;
1885   } else {
1886     numReconnectAttempts = numActualAttempts;
1887   }
1888 
1889   if (this.autoReconnectAttemptLimit != -1
1890       && numReconnectAttempts > 0
1891       && numReconnectAttempts % (this.autoReconnectAttemptLimit) == 0) {
1892     this.log.warn("[CONNECTION_MONITOR] Automatic reconnect attempt limit reached."
1893                   + " No further automatic connection attempts will be made until"
1894                   + " the next manual connection attempt.");
1895     return;
1896   }
1897 
1898   this.scheduleReconnect(this.autoReconnectFrequency);
1899 
1900   this.log.warn("[CONNECTION_MONITOR] Attempting automatic reconnect. (Next attempt in "
1901                 + this.autoReconnectFrequency + "ms.)");
1902   this.orbiter.connect();
1903 }
1904 
1905 //==============================================================================
1906 // CONFIGURATION
1907 //==============================================================================
1908 
1909 net.user1.orbiter.ConnectionMonitor.prototype.restoreDefaults = function () {
1910   this.setAutoReconnectFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY);
1911   this.setAutoReconnectAttemptLimit(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT);
1912   this.setConnectionTimeout(net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT);
1913   this.setHeartbeatFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY);
1914 }
1915 
1916 net.user1.orbiter.ConnectionMonitor.prototype.setHeartbeatFrequency = function (milliseconds) {
1917   if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY) {
1918     this.heartBeatFrequency = milliseconds;
1919     this.log.info("[CONNECTION_MONITOR] Heartbeat frequency set to " 
1920                   + milliseconds + " ms.");
1921     // Log a warning for low heartbeat frequencies...
1922     if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY && milliseconds < 1000) {
1923       this.log.info("[CONNECTION_MONITOR] HEARTBEAT FREQUENCY WARNING: " 
1924                + milliseconds + " ms. Current frequency will generate "
1925                + (Math.floor((1000/milliseconds)*10)/10) 
1926                + " messages per second per connected client.");
1927     }
1928     
1929     // If the connection is ready, then restart
1930     // the heartbeat when the heartbeat frequency changes.
1931     if (this.orbiter.isReady()) {
1932       this.startHeartbeat();
1933     }
1934   } else {
1935     this.log.warn("[CONNECTION_MONITOR] Invalid heartbeat frequency specified: " 
1936              + milliseconds + ". Frequency must be "
1937              + net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY + " or greater.");
1938   }
1939 }
1940 
1941 net.user1.orbiter.ConnectionMonitor.prototype.getHeartbeatFrequency = function () {
1942   return this.heartBeatFrequency;
1943 }
1944 
1945 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectFrequency = function (minMS, maxMS, delayFirstAttempt) {
1946   maxMS = (typeof maxMS == "undefined") ? -1 : maxMS;
1947   delayFirstAttempt = (typeof delayFirstAttempt == "undefined") ? false : delayFirstAttempt;
1948 
1949   if (minMS == 0 || minMS < -1) {
1950     this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect minMS specified: ["
1951       + minMS + "]. Value must not be zero or less than -1. Value adjusted"
1952       + " to [-1] (no reconnect).");
1953     minMS = -1;
1954   }
1955   if (minMS == -1) {
1956     this.stopReconnect();
1957   } else {
1958     if (maxMS == -1) {
1959       maxMS = minMS;
1960     }
1961     if (maxMS < minMS) {
1962       this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect maxMS specified: ["
1963                     + maxMS + "]." + " Value of maxMS must be greater than or equal "
1964                     + "to minMS. Value adjusted to [" + minMS + "].");
1965       maxMS = minMS;
1966     }
1967   }
1968 
1969   this.autoReconnectDelayFirstAttempt = delayFirstAttempt;
1970   this.autoReconnectMinMS = minMS;
1971   this.autoReconnectMaxMS = maxMS;
1972 
1973   this.log.info("[CONNECTION_MONITOR] Assigning auto-reconnect frequency settings: [minMS: "
1974                 + minMS + ", maxMS: " + maxMS + ", delayFirstAttempt: "
1975                 + delayFirstAttempt.toString() + "].");
1976   if (minMS > 0 && minMS < 1000) {
1977     this.log.info("[CONNECTION_MONITOR] RECONNECT FREQUENCY WARNING: "
1978                   + minMS + " minMS specified. Current frequency will cause "
1979                   + (Math.floor((1000/minMS)*10)/10).toString()
1980                   + " reconnection attempts per second.");
1981   }
1982   this.selectReconnectFrequency();
1983 }
1984 
1985 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectFrequency = function () {
1986   return this.autoReconnectFrequency;
1987 }
1988 
1989 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectAttemptLimit = function (attempts) {
1990   if (attempts < -1 || attempts == 0) {
1991     this.log.warn("[CONNECTION_MONITOR] Invalid Auto-reconnect attempt limit specified: " 
1992              + attempts + ". Limit must -1 or greater than 1.");
1993     return;
1994   }
1995     
1996   this.autoReconnectAttemptLimit = attempts;
1997   
1998   if (attempts == -1) {
1999     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to none."); 
2000   } else {
2001     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to " 
2002                   + attempts + " attempt(s).");
2003   }
2004 };
2005     
2006 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectAttemptLimit = function () {
2007   return this.autoReconnectAttemptLimit;
2008 }
2009 
2010 net.user1.orbiter.ConnectionMonitor.prototype.setConnectionTimeout = function (milliseconds) {
2011   if (milliseconds > 0) {
2012     this.connectionTimeout = milliseconds;
2013     this.log.info("[CONNECTION_MONITOR] Connection timeout set to " 
2014                   + milliseconds + " ms.");
2015   } else {
2016     this.log.warn("[CONNECTION_MONITOR] Invalid connection timeout specified: " 
2017                              + milliseconds + ". Frequency must be greater " 
2018                              + "than zero.");
2019   }
2020 }
2021 
2022 net.user1.orbiter.ConnectionMonitor.prototype.getConnectionTimeout = function () {
2023   return this.connectionTimeout;
2024 }
2025 
2026 net.user1.orbiter.ConnectionMonitor.prototype.sharePing = function (share) {
2027   this.sharedPing = share;
2028 }
2029 
2030 net.user1.orbiter.ConnectionMonitor.prototype.isPingShared = function () {
2031   return this.sharedPing;
2032 }
2033 
2034 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeatLogging = function () {
2035   this.log.addSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2036   this.log.addSuppressionTerm("<A>_PING</A>");
2037   this.log.addSuppressionTerm("[_PING]");
2038   this.log.addSuppressionTerm("<![CDATA[_PING]]>");
2039 }
2040 
2041 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeatLogging = function () {
2042   this.log.removeSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2043   this.log.removeSuppressionTerm("<A>_PING</A>");
2044   this.log.removeSuppressionTerm("[_PING]");
2045   this.log.removeSuppressionTerm("<![CDATA[_PING]]>");
2046 }
2047 
2048 // =============================================================================
2049 // DISPOSAL
2050 // =============================================================================
2051 
2052 net.user1.orbiter.ConnectionMonitor.prototype.dispose = function () {
2053   this.disposed = true;
2054   
2055   this.stopHeartbeat();
2056   this.stopReconnect();
2057 
2058   this.heartbeats = null;
2059   
2060   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
2061   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
2062   this.orbiter = null;
2063   this.msgManager.removeMessageListener("u7", this.u7);
2064   this.msgManager(null);
2065   this.log = null;
2066 };
2067 
2068 
2069 
2070 
2071 
2072 
2073 
2074 
2075 
2076 
2077 
2078 
2079 
2080 
2081 
2082 
2083 
2084 //==============================================================================
2085 // CLASS DECLARATION
2086 //==============================================================================
2087 /** @class
2088     @private */
2089 net.user1.orbiter.CoreMessageListener = function (orbiter) {
2090   this.orbiter = orbiter;
2091   this.log = orbiter.getLog();      
2092   this.registerCoreListeners();
2093   this.orbiter.getConnectionManager().addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
2094                                                        this.selectConnectionListener, this);
2095 };
2096 
2097 net.user1.orbiter.CoreMessageListener.prototype.registerCoreListeners = function () {
2098   var msgMan = this.orbiter.getMessageManager();
2099   msgMan.addMessageListener(net.user1.orbiter.UPC.RECEIVE_MESSAGE, this.u7, this);
2100   msgMan.addMessageListener(net.user1.orbiter.UPC.SERVER_HELLO, this.u66, this);
2101   msgMan.addMessageListener(net.user1.orbiter.UPC.SESSION_TERMINATED, this.u84, this);
2102 };
2103 
2104 net.user1.orbiter.CoreMessageListener.prototype.selectConnectionListener = function (e) {
2105   var msgMan = this.orbiter.getMessageManager();
2106   if (msgMan.removeListenersOnDisconnect) {
2107     this.registerCoreListeners();
2108   }
2109 };
2110 
2111 net.user1.orbiter.CoreMessageListener.prototype.u7 = function (message,
2112                                                                broadcastType,
2113                                                                fromClientID,
2114                                                                toRoomID) {
2115   var msgMan = this.orbiter.getMessageManager();
2116   var args;
2117   var userDefinedArgs = Array.prototype.slice.call(arguments).slice(4);
2118 
2119   // ===== To Clients, or To Server =====
2120   if (broadcastType != net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS) {
2121     args = [fromClientID].concat(userDefinedArgs);
2122     msgMan.notifyMessageListeners(message, args);
2123     return;
2124   }
2125   
2126   // ===== To Rooms =====
2127   var listeners = msgMan.getMessageListeners(message);
2128 
2129   // Split the recipient room ID into two parts
2130   var toRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(toRoomID);
2131   var toRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(toRoomID);
2132   var listenerFound; 
2133   var listenerIgnoredMessage;
2134   var messageListener;
2135                                        
2136   // ===== Run once for each message listener =====
2137   for (var i = 0; i < listeners.length; i++) {
2138     messageListener = listeners[i];
2139     listenerIgnoredMessage = true;
2140     
2141     // --- Has no "forRoomIDs" filter ---
2142     if (messageListener.getForRoomIDs() == null) {
2143       args = [fromClientID, toRoomID].concat(userDefinedArgs);
2144       messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
2145       listenerFound = true;
2146       listenerIgnoredMessage = false;
2147       continue;  // Done with this listener. On to the next.
2148     }
2149     
2150     // --- Has a "forRoomIDs" filter ---
2151     var listenerRoomIDs = messageListener.getForRoomIDs();
2152     var listenerRoomQualifier;
2153     var listenerRoomSimpleID;
2154     var listenerRoomIDString;
2155     // ===== Run once for each room id =====
2156     for (var i = 0; i < listenerRoomIDs.length; i++) {
2157       listenerRoomIDString = listenerRoomIDs[i];
2158       listenerRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(listenerRoomIDString);
2159       listenerRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(listenerRoomIDString);
2160 
2161       // Check if the listener is interested in the recipient room...
2162       if (listenerRoomQualifier == toRoomQualifier
2163           && 
2164           (listenerRoomSimpleID == toRoomSimpleID
2165            || listenerRoomSimpleID == "*")) {
2166         // Found a match. Notify the listener...
2167           
2168         // Prepare args.
2169         if (listenerRoomIDs.length == 1) {
2170           // The listener is interested in messages sent to a 
2171           // specific room only, so omit the "toRoom" arg.
2172           args = [fromClientID].concat(userDefinedArgs);
2173         } else {
2174           // The listener is interested in messages sent to 
2175           // multiple rooms, so include the "toRoomID" arg so the listener 
2176           // knows which room received the message.
2177           args = [fromClientID, toRoomID].concat(userDefinedArgs);
2178         }
2179         
2180         messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
2181         listenerFound = true;
2182         listenerIgnoredMessage = false;
2183         break; // Stop looking at this listener's room ids
2184       }
2185     } // Done looking at this listener's room ids
2186     if (listenerIgnoredMessage) {
2187       this.log.debug("Message listener ignored message: " + message + ". "
2188                      + "Listener registered to receive " 
2189                      + "messages sent to: " + messageListener.getForRoomIDs() 
2190                      + ", but message was sent to: " + toRoomID);
2191     }
2192   } // Done looking at listeners for the incoming message
2193   if (!listenerFound) {
2194     this.log.warn("No message listener handled incoming message: " 
2195                   + message + ", sent to: " + toRoomID);
2196   }
2197 };
2198 
2199 net.user1.orbiter.CoreMessageListener.prototype.u66 = function (serverVersion, 
2200                                                                 sessionID,
2201                                                                 serverUPCVersionString,
2202                                                                 protocolCompatible,
2203                                                                 affinityAddress,
2204                                                                 affinityDuration) {
2205   this.log.info("[ORBITER] Server version: " + serverVersion);
2206   this.log.info("[ORBITER] Server UPC version: " + serverUPCVersionString);
2207   
2208   var inProgressConnection = this.orbiter.getConnectionManager().getInProgressConnection();
2209   var inProgressConnectionHost = inProgressConnection.getHost();
2210   if (affinityAddress != ""
2211       && typeof affinityAddress !== "undefined"  
2212       && affinityAddress != inProgressConnectionHost) {
2213     this.orbiter.getConnectionManager().setAffinity(inProgressConnectionHost, 
2214                                                     affinityAddress, 
2215                                                     parseFloat(affinityDuration));
2216     inProgressConnection.applyAffinity();
2217   }
2218 };
2219 
2220 /**
2221  * SESSION_TERMINATED
2222  */
2223 net.user1.orbiter.CoreMessageListener.prototype.u84 = function () {
2224   this.orbiter.getConnectionManager().dispatchSessionTerminated();
2225 };
2226 //==============================================================================
2227 // CLASS DECLARATION
2228 //==============================================================================
2229 /** @class
2230     @extends net.user1.events.Event
2231 */
2232 net.user1.orbiter.OrbiterEvent = function (type, 
2233                                            serverUPCVersion,
2234                                            connectionRefusal) {
2235   net.user1.events.Event.call(this, type);
2236 
2237   this.serverUPCVersion = serverUPCVersion;
2238   this.connectionRefusal = connectionRefusal;
2239 };
2240 
2241 //==============================================================================
2242 // INHERITANCE
2243 //==============================================================================
2244 net.user1.utils.extend(net.user1.orbiter.OrbiterEvent, net.user1.events.Event);
2245  
2246 //==============================================================================
2247 // STATIC VARIABLES
2248 //==============================================================================
2249 /** @constant */
2250 net.user1.orbiter.OrbiterEvent.READY = "READY";
2251 /** @constant */
2252 net.user1.orbiter.OrbiterEvent.CLOSE = "CLOSE";
2253 /** @constant */
2254 net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE = "PROTOCOL_INCOMPATIBLE";
2255 /** @constant */
2256 net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED = "CONNECT_REFUSED";
2257 
2258 //==============================================================================
2259 // INSTANCE METHODS
2260 //==============================================================================  
2261 net.user1.orbiter.OrbiterEvent.prototype.getServerUPCVersion = function () {
2262   return this.serverUPCVersion;
2263 }
2264 
2265 net.user1.orbiter.OrbiterEvent.prototype.getConnectionRefusal = function () {
2266   return this.connectionRefusal;
2267 }
2268 
2269 net.user1.orbiter.OrbiterEvent.prototype.toString = function () {
2270   return "[object OrbiterEvent]";
2271 }  
2272 
2273 //==============================================================================
2274 // CLASS DECLARATION
2275 //==============================================================================
2276 /** @class
2277  * The Orbiter class is the root class of every OrbiterMicro application.
2278  * It provides basic tools for connecting to Union server, and gives
2279  * the application access to the core Orbiter system modules.
2280  * The Orbiter class dispatches the following events:
2281 
2282 <ul class="summary">
2283 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.READY}</li>
2284 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CLOSE}</li>
2285 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE}</li>
2286 </ul>
2287 
2288 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2289 
2290     @extends net.user1.events.EventDispatcher
2291 */
2292 net.user1.orbiter.Orbiter = function () {
2293   // Invoke superclass constructor
2294   net.user1.events.EventDispatcher.call(this);
2295   
2296   // Initialization. For non-browser environments, set window to null.
2297   this.window = typeof window == "undefined" ? null : window;
2298   
2299   // Initialize system versions.
2300   this.system = new net.user1.orbiter.System(this.window);
2301   this.log = new net.user1.logger.Logger();
2302   
2303   // Output host version information.
2304   if (typeof navigator != "undefined") {
2305     this.log.info("User Agent: " + navigator.userAgent + " " + navigator.platform);
2306   }
2307   this.log.info("Union Client Version: " + this.system.getClientType() + " " + this.system.getClientVersion().toStringVerbose());
2308   this.log.info("Client UPC Protocol Version: " + this.system.getUPCVersion().toString());
2309   this.consoleLogger = null;
2310   
2311   if (!this.system.isJavaScriptCompatible()) {
2312     // Missing required JavaScript capabilities, so abort. 
2313     this.log.fatal("[ORBITERMICRO] JavaScript version incompatibility detected." 
2314                    + " Quitting.");
2315     return;
2316   }
2317   
2318   // Set up the connection manager.
2319   this.connectionMan = new net.user1.orbiter.ConnectionManager(this);
2320   
2321   // Set up the message manager.
2322   this.messageManager = new net.user1.orbiter.MessageManager(this.log, this.connectionMan);
2323   
2324   
2325   // Set up the core message listener
2326   this.coreMsgListener = new net.user1.orbiter.CoreMessageListener(this);
2327   
2328   // Register for ConnectionManager events.
2329   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.READY, 
2330                                       this._readyListener, this);
2331   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE, 
2332                                       this._connectFailureListener, this);
2333   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT, 
2334                                       this._disconnectListener, this);
2335   
2336   // Set up the connection monitor
2337   this.connectionMonitor = new net.user1.orbiter.ConnectionMonitor(this);
2338   this.connectionMonitor.restoreDefaults();
2339   
2340   this.clientID = "";
2341   this.sessionID = "";
2342   
2343   // Self-client shim
2344   this.selfClient = {};
2345   this.selfClient.ping = -1;
2346   this.selfClient.getPing = function () {
2347     return this.ping;
2348   };
2349   
2350   // Register to be notified when a new connection is about to be opened
2351   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
2352                                       this._selectConnectionListener, this);
2353 
2354   // Enable HTTP failover connections
2355   this.httpFailoverEnabled = true;
2356   
2357   this.log.info("[ORBITERMICRO] Initialization complete.");
2358 };
2359 
2360 //==============================================================================
2361 // INHERITANCE
2362 //==============================================================================
2363 net.user1.utils.extend(net.user1.orbiter.Orbiter, net.user1.events.EventDispatcher);
2364 
2365 //==============================================================================    
2366 // CLIENT INFO
2367 //==============================================================================    
2368 net.user1.orbiter.Orbiter.prototype.getClientID = function () {
2369   return this.clientID;
2370 };
2371 
2372 net.user1.orbiter.Orbiter.prototype.self = function () {
2373   return this.selfClient;
2374 }
2375 
2376 //==============================================================================    
2377 // SESSION ID
2378 //==============================================================================     
2379 /** @private */
2380 net.user1.orbiter.Orbiter.prototype.setSessionID = function (id) {
2381   this.sessionID = id;
2382 };
2383 
2384 net.user1.orbiter.Orbiter.prototype.getSessionID = function () {
2385   return this.sessionID == null ? "" : this.sessionID;
2386 };
2387 
2388 //==============================================================================    
2389 // UPC LISTENERS
2390 //==============================================================================    
2391 /** @private */
2392 net.user1.orbiter.Orbiter.prototype.u29 = function (clientID) {
2393   this.clientID = clientID;
2394 };
2395 
2396 /** @private */
2397 net.user1.orbiter.Orbiter.prototype.u66 = function (clientID,
2398                                                     sessionID,
2399                                                     serverUPCVersionString,
2400                                                     protocolCompatible,
2401                                                     affinityAddress,
2402                                                     affinityDuration) {
2403   var serverUPCVersion = new net.user1.orbiter.VersionNumber();
2404   serverUPCVersion.fromVersionString(serverUPCVersionString);
2405   if (protocolCompatible == "false") {
2406     this.dispatchProtocolIncompatible(serverUPCVersion);
2407   }
2408 };
2409 
2410 /** @private */
2411 net.user1.orbiter.Orbiter.prototype.u164 = function (reason, description) {
2412   this.connectionMonitor.setAutoReconnectFrequency(-1);
2413   this.dispatchConnectRefused(new net.user1.orbiter.ConnectionRefusal(reason, description));
2414 };
2415 
2416 //==============================================================================    
2417 // CONNECTION
2418 //==============================================================================
2419 /** @private */   
2420 net.user1.orbiter.Orbiter.prototype.buildConnection = function (host, port, type, sendDelay) {
2421   var connection;
2422   
2423   switch (type) {
2424     case net.user1.orbiter.ConnectionType.HTTP:
2425       if (this.system.hasHTTPDirectConnection()) {
2426         connection = new net.user1.orbiter.HTTPDirectConnection();
2427       } else {
2428         connection = new net.user1.orbiter.HTTPIFrameConnection();
2429       }
2430       break;
2431 
2432     case net.user1.orbiter.ConnectionType.SECURE_HTTP:
2433       if (this.system.hasHTTPDirectConnection()) {
2434         connection = new net.user1.orbiter.SecureHTTPDirectConnection();
2435       } else {
2436         connection = new net.user1.orbiter.SecureHTTPIFrameConnection();
2437       }
2438       break;
2439 
2440     case net.user1.orbiter.ConnectionType.WEBSOCKET:
2441       connection = new net.user1.orbiter.WebSocketConnection();
2442       break;
2443 
2444     case net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET:
2445       connection = new net.user1.orbiter.SecureWebSocketConnection();
2446       break;
2447 
2448     default:
2449       throw new Error("[ORBITER] Error at buildConnection(). Invalid type specified: [" + type + "]");
2450   }
2451   
2452   try {
2453     connection.setServer(host, port);
2454   } catch (e) {
2455     this.log.error("[CONNECTION] " + connection.toString() + " " + e);
2456   } finally {
2457     this.connectionMan.addConnection(connection);
2458     if (connection instanceof net.user1.orbiter.HTTPConnection) {
2459       // Set delay after adding connection so the connection object has
2460       // access to this Orbiter object
2461       connection.setSendDelay(sendDelay);
2462     }
2463   }
2464 };
2465 
2466 /**
2467  * Connects to Union Server using the specified host and port(s).
2468  * 
2469  * @param host
2470  * @param port1
2471  * @param port2
2472  * @param ...
2473  * @param portn
2474  */
2475 net.user1.orbiter.Orbiter.prototype.connect = function (host) {
2476   this.useSecureConnect = false;
2477   this.doConnect.apply(this, arguments);
2478 };
2479 
2480 /**
2481  * <p>
2482  * The secureConnect() method is identical to the connect() method, except that
2483  * it uses an encrypted connection (TLS or SSL) rather than an
2484  * unencrypted connection. Before secureConnect() can be used, Union Server
2485  * must be configured to accept client communications over a secure gateway,
2486  * which includes the installation of a server-side security certificate. For
2487  * instructions on configuring Union Server for secure communications, see
2488  * Union Server's documentation at http://unionplatform.com.
2489  * </p>
2490  *
2491  * @see net.user1.orbiter.Orbiter#connect
2492  */
2493 net.user1.orbiter.Orbiter.prototype.secureConnect = function (host) {
2494   this.useSecureConnect = true;
2495   this.doConnect.apply(this, arguments);
2496 };
2497 
2498 /**
2499  * @private
2500  */
2501 net.user1.orbiter.Orbiter.prototype.doConnect = function (host) {
2502   var ports = Array.prototype.slice.call(arguments).slice(1);
2503   if (host != null) {
2504     this.setServer.apply(this, [host].concat(ports));
2505   }
2506   this.log.info("[ORBITER] Connecting to Union...");
2507   this.connectionMan.connect();
2508 };
2509 
2510 net.user1.orbiter.Orbiter.prototype.disconnect = function () {
2511   this.connectionMan.disconnect();
2512 };
2513 
2514 /**
2515  * Assigns the host and port(s) Orbiter should use when attempting to connect to
2516  * Union Server. The first argument is the host address (e.g., "example.com"),
2517  * Subsequent arguments list the integer ports for the connection (e.g., 80). 
2518  * Orbiter will attempt to connect over the ports in the order specified. For 
2519  * example, given the code setServer("tryunion.com", 9100, 80, 443, Orbiter
2520  * will first attempt to connect to Union Server over port 9100; if the
2521  * connection fails, Orbiter will automatically next attempt to connect over 
2522  * port 80, if that fails, Orbiter will attempt to connect to port 443. To add 
2523  * multiple hosts (not just multiple ports) to Orbiter's list of failover
2524  * connections, use ConnectionManager's addConnection() method.
2525  * 
2526  * To reduce network latency and bandwidth consumption, Orbiter automatically
2527  * attempts to connect via WebSocket wherever WebSocket is supported. Where
2528  * WebSocket is not supported, Orbiter automatically fails over to HTTP
2529  * communications.
2530  * 
2531  * Wherever possible, to allow maximum connection success by Union clients, 
2532  * Union Server should be run on port 80.
2533  * 
2534  * @param host
2535  * @param port1
2536  * @param port2
2537  * @param ...
2538  * @param portn
2539  */
2540 net.user1.orbiter.Orbiter.prototype.setServer = function (host) {
2541   if (host != null && arguments.length > 1) {
2542     if (this.connectionMan.getConnections().length > 0) {
2543       this.connectionMan.removeAllConnections();
2544     }
2545     // Where possible, create regular WebSocket connections for the specified
2546     // host and its ports.
2547     var connectionType;
2548     if (this.system.hasWebSocket()) {
2549       for (var i = 1; i < arguments.length; i++) {
2550         connectionType = this.useSecureConnect
2551                          ? net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET
2552                          : net.user1.orbiter.ConnectionType.WEBSOCKET;
2553         this.buildConnection(host, arguments[i], connectionType, -1);
2554       }
2555     } else {
2556       this.log.info("[ORBITERMICRO] WebSocket not found in host environment. Trying HTTP.");
2557     }
2558     // Next, if failover is enabled or WebSocket is not supported, create HTTPConnections
2559     if (this.isHTTPFailoverEnabled() || !this.system.hasWebSocket()) {
2560       for (i = 1; i < arguments.length; i++) {
2561         connectionType = this.useSecureConnect
2562                          ? net.user1.orbiter.ConnectionType.SECURE_HTTP
2563                          : net.user1.orbiter.ConnectionType.HTTP;
2564         this.buildConnection(host,
2565                              arguments[i],
2566                              connectionType,
2567                              net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY);
2568       }
2569     }
2570   } else {
2571     this.log.error("[ORBITERMICRO] setServer() failed. Invalid host or port supplied [" + arguments + "].");
2572   }
2573 };
2574 
2575 net.user1.orbiter.Orbiter.prototype.isReady = function () {
2576   return this.connectionMan.isReady();
2577 };
2578 
2579   
2580 //==============================================================================
2581 // HTTP FAILOVER CONFIGURATION
2582 //==============================================================================
2583 net.user1.orbiter.Orbiter.prototype.enableHTTPFailover = function () {
2584   this.httpFailoverEnabled = true;
2585 };
2586 
2587 net.user1.orbiter.Orbiter.prototype.disableHTTPFailover = function () {
2588   this.httpFailoverEnabled = false;
2589 };
2590 
2591 net.user1.orbiter.Orbiter.prototype.isHTTPFailoverEnabled = function () {
2592   return this.httpFailoverEnabled;
2593 };
2594 
2595 //==============================================================================
2596 // CONNECTION EVENT LISTENERS
2597 //==============================================================================
2598 /** @private */
2599 net.user1.orbiter.Orbiter.prototype._disconnectListener = function (e) {
2600   this.clientID = "";
2601   this.dispatchClose();
2602 };
2603 
2604 /** @private */
2605 net.user1.orbiter.Orbiter.prototype._connectFailureListener = function (e) {
2606   this.clientID = "";
2607   this.dispatchClose();
2608 };
2609 
2610 /** @private */
2611 net.user1.orbiter.Orbiter.prototype._readyListener = function (e) {
2612   this.log.info("[ORBITER] Orbiter now connected and ready.");
2613   this.dispatchReady();
2614 };
2615 
2616 /** @private */
2617 net.user1.orbiter.Orbiter.prototype._selectConnectionListener = function (e) {
2618   // Register to be notified when the client's ID is received 
2619   this.messageManager.addMessageListener("u29", this.u29, this);
2620   // Register to be notified when the server's "hello" message is received 
2621   this.messageManager.addMessageListener("u66", this.u66, this);
2622   this.messageManager.addMessageListener(net.user1.orbiter.UPC.CONNECTION_REFUSED, this.u164, this);
2623 };
2624 
2625 //==============================================================================    
2626 // INSTANCE RETRIEVAL
2627 //==============================================================================    
2628 net.user1.orbiter.Orbiter.prototype.getLog = function () {
2629   return this.log;
2630 };
2631 
2632 net.user1.orbiter.Orbiter.prototype.getMessageManager = function () {
2633   return this.messageManager;
2634 };
2635 
2636 net.user1.orbiter.Orbiter.prototype.getConnectionManager = function () {
2637   return this.connectionMan;
2638 };
2639 
2640 net.user1.orbiter.Orbiter.prototype.getConnectionMonitor = function () {
2641   return this.connectionMonitor;
2642 };
2643 
2644 net.user1.orbiter.Orbiter.prototype.getSystem = function () {
2645   return this.system;
2646 };
2647 
2648 //==============================================================================    
2649 // LOGGING
2650 //==============================================================================
2651 net.user1.orbiter.Orbiter.prototype.enableConsole = function () {
2652   if (this.consoleLogger == null) {
2653     this.consoleLogger = new net.user1.logger.ConsoleLogger(this.log);
2654   }
2655 }
2656 
2657 net.user1.orbiter.Orbiter.prototype.disableConsole = function() {
2658   if (this.consoleLogger != null) {
2659     this.consoleLogger.dispose();
2660     this.consoleLogger = null;
2661   }
2662 };
2663   
2664 //==============================================================================    
2665 // EVENT DISPATCH
2666 //==============================================================================
2667 /** @private */
2668 net.user1.orbiter.Orbiter.prototype.dispatchReady = function () {
2669   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.READY));
2670 }
2671 
2672 /** @private */
2673 net.user1.orbiter.Orbiter.prototype.dispatchClose = function () {
2674   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CLOSE));
2675 }
2676 
2677 /** @private */
2678 net.user1.orbiter.Orbiter.prototype.dispatchProtocolIncompatible = function () {
2679   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE));
2680 }
2681 
2682 /** @private */
2683 net.user1.orbiter.Orbiter.prototype.dispatchConnectRefused = function (refusal) {
2684   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED,
2685                                  null, refusal));
2686 };
2687 
2688 //==============================================================================    
2689 // TOSTRING
2690 //==============================================================================
2691 net.user1.orbiter.Orbiter.prototype.toString = function () {
2692   return "[object Orbiter]";
2693 }
2694     
2695 //==============================================================================
2696 // CONNECTION STATE CONSTANTS
2697 //==============================================================================
2698 /** @class */
2699 net.user1.orbiter.ConnectionState = new Object();
2700 /** @constant */
2701 net.user1.orbiter.ConnectionState.UNKNOWN                    = -1;
2702 /** @constant */
2703 net.user1.orbiter.ConnectionState.NOT_CONNECTED              = 0;
2704 /** @constant */
2705 net.user1.orbiter.ConnectionState.READY                      = 1;
2706 /** @constant */
2707 net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS     = 2;
2708 /** @constant */
2709 net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS  = 3;
2710 /** @constant */
2711 net.user1.orbiter.ConnectionState.LOGGED_IN                  = 4;
2712 //==============================================================================
2713 // CLASS DECLARATION
2714 //==============================================================================
2715 /** @class
2716     @extends net.user1.events.Event
2717 */
2718 net.user1.orbiter.ConnectionEvent = function (type, upc, data, connection, status) {
2719   net.user1.events.Event.call(this, type);
2720   
2721   this.upc = upc;
2722   this.data = data;
2723   this.connection = connection
2724   this.status = status;
2725 };
2726 
2727 //==============================================================================
2728 // INHERITANCE
2729 //==============================================================================
2730 net.user1.utils.extend(net.user1.orbiter.ConnectionEvent, net.user1.events.Event);
2731 
2732 //==============================================================================
2733 // STATIC VARIABLES
2734 //==============================================================================
2735 
2736 /** @constant */
2737 net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT = "BEGIN_CONNECT";
2738 /** @constant */
2739 net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE = "BEGIN_HANDSHAKE";
2740 /** @constant */
2741 net.user1.orbiter.ConnectionEvent.READY = "READY";
2742 /** @constant */
2743 net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE = "CONNECT_FAILURE";
2744 /** @constant */
2745 net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT = "CLIENT_KILL_CONNECT";
2746 /** @constant */
2747 net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT = "SERVER_KILL_CONNECT";
2748 /** @constant */
2749 net.user1.orbiter.ConnectionEvent.DISCONNECT = "DISCONNECT";
2750 /** @constant */
2751 net.user1.orbiter.ConnectionEvent.RECEIVE_UPC = "RECEIVE_UPC";
2752 /** @constant */
2753 net.user1.orbiter.ConnectionEvent.SEND_DATA = "SEND_DATA";
2754 /** @constant */
2755 net.user1.orbiter.ConnectionEvent.RECEIVE_DATA = "RECEIVE_DATA";
2756 /** @constant */
2757 net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
2758 /** @constant */
2759 net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND = "SESSION_NOT_FOUND";
2760   
2761 //==============================================================================
2762 // INSTANCE METHODS
2763 //==============================================================================
2764 
2765 net.user1.orbiter.ConnectionEvent.prototype.getUPC = function () {
2766   return this.upc;
2767 }
2768 
2769 net.user1.orbiter.ConnectionEvent.prototype.getData = function () {
2770   return this.data;
2771 }
2772 
2773 net.user1.orbiter.ConnectionEvent.prototype.getStatus = function () {
2774   return this.status;
2775 }
2776 
2777 net.user1.orbiter.ConnectionEvent.prototype.toString = function () {
2778   return "[object ConnectionEvent]";
2779 }  
2780 
2781 //==============================================================================
2782 // HTTP REQUEST MODE CONSTANTS
2783 //==============================================================================
2784 /** @class */
2785 net.user1.orbiter.ConnectionType = new Object();
2786 /** @constant */
2787 net.user1.orbiter.ConnectionType.HTTP =  "HTTP";
2788 /** @constant */
2789 net.user1.orbiter.ConnectionType.SECURE_HTTP =  "SECURE_HTTP";
2790 /** @constant */
2791 net.user1.orbiter.ConnectionType.WEBSOCKET =  "WEBSOCKET";
2792 /** @constant */
2793 net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET =  "SECURE_WEBSOCKET";
2794 //==============================================================================
2795 // CLASS DECLARATION
2796 //==============================================================================
2797 /** @class
2798  * Connection is the abstract superclass of HTTPConnection and WebSocketConnection;
2799  * it is used internally by Orbiter, and is not intended for direct use by Orbiter
2800  * developers. For information on communication with Union Server, see
2801  * Orbiter's connect() method, the WebSocketConnection class and the
2802  * HTTPDirectConnection and HTTPIFrameConnection classes.
2803  *
2804  * The Connection class dispatches the following events:
2805 
2806 <ul class="summary">
2807 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT}</li>
2808 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE}</li>
2809 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.READY}</li>
2810 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE}</li>
2811 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT}</li>
2812 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT}</li>
2813 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.DISCONNECT}</li>
2814 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_UPC}</li>
2815 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SEND_DATA}</li>
2816 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_DATA}</li>
2817 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED}</li>
2818 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND}</li>
2819 </ul>
2820 
2821 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2822 
2823     @extends net.user1.events.EventDispatcher
2824 
2825  *
2826  * @see net.user1.orbiter.Orbiter#connect
2827  * @see net.user1.orbiter.Orbiter#secureConnect
2828  * @see net.user1.orbiter.HTTPDirectConnection
2829  * @see net.user1.orbiter.HTTPIFrameConnection
2830  * @see net.user1.orbiter.WebSocketConnection
2831  * @see net.user1.orbiter.SecureHTTPDirectConnection
2832  * @see net.user1.orbiter.SecureHTTPIFrameConnection
2833  * @see net.user1.orbiter.SecureWebSocketConnection
2834  */
2835 net.user1.orbiter.Connection = function (host, port, type) {
2836   // Call superconstructor
2837   net.user1.events.EventDispatcher.call(this);
2838 
2839   // Variables
2840   this.mostRecentConnectAchievedReady = false;
2841   this.mostRecentConnectTimedOut = false;
2842   this.readyCount = 0;
2843   this.connectAttemptCount = 0;
2844   this.connectAbortCount = 0;
2845   this.readyTimeoutID = 0;
2846   this.readyTimeout = 0;
2847   this.orbiter = null;
2848   this.disposed = false;
2849   this.requestedHost = null;
2850   
2851   // Initialization
2852   this.setServer(host, port);
2853   this.connectionType = type;
2854   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
2855 };
2856 
2857 //==============================================================================    
2858 // INHERITANCE
2859 //============================================================================== 
2860 net.user1.utils.extend(net.user1.orbiter.Connection, net.user1.events.EventDispatcher);
2861 
2862 //==============================================================================    
2863 // DEPENDENCIES
2864 //============================================================================== 
2865 /** @private */
2866 net.user1.orbiter.Connection.prototype.setOrbiter = function (orbiter) {
2867   if (this.orbiter != null) {
2868     this.orbiter.getMessageManager().removeMessageListener("u63", this.u63);
2869     this.orbiter.getMessageManager().removeMessageListener("u66", this.u66);
2870     this.orbiter.getMessageManager().removeMessageListener("u84", this.u84);
2871     this.orbiter.getMessageManager().removeMessageListener("u85", this.u85);
2872   }
2873   this.orbiter = orbiter;
2874 }
2875   
2876 //==============================================================================    
2877 // CONNECT/DISCONNECT
2878 //============================================================================== 
2879 net.user1.orbiter.Connection.prototype.connect = function () {
2880   this.disconnect();
2881   this.applyAffinity();
2882   this.orbiter.getLog().info(this.toString() + " Attempting connection...");
2883   this.connectAttemptCount++;
2884   this.mostRecentConnectAchievedReady = false;
2885   this.mostRecentConnectTimedOut = false;
2886   this.connectionState = net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS;
2887   // Start the ready timer. Ready state must be achieved before the timer
2888   // completes or the connection will auto-disconnect.
2889   this.startReadyTimer();
2890   this.dispatchBeginConnect();
2891 }
2892   
2893 net.user1.orbiter.Connection.prototype.disconnect = function () {
2894   var state = this.connectionState;
2895  
2896   if (state != net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
2897     this.deactivateConnection();
2898  
2899     if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
2900       this.connectAbortCount++;
2901       this.dispatchConnectFailure("Client closed connection before READY state was achieved.");
2902     } else {
2903       this.dispatchClientKillConnect();
2904     }
2905   }
2906 }
2907     
2908 /** @private */
2909 net.user1.orbiter.Connection.prototype.deactivateConnection = function () {
2910   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
2911   this.stopReadyTimer();
2912   this.orbiter.setSessionID("");
2913 }
2914   
2915 //==============================================================================    
2916 // CONNECTION CONFIGURATION
2917 //==============================================================================    
2918 net.user1.orbiter.Connection.prototype.setServer = function (host,
2919                                                              port) {
2920   this.requestedHost = host;
2921       
2922   // Check for valid ports
2923   if (port < 1 || port > 65536) {
2924     throw new Error("Illegal port specified [" + port + "]. Must be greater than 0 and less than 65537.");
2925   }
2926   this.port  = port;
2927 }
2928 
2929 net.user1.orbiter.Connection.prototype.getRequestedHost = function () {
2930   return this.requestedHost;
2931 };
2932 
2933 net.user1.orbiter.Connection.prototype.getHost = function () {
2934   if (this.host == null) {
2935     return this.getRequestedHost();
2936   } else {
2937     return this.host;
2938   }
2939 };
2940 
2941 net.user1.orbiter.Connection.prototype.getPort = function () {
2942   return this.port;
2943 };
2944 
2945 net.user1.orbiter.Connection.prototype.getType = function () {
2946   return this.connectionType;
2947 };
2948     
2949 //==============================================================================
2950 // READY HANDSHAKE
2951 //==============================================================================
2952 /** @private */
2953 net.user1.orbiter.Connection.prototype.beginReadyHandshake = function () {
2954   this.dispatchBeginHandshake();
2955   
2956   if (!this.orbiter.getMessageManager().hasMessageListener("u63", this.u63)) {
2957     this.orbiter.getMessageManager().addMessageListener("u63", this.u63, this);
2958     this.orbiter.getMessageManager().addMessageListener("u66", this.u66, this);
2959     this.orbiter.getMessageManager().addMessageListener("u84", this.u84, this);
2960     this.orbiter.getMessageManager().addMessageListener("u85", this.u85, this);
2961   }
2962   
2963   this.sendHello();
2964 }
2965 
2966 /** @private */
2967 net.user1.orbiter.Connection.prototype.sendHello = function() {
2968   var helloString = this.buildHelloMessage();
2969   this.orbiter.getLog().debug(this.toString() + " Sending CLIENT_HELLO: " + helloString);
2970   this.transmitHelloMessage(helloString);
2971 }
2972 
2973 /** @private */
2974 net.user1.orbiter.Connection.prototype.buildHelloMessage = function () {
2975   var helloString = "<U><M>u65</M>"
2976     + "<L>"
2977     + "<A>" + this.orbiter.getSystem().getClientType() + "</A>"
2978     + "<A>" + (typeof navigator != "undefined" ? navigator.userAgent + ";" : "") 
2979     + this.orbiter.getSystem().getClientVersion().toStringVerbose() + "</A>"
2980     + "<A>" + this.orbiter.getSystem().getUPCVersion().toString() + "</A></L></U>";
2981   return helloString;
2982 }
2983 
2984 /** @private */
2985 net.user1.orbiter.Connection.prototype.transmitHelloMessage = function (helloString) {
2986   this.send(helloString);
2987 }
2988     
2989 //==============================================================================
2990 // READY TIMER
2991 //==============================================================================
2992 /** @private */
2993 net.user1.orbiter.Connection.prototype.readyTimerListener = function () {
2994   this.stopReadyTimer();
2995   if (this.connectionState == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
2996     this.orbiter.getLog().warn("[CONNECTION] " + this.toString() + " Failed to achieve" + 
2997             " ready state after " + this.readyTimeout + "ms. Aborting connection...");
2998     this.mostRecentConnectTimedOut = true;
2999     this.disconnect();
3000   }
3001 }
3002 
3003 /** @private */
3004 net.user1.orbiter.Connection.prototype.stopReadyTimer = function () {
3005   if (this.readyTimeoutID != -1) {
3006     clearTimeout(this.readyTimeoutID);
3007   }
3008 }
3009 
3010 /** @private */
3011 net.user1.orbiter.Connection.prototype.startReadyTimer = function () {
3012   var currentObj = this;
3013   var callback   = this.readyTimerListener;
3014   this.stopReadyTimer();
3015   this.readyTimeout = this.orbiter.getConnectionManager().getReadyTimeout();
3016   var self = this;
3017   this.readyTimeoutID = setTimeout (function () {
3018     callback.call(currentObj);
3019   }, self.readyTimeout);
3020 }
3021 
3022 //==============================================================================
3023 // READY STATE ACCESS
3024 //==============================================================================
3025 /** @private */
3026 net.user1.orbiter.Connection.prototype.getReadyCount = function () {
3027   return this.readyCount;
3028 }
3029     
3030 net.user1.orbiter.Connection.prototype.isReady = function () {
3031   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
3032 }
3033 
3034 /** @private */
3035 net.user1.orbiter.Connection.prototype.isValid = function () {
3036   if (this.mostRecentConnectAchievedReady) {
3037     this.orbiter.getLog().debug(this.toString() + " Connection is"
3038       + " valid because its last connection attempt succeeded.");
3039     return true;
3040   }
3041   
3042   if (this.connectAttemptCount == 0) {
3043     this.orbiter.getLog().debug(this.toString() + " Connection is"
3044       + " valid because it has either never attempted to connect, or has not"
3045       + " attempted to connect since its last successful connection.");
3046     return true;
3047   }
3048   
3049   if ((this.connectAttemptCount > 0) && 
3050       (this.connectAttemptCount == this.connectAbortCount)
3051       && !this.mostRecentConnectTimedOut) {
3052     this.orbiter.getLog().debug(this.toString() + " Connection is"
3053       + " valid because either all connection attempts ever or all"
3054       + " connection attempts since its last successful connection were"
3055       + " aborted before the ready timeout was reached.");
3056     return true;
3057   }
3058   
3059   this.orbiter.getLog().debug(this.toString() + " Connection is not"
3060     + " valid; its most recent connection failed to achieve a ready state.");
3061   return false;
3062 }
3063 
3064     
3065 //==============================================================================
3066 // UPC LISTENERS
3067 //==============================================================================
3068 /** @private */
3069 net.user1.orbiter.Connection.prototype.u63 = function () {
3070   this.stopReadyTimer();
3071   this.connectionState = net.user1.orbiter.ConnectionState.READY;
3072   this.mostRecentConnectAchievedReady = true;
3073   this.readyCount++;
3074   this.connectAttemptCount = 0;
3075   this.connectAbortCount   = 0;
3076   this.dispatchReady();
3077 }    
3078 
3079 /** @private */
3080 net.user1.orbiter.Connection.prototype.u66 = function (serverVersion, 
3081                                                        sessionID, 
3082                                                        upcVersion, 
3083                                                        protocolCompatible,
3084                                                        affinityAddress,
3085                                                        affinityDuration) {
3086   this.orbiter.setSessionID(sessionID);
3087 };
3088 
3089 /** @private */
3090 net.user1.orbiter.Connection.prototype.u84 = function () {
3091   this.dispatchSessionTerminated();
3092 }    
3093 
3094 /** @private */
3095 net.user1.orbiter.Connection.prototype.u85 = function () {
3096   this.dispatchSessionNotFound();
3097 }    
3098 
3099 //==============================================================================    
3100 // SERVER AFFINITY
3101 //==============================================================================
3102 /** @private */
3103 net.user1.orbiter.Connection.prototype.applyAffinity = function () {
3104   var affinityAddress = this.orbiter.getConnectionManager().getAffinity(this.requestedHost);
3105   if (affinityAddress == this.requestedHost) {
3106     this.orbiter.getLog().info(this.toString() + " No affinity address found for requested host [" 
3107                                + this.requestedHost + "]. Using requested host for next connection attempt.");
3108   } else {
3109     this.orbiter.getLog().info(this.toString() + " Applying affinity address [" + affinityAddress + "] for supplied host [" + this.requestedHost + "].");
3110   }
3111   this.host = affinityAddress;
3112 }
3113 
3114 //==============================================================================    
3115 // TOSTRING
3116 //==============================================================================     
3117 net.user1.orbiter.Connection.prototype.toString = function () {
3118   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
3119           + ", host: " + (this.host == null ? "" : this.host) 
3120           + ", port: " + this.port + "]";
3121   return s;
3122 }
3123     
3124 //==============================================================================    
3125 // EVENT DISPATCHING
3126 //==============================================================================  
3127 /** @private */
3128 net.user1.orbiter.Connection.prototype.dispatchSendData = function (data) {
3129   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SEND_DATA,
3130                                     null, data, this));
3131 }
3132 
3133 /** @private */
3134 net.user1.orbiter.Connection.prototype.dispatchReceiveData = function (data) {
3135   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_DATA,
3136                                     null, data, this));
3137 }
3138 
3139 /** @private */
3140 net.user1.orbiter.Connection.prototype.dispatchConnectFailure = function (status) {
3141   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
3142                                     null, null, this, status));
3143 }
3144 
3145 /** @private */
3146 net.user1.orbiter.Connection.prototype.dispatchBeginConnect = function () {
3147   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT,
3148                                     null, null, this));
3149 }
3150 
3151 /** @private */
3152 net.user1.orbiter.Connection.prototype.dispatchBeginHandshake = function () {
3153   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE,
3154                                     null, null, this));
3155 }
3156 
3157 /** @private */
3158 net.user1.orbiter.Connection.prototype.dispatchReady = function () {
3159   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.READY,
3160                                     null, null, this));
3161 }
3162 
3163 /** @private */
3164 net.user1.orbiter.Connection.prototype.dispatchServerKillConnect  = function () {
3165   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT,
3166                                     null, null, this));
3167   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
3168                                     null, null, this));
3169 }
3170 
3171 /** @private */
3172 net.user1.orbiter.Connection.prototype.dispatchClientKillConnect = function () {
3173     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT,
3174                                       null, null, this));
3175     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
3176                                       null, null, this));
3177 }
3178 
3179 /** @private */
3180 net.user1.orbiter.Connection.prototype.dispatchSessionTerminated = function () {
3181   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED,
3182                                     null, null, this));
3183 }
3184 
3185 /** @private */
3186 net.user1.orbiter.Connection.prototype.dispatchSessionNotFound = function () {
3187   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND,
3188                                     null, null, this));
3189 }
3190 
3191 //==============================================================================    
3192 // DISPOSAL
3193 //==============================================================================  
3194 /** @private */
3195 net.user1.orbiter.Connection.prototype.dispose = function () {
3196   this.disposed = true;
3197   this.messageManager.removeMessageListener("u63", this.u63);
3198   this.messageManager.removeMessageListener("u66", this.u66);
3199   this.messageManager.removeMessageListener("u84", this.u84);
3200   this.messageManager.removeMessageListener("u85", this.u85);
3201   this.stopReadyTimer();
3202   this.readyTimer = null;
3203   this.orbiter = null;
3204 }
3205 //==============================================================================    
3206 // CLASS DECLARATION
3207 //==============================================================================
3208 /**
3209  * @class
3210  *
3211  * <p>
3212  * The WebSocketConnection class is used by Orbiter to communicate with
3213  * Union Server over a persistent TCP/IP socket. Normally, developers need not
3214  * use the WebSocketConnection class directly, and should instead make connections
3215  * via the Orbiter class's connect() method. However, the
3216  * WebSocketConnection class is required for fine-grained connection configuration,
3217  * such as defining failover socket connections for multiple Union Servers
3218  * running at different host addresses.
3219  * </p>
3220  *
3221  * <p>
3222  * By default, Orbiter uses WebSocketConnection connections to communicate
3223  * with Union Server. WebSocketConnection connections offer faster response times than
3224  * HTTP connections, but occupy an operating-system-level socket continuously
3225  * for the duration of the connection. If a WebSocketConnection connection
3226  * cannot be established (due to, say, a restrictive firewall), Orbiter
3227  * automatically attempts to communicate using HTTP requests sent via an
3228  * HTTPDirectConnection or HTTPIFrameConnection. Developers can override
3229  * Orbiter's default connection failover system by manually configuring
3230  * connections using the ConnectionManager class and Orbiter's
3231  * disableHTTPFailover() method.</p>
3232  *
3233  * <p>
3234  * For secure WebSocket and HTTP communications, see SecureWebSocketConnection,
3235  * SecureHTTPDirectConnection, and SecureHTTPIFrameConnection.
3236  * </p>
3237  *
3238  * For a list of events dispatched by WebSocketConnection, see
3239  * WebSocketConnection's superclass, {@link net.user1.orbiter.Connection}.
3240  *
3241  * @extends net.user1.orbiter.Connection
3242  *
3243  * @see net.user1.orbiter.Orbiter#connect
3244  * @see net.user1.orbiter.Orbiter#secureConnect
3245  * @see net.user1.orbiter.SecureWebSocketConnection
3246  * @see net.user1.orbiter.SecureHTTPDirectConnection
3247  * @see net.user1.orbiter.SecureHTTPIFrameConnection
3248  */
3249 net.user1.orbiter.WebSocketConnection = function (host, port, type) {
3250   // Invoke superclass constructor
3251   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.WEBSOCKET);
3252   
3253   this.socket = null;
3254 };
3255 
3256 //==============================================================================
3257 // INHERITANCE
3258 //==============================================================================
3259 net.user1.utils.extend(net.user1.orbiter.WebSocketConnection, net.user1.orbiter.Connection);
3260     
3261 //==============================================================================    
3262 // SOCKET OBJECT MANAGEMENT
3263 //==============================================================================
3264 /** @private */     
3265 net.user1.orbiter.WebSocketConnection.prototype.getNewSocket = function () {
3266   // Deactivate the old socket
3267   this.deactivateSocket(this.socket);
3268   
3269   // Create the new socket
3270   if (typeof MozWebSocket != "undefined") {
3271     // Firefox 6
3272     this.socket = new MozWebSocket(this.buildURL());
3273   } else {
3274     // Other browsers
3275     this.socket = new WebSocket(this.buildURL());
3276   }
3277 
3278   // Register for socket events
3279   var self = this;
3280   this.socket.onopen = function (e) {self.connectListener(e)};
3281   this.socket.onmessage = function (e) {self.dataListener(e)};
3282   this.socket.onclose = function (e) {self.closeListener(e)};
3283   this.socket.onerror = function (e) {self.ioErrorListener(e)};
3284 };
3285 
3286 /** @private */
3287 net.user1.orbiter.WebSocketConnection.prototype.buildURL = function () {
3288   return "ws://" + this.host + ":" + this.port;
3289 };
3290 
3291 /** @private */ 
3292 net.user1.orbiter.WebSocketConnection.prototype.deactivateSocket = function (oldSocket) {
3293   if (oldSocket == null) {
3294     return;
3295   }
3296   
3297   this.socket.onopen = null;
3298   this.socket.onmessage = null;
3299   this.socket.onclose = null;
3300   this.socket.onerror = null;
3301   
3302   try {
3303     oldSocket.close()
3304   } catch (e) {
3305     // Do nothing
3306   }
3307   
3308   this.socket = null;
3309 };
3310     
3311 //==============================================================================    
3312 // CONNECTION AND DISCONNECTION
3313 //==============================================================================    
3314   
3315 net.user1.orbiter.WebSocketConnection.prototype.connect = function () {
3316   net.user1.orbiter.Connection.prototype.connect.call(this);
3317       
3318   // Attempt to connect
3319   try {
3320     this.getNewSocket();
3321   } catch (e) {
3322     // Socket could not be opened
3323     this.deactivateConnection();
3324     this.dispatchConnectFailure(e.toString());
3325   }
3326 };
3327 
3328 /** @private */ 
3329 net.user1.orbiter.WebSocketConnection.prototype.deactivateConnection = function () {
3330   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
3331   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
3332   this.deactivateSocket(this.socket);
3333   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
3334 };    
3335 
3336 //==============================================================================    
3337 // SOCKET CONNECTION LISTENERS
3338 //==============================================================================
3339 /** @private */     
3340 net.user1.orbiter.WebSocketConnection.prototype.connectListener = function (e) {
3341   if (this.disposed) return;
3342   
3343   this.orbiter.getLog().debug(this.toString() + " Socket connected.");
3344   this.beginReadyHandshake();
3345 }
3346   
3347 /** @private */ 
3348 net.user1.orbiter.WebSocketConnection.prototype.closeListener = function (e) {
3349   if (this.disposed) return;
3350   
3351   var state = this.connectionState;
3352   this.deactivateConnection();
3353   
3354   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
3355     this.dispatchConnectFailure("WebSocket onclose: Server closed connection before READY state was achieved.");
3356   } else {
3357     this.dispatchServerKillConnect();
3358   }
3359 };
3360 
3361 /** @private */ 
3362 net.user1.orbiter.WebSocketConnection.prototype.ioErrorListener = function (e) {
3363   if (this.disposed) return;
3364   
3365   var state = this.connectionState;
3366   this.deactivateConnection();
3367   
3368   // Note: when Union closes the connection, Firefox 7 dispatches onerror, not 
3369   // onclose, so treat onerror like an onclose event
3370   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
3371     this.dispatchConnectFailure("WebSocket onerror: Server closed connection before READY state was achieved.");
3372   } else {
3373     this.dispatchServerKillConnect();
3374   }
3375 };
3376 
3377 //==============================================================================    
3378 // DATA RECEIVING AND SENDING
3379 //==============================================================================  
3380 /** @private */ 
3381 net.user1.orbiter.WebSocketConnection.prototype.dataListener = function (dataEvent) {
3382   if (this.disposed) return;
3383 
3384   var data = dataEvent.data;
3385   this.dispatchReceiveData(data);
3386 
3387   if (data.indexOf("<U>") == 0) {
3388     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(
3389                                       net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
3390                                       data));
3391   } else {
3392     // The message isn't UPC. Must be an error...
3393     this.orbiter.getLog().error(this.toString() + " Received invalid message" 
3394                                + " (not UPC or malformed UPC): " + data);
3395   }
3396 };
3397 
3398 /** @private */ 
3399 net.user1.orbiter.WebSocketConnection.prototype.send = function (data) {
3400   this.dispatchSendData(data);
3401   this.socket.send(data);
3402 };
3403     
3404 // =============================================================================
3405 // DISPOSAL
3406 // =============================================================================
3407 /** @private */ 
3408 net.user1.orbiter.WebSocketConnection.prototype.dispose = function () {
3409   net.user1.orbiter.Connection.prototype.dispose.call(this);
3410   this.deactivateSocket(this.socket);
3411 };
3412 //==============================================================================    
3413 // CLASS DECLARATION
3414 //==============================================================================
3415 /** @class
3416  *
3417  * <p>
3418  * The SecureWebSocketConnection class is identical to WebSocketConnection
3419  * except that it performs communications over WSS (i.e., an encrypted TLS or
3420  * SSL socket connection) rather than plain WebSocket.</p>
3421  *
3422  * For a list of events dispatched by SecureWebSocketConnection, see
3423  * {@link net.user1.orbiter.Connection}.
3424  *
3425  * @extends net.user1.orbiter.WebSocketConnection
3426  *
3427  * @see net.user1.orbiter.WebSocketConnection
3428  */
3429 net.user1.orbiter.SecureWebSocketConnection = function (host, port) {
3430   // Invoke superclass constructor
3431   net.user1.orbiter.WebSocketConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET);
3432 };
3433 
3434 //==============================================================================
3435 // INHERITANCE
3436 //==============================================================================
3437 net.user1.utils.extend(net.user1.orbiter.SecureWebSocketConnection, net.user1.orbiter.WebSocketConnection);
3438     
3439 /** @private */
3440 net.user1.orbiter.SecureWebSocketConnection.prototype.buildURL = function () {
3441   return "wss://" + this.host + ":" + this.port;
3442 };
3443 //==============================================================================    
3444 // CLASS DECLARATION
3445 //==============================================================================
3446 /**
3447  * @class
3448  *
3449  * HTTPConnection is the abstract superclass of HTTPDirectConnection and
3450  * HTTPIFrameConnection; it is used internally by Orbiter, and is not intended
3451  * for direct use by Orbiter developers. For information on HTTP communication
3452  * with Union Server, see the HTTPDirectConnection and HTTPIFrameConnection classes.
3453  *
3454  * For a list of events dispatched by HTTPConnection, see HTTPConnection's
3455  * superclass, {@link net.user1.orbiter.Connection}.
3456  *
3457  * @extends net.user1.orbiter.Connection
3458  *
3459  *
3460  * @see net.user1.orbiter.HTTPDirectConnection
3461  * @see net.user1.orbiter.HTTPIFrameConnection
3462  */
3463 net.user1.orbiter.HTTPConnection = function (host, port, type) {
3464   // Invoke superclass constructor
3465   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
3466 
3467   // Instance variables
3468   this.url = "";
3469   this.sendDelayTimerEnabled = true;
3470   this.sendDelayTimeoutID = -1;
3471   this.sendDelayTimerRunning = false;
3472   this.sendDelay = net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY;
3473   
3474   this.messageQueue = new Array();
3475   
3476   this.retryDelay = 500;
3477   this.retryHelloTimeoutID = -1;
3478   this.retryIncomingTimeoutID = -1;
3479   this.retryOutgoingTimeoutID = -1;
3480 
3481   this.helloResponsePending = false;
3482   this.outgoingResponsePending = false;
3483   
3484   // Initialization
3485   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED, this.sessionTerminatedListener, this);
3486   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND, this.sessionNotFoundListener, this);
3487 };
3488 
3489 //==============================================================================
3490 // INHERITANCE
3491 //==============================================================================
3492 net.user1.utils.extend(net.user1.orbiter.HTTPConnection, net.user1.orbiter.Connection);
3493 
3494 //==============================================================================    
3495 // STATIC VARIABLES
3496 //==============================================================================    
3497 /** @constant */
3498 net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY = 300;
3499     
3500 //==============================================================================    
3501 // ABSTRACT METHODS (MUST BE IMPLEMENTED BY SUBCLASSES)
3502 //==============================================================================    
3503     
3504 net.user1.orbiter.HTTPConnection.prototype.doRequestDeactivation = net.user1.utils.abstractError;
3505 net.user1.orbiter.HTTPConnection.prototype.doSendHello = net.user1.utils.abstractError;
3506 net.user1.orbiter.HTTPConnection.prototype.doRetryHello = net.user1.utils.abstractError;
3507 net.user1.orbiter.HTTPConnection.prototype.doSendOutgoing = net.user1.utils.abstractError;
3508 net.user1.orbiter.HTTPConnection.prototype.doRetryOutgoing = net.user1.utils.abstractError;
3509 net.user1.orbiter.HTTPConnection.prototype.doSendIncoming = net.user1.utils.abstractError;
3510 net.user1.orbiter.HTTPConnection.prototype.doRetryIncoming = net.user1.utils.abstractError;
3511 net.user1.orbiter.HTTPConnection.prototype.doDispose = net.user1.utils.abstractError;
3512     
3513 //==============================================================================    
3514 // CONNECTION AND DISCONNECTION
3515 //==============================================================================    
3516 net.user1.orbiter.HTTPConnection.prototype.connect = function () {
3517   net.user1.orbiter.Connection.prototype.connect.call(this);
3518 };
3519     
3520 /** @private */
3521 net.user1.orbiter.HTTPConnection.prototype.deactivateConnection = function () {
3522   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
3523   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
3524   this.stopSendDelayTimer();
3525   if (this.retryHelloTimeoutID != -1) {
3526     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled hello-request retry.");
3527     clearTimeout(this.retryHelloTimeoutID);
3528     this.retryHelloTimeoutID = -1
3529   }
3530   if (this.retryIncomingTimeoutID != -1) {
3531     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled incoming-request retry.");
3532     clearTimeout(this.retryIncomingTimeoutID);
3533     this.retryIncomingTimeoutID = -1
3534   }
3535   if (this.retryOutgoingTimeoutID != -1) {
3536     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled outgoing-request retry.");
3537     clearTimeout(this.retryOutgoingTimeoutID);
3538     this.retryOutgoingTimeoutID = -1
3539   }
3540   this.deactivateHTTPRequests();
3541   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
3542 };
3543     
3544 /** @private */
3545 net.user1.orbiter.HTTPConnection.prototype.deactivateHTTPRequests = function () {
3546   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Closing all pending HTTP requests.");
3547   this.doRequestDeactivation();
3548   this.helloResponsePending = false;
3549   this.outgoingResponsePending = false;
3550 };
3551 
3552 //==============================================================================    
3553 // SESSION MANAGEMENT
3554 //==============================================================================     
3555 
3556 /** @private */
3557 net.user1.orbiter.HTTPConnection.prototype.sessionTerminatedListener = function (e) {
3558   var state = this.connectionState;
3559   this.deactivateConnection();
3560   
3561   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
3562     this.dispatchConnectFailure("Server terminated session before READY state was achieved.");
3563   } else {
3564     this.dispatchServerKillConnect();
3565   }
3566 };
3567 
3568 /** @private */
3569 net.user1.orbiter.HTTPConnection.prototype.sessionNotFoundListener = function (e) {
3570   var state = this.connectionState;
3571   
3572   this.deactivateConnection();
3573   
3574   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
3575     this.dispatchConnectFailure("Client attempted to reestablish an expired session"
3576                                 + " or establish an unknown session.");
3577   } else {
3578     this.dispatchServerKillConnect();
3579   }
3580 }
3581 
3582     
3583 //==============================================================================    
3584 // SERVER ASSIGNMENT
3585 //==============================================================================    
3586 /** @private */
3587 net.user1.orbiter.HTTPConnection.prototype.setServer = function (host, port) {
3588   try {
3589     net.user1.orbiter.Connection.prototype.setServer.call(this, host, port);
3590   } finally {
3591     this.buildURL();
3592   }
3593 }
3594 
3595 /** @private */
3596 net.user1.orbiter.HTTPConnection.prototype.buildURL = function () {
3597   this.url = "http://" + this.host + ":" + this.port;
3598 }
3599 
3600 //==============================================================================    
3601 // OUTGOING DELAY TIMER
3602 //==============================================================================    
3603 /** @private */
3604 net.user1.orbiter.HTTPConnection.prototype.sendDelayTimerListener = function () {
3605   this.sendDelayTimerRunning = false;
3606   if (this.messageQueue.length > 0) {
3607     this.flushMessageQueue();
3608   } else {
3609     // No messages in queue, so take no action.
3610   }
3611 }
3612     
3613 /** @private */
3614 net.user1.orbiter.HTTPConnection.prototype.stopSendDelayTimer = function () {
3615   this.sendDelayTimerRunning = false;
3616   if (this.sendDelayTimeoutID != -1) {
3617     clearTimeout(this.sendDelayTimeoutID);
3618   }
3619   this.sendDelayTimeoutID = -1;
3620 }
3621     
3622 /** @private */
3623 net.user1.orbiter.HTTPConnection.prototype.startSendDelayTimer = function () {
3624   this.stopSendDelayTimer();
3625   var currentObj = this;
3626   var callback   = this.sendDelayTimerListener;
3627   this.sendDelayTimerRunning = true;
3628   this.sendDelayTimeoutID = setTimeout(function () {
3629     callback.call(currentObj);
3630   }, this.sendDelay);
3631 }
3632     
3633 net.user1.orbiter.HTTPConnection.prototype.setSendDelay = function (milliseconds) {
3634   if (milliseconds > 0) {
3635     if ((milliseconds != this.sendDelay)) {
3636       this.sendDelay = milliseconds;
3637       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Send delay set to: ["
3638                              + milliseconds + "]."); 
3639     }
3640     this.sendDelayTimerEnabled = true;
3641   } else if (milliseconds == -1) {
3642     this.orbiter.getLog().debug("[CONNECTION] " + toString() + " Send delay disabled.");
3643     this.sendDelayTimerEnabled = false;
3644     this.stopSendDelayTimer();
3645   } else {
3646     throw new Error("[CONNECTION]" + this.toString() + " Invalid send-delay specified: [" 
3647                     + milliseconds + "]."); 
3648   }
3649 }
3650     
3651 net.user1.orbiter.HTTPConnection.prototype.getSendDelay = function () {
3652   return this.sendDelay;
3653 }
3654 
3655 //==============================================================================    
3656 // RETRY DELAY
3657 //============================================================================== 
3658 net.user1.orbiter.HTTPConnection.prototype.setRetryDelay = function (milliseconds) {
3659   if (milliseconds > -1) {
3660     if (milliseconds != this.retryDelay) {
3661       this.retryDelay = milliseconds;
3662       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Retry delay set to: ["
3663                                   + milliseconds + "]."); 
3664     }
3665   } else {
3666     throw new Error("[CONNECTION]" + this.toString() + " Invalid retry delay specified: [" 
3667                     + milliseconds + "]."); 
3668   }
3669 }
3670 
3671 //==============================================================================    
3672 // DATA SENDING AND QUEUING
3673 //==============================================================================  
3674     
3675 net.user1.orbiter.HTTPConnection.prototype.send = function (data) {
3676   // If the timer isn't running...
3677   if (!this.sendDelayTimerRunning) {
3678     // ...it is either disabled or expired. Either way, it's time to 
3679     // attempt to flush the queue.
3680     this.messageQueue.push(data);
3681     this.flushMessageQueue();
3682   } else {
3683     // The send-delay timer is running, so we can't send yet. Just queue the message.
3684     this.messageQueue.push(data);
3685   }
3686 }
3687     
3688 /** @private */
3689 net.user1.orbiter.HTTPConnection.prototype.flushMessageQueue = function () {
3690   if (!this.outgoingResponsePending) {
3691     this.openNewOutgoingRequest(this.messageQueue.join(""));
3692     this.messageQueue = new Array();
3693   } else {
3694     // AN OUTGOING RESPONSE IS STILL PENDING, SO DON'T SEND A NEW ONE
3695   }
3696 }
3697 
3698 //==============================================================================    
3699 // HELLO REQUEST MANAGEMENT
3700 //==============================================================================  
3701 
3702 /** @private */
3703 net.user1.orbiter.HTTPConnection.prototype.transmitHelloMessage = function (helloString) {
3704   this.dispatchSendData(helloString);
3705   this.helloResponsePending = true;
3706   this.doSendHello(helloString);
3707 }    
3708 
3709 /** @private */
3710 net.user1.orbiter.HTTPConnection.prototype.helloCompleteListener = function (data) {
3711   if (this.disposed) return;
3712   
3713   if (this.helloResponsePending) {
3714     this.helloResponsePending = false;
3715     this.processIncomingData(data);
3716     
3717     // Don't immediately open a request in the complete handler due to Win IE bug
3718     var self = this;
3719     setTimeout(function () {
3720       self.openNewIncomingRequest();
3721     }, 0);
3722   } else {
3723     if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
3724       this.orbiter.getLog().error("[CONNECTION]" + toString() + " u66 (SERVER_HELLO) received, but client is not connected. Ignoring.");
3725     } else {
3726       this.orbiter.getLog().error("[CONNECTION]" + toString() + " Redundant u66 (SERVER_HELLO) received. Ignoring.");
3727     }
3728   }
3729 }
3730 
3731 /** @private */
3732 net.user1.orbiter.HTTPConnection.prototype.helloErrorListener = function () {
3733   if (this.disposed) return;
3734   // There's already a retry scheduled
3735   if (this.retryHelloTimeoutID != -1) return;  
3736   // The connection attempt has been aborted
3737   if (this.connectionState != net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
3738     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
3739                                 + " Connection is no longer in progress, so no retry scheduled."); 
3740     return;
3741   }
3742   
3743   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
3744                               + " Retrying in " +  this.retryDelay + "ms."); 
3745   
3746   // Retry
3747   var self = this;
3748   this.retryHelloTimeoutID = setTimeout(function () {
3749     self.retryHelloTimeoutID = -1;
3750     self.doRetryHello();
3751   }, this.retryDelay);
3752 }
3753 
3754 //==============================================================================    
3755 // OUTGOING REQUEST MANAGEMENT
3756 //==============================================================================
3757 
3758 /** @private */
3759 net.user1.orbiter.HTTPConnection.prototype.openNewOutgoingRequest = function (data) {
3760   this.dispatchSendData(data);
3761   this.outgoingResponsePending = true;
3762   this.doSendOutgoing(data);
3763   if (this.sendDelayTimerEnabled == true) {
3764     this.startSendDelayTimer();
3765   }
3766 }
3767 
3768 /** @private */
3769 net.user1.orbiter.HTTPConnection.prototype.outgoingCompleteListener = function () {
3770   if (this.disposed) return;
3771   
3772   this.outgoingResponsePending = false;
3773   
3774   if (!this.sendDelayTimerRunning && this.messageQueue.length > 0) {
3775     // Don't immediately open a request in the complete handler due to Win IE bug
3776     var self = this;
3777     setTimeout(function () {
3778       self.flushMessageQueue();
3779     }, 0);
3780   }
3781 }
3782 
3783 /** @private */
3784 net.user1.orbiter.HTTPConnection.prototype.outgoingErrorListener = function () {
3785   if (this.disposed) return;
3786   // There's already a retry scheduled
3787   if (this.retryOutgoingTimeoutID != -1) return;  
3788   // The connection has been closed
3789   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3790       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3791     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
3792                                 + " Connection is closed, so no retry scheduled."); 
3793     return;
3794   } 
3795   
3796   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
3797                               + " Retrying in " +  this.retryDelay + "ms.");  
3798       
3799   // Retry
3800   var self = this;
3801   this.retryOutgoingTimeoutID = setTimeout(function () {
3802     self.retryOutgoingTimeoutID = -1;
3803     if (self.disposed
3804         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3805         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3806       return;
3807     }
3808     self.doRetryOutgoing();
3809   }, this.retryDelay);
3810 }
3811 
3812 //==============================================================================    
3813 // INCOMING REQUEST MANAGEMENT
3814 //==============================================================================  
3815 
3816 /** @private */
3817 net.user1.orbiter.HTTPConnection.prototype.openNewIncomingRequest = function () {
3818   this.doSendIncoming();
3819 }
3820 
3821 /** @private */
3822 net.user1.orbiter.HTTPConnection.prototype.incomingCompleteListener = function (data) {
3823   if (this.disposed
3824       || this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3825       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3826     // Incoming request complete, but connection is closed. Ignore content.
3827     return;
3828   }
3829   
3830   // Don't immediately open a request in the complete handler due to Win IE bug
3831   var self = this;
3832   setTimeout(function () {
3833     self.processIncomingData(data);
3834     // A message listener might have closed this connection in response to an incoming
3835     // message. Do not open a new incoming request unless the connection is still open.
3836     if (self.disposed
3837         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3838         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3839       return;
3840     }
3841     self.openNewIncomingRequest();
3842   }, 0);
3843 }
3844 
3845 /** @private */
3846 net.user1.orbiter.HTTPConnection.prototype.incomingErrorListener = function () {
3847   if (this.disposed) return;
3848   // There's already a retry scheduled
3849   if (this.retryIncomingTimeoutID != -1) return;  
3850   // The connection has been closed
3851   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3852       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3853     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed."
3854                                 + " Connection is closed, so no retry scheduled."); 
3855     return;
3856   } 
3857 
3858   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed." 
3859                               + " Retrying in " +  this.retryDelay + "ms."); 
3860       
3861   // Retry
3862   var self = this;
3863   this.retryIncomingTimeoutID = setTimeout(function () {
3864     self.retryIncomingTimeoutID = -1;
3865     if (self.disposed
3866         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
3867         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
3868       return;
3869     }
3870     self.doRetryIncoming();
3871   }, this.retryDelay);
3872 }
3873     
3874 //==============================================================================    
3875 // PROCESS DATA FROM THE SERVER
3876 //==============================================================================
3877  
3878 /** @private */
3879 net.user1.orbiter.HTTPConnection.prototype.processIncomingData = function (data) {
3880   if (this.disposed) return;
3881   var listeners;
3882   
3883   this.dispatchReceiveData(data);
3884   
3885   var upcs = new Array();
3886   var upcEndTagIndex = data.indexOf("</U>");
3887   // Empty responses are valid.
3888   if (upcEndTagIndex == -1 && data.length > 0) {
3889     this.orbiter.getLog().error("Invalid message received. No UPC found: [" + data + "]");
3890     if (!this.isReady()) {
3891       // If invalid XML is received prior to achieving ready, then this
3892       // probably isn't a Union server, so disconnect.
3893       this.disconnect();
3894       return;
3895     }
3896   }
3897   
3898   while (upcEndTagIndex != -1) {
3899     upcs.push(data.substring(0, upcEndTagIndex+4));
3900     data = data.substring(upcEndTagIndex+4);
3901     upcEndTagIndex = data.indexOf("</U>");
3902   }
3903   for (var i = 0; i < upcs.length; i++) {
3904     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, upcs[i]));
3905   }
3906 }
3907 
3908 //==============================================================================    
3909 // SERVER AFFINITY
3910 //==============================================================================
3911 /** @private */ 
3912 net.user1.orbiter.HTTPConnection.prototype.applyAffinity = function (data) {
3913   net.user1.orbiter.Connection.prototype.applyAffinity.call(this);
3914   this.buildURL();
3915 };
3916 
3917 //==============================================================================    
3918 // TOSTRING
3919 //==============================================================================     
3920 net.user1.orbiter.HTTPConnection.prototype.toString = function () {
3921   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
3922           + ", host: " + (this.host == null ? "" : this.host) 
3923           + ", port: " + this.port 
3924           + ", send-delay: " + this.getSendDelay() + "]";
3925   return s;
3926 }
3927     
3928 // =============================================================================
3929 // DISPOSAL
3930 // =============================================================================
3931 /** @private */ 
3932 net.user1.orbiter.HTTPConnection.prototype.dispose = function () {
3933   this.doDispose();
3934   this.stopSendDelayTimer();
3935   net.user1.orbiter.Connection.prototype.dispose.call(this);
3936 }
3937 //==============================================================================    
3938 // CLASS DECLARATION
3939 //==============================================================================
3940 /**
3941  * @class
3942  *
3943  * The HTTPIFrameConnection class is used by Orbiter to communicate with
3944  * Union Server over HTTP in browsers that do not support CORS.
3945  * Rather than using CORS, HTTPIFrameConnection bypasses cross-origin restrictions
3946  * by proxying communications through a hidden HTML iframe.
3947  *
3948  * For a list of events dispatched by HTTPDirectConnection,
3949  * {@link net.user1.orbiter.Connection}.
3950  *
3951  * For more information on HTTP communication with Union Server, see
3952  * the HTTPDirectConnection class.
3953  *
3954  * @extends net.user1.orbiter.HTTPConnection
3955  *
3956  * @see net.user1.orbiter.HTTPDirectConnection
3957  * @see net.user1.orbiter.WebSocketConnection
3958  * @see net.user1.orbiter.SecureHTTPDirectConnection
3959  * @see net.user1.orbiter.SecureHTTPIFrameConnection
3960  * @see net.user1.orbiter.SecureWebSocketConnection
3961  */
3962 net.user1.orbiter.HTTPIFrameConnection = function (host, port, type) {
3963   // Invoke superclass constructor
3964   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
3965   this.postMessageInited = false;
3966   this.iFrameReady = false;
3967 };
3968 
3969 //==============================================================================
3970 // INHERITANCE
3971 //==============================================================================
3972 net.user1.utils.extend(net.user1.orbiter.HTTPIFrameConnection, net.user1.orbiter.HTTPConnection);
3973 
3974 //==============================================================================    
3975 // POSTMESSAGE INITIALIZATION
3976 //==============================================================================   
3977 /** @private */ 
3978 net.user1.orbiter.HTTPIFrameConnection.prototype.initPostMessage = function () {
3979   if (this.postMessageInited) {
3980     throw new Error("[HTTPIFrameConnection] Illegal duplicate initialization attempt.");
3981   }
3982   var self = this;
3983   var win = this.orbiter.window;
3984   var errorMsg = null;
3985   
3986   if (win == null) {
3987     errorMsg = "[HTTPIFrameConnection] Unable to create connection." 
3988                + " No window object found.";
3989   } else {
3990     if (typeof win.addEventListener != "undefined") {
3991       // ...the standard way 
3992       win.addEventListener("message", postMessageListener, false);
3993     } else if (typeof win.attachEvent != "undefined") {
3994       // ...the IE-specific way 
3995       win.attachEvent("onmessage", postMessageListener);
3996     } else {
3997       errorMsg = "[HTTPIFrameConnection] Unable to create connection."
3998                + " No event listener registration method found on window object.";
3999     }
4000   }
4001   
4002   if (errorMsg != null) {
4003     this.orbiter.getLog().error(errorMsg);
4004     throw new Error(errorMsg);
4005   }
4006 
4007   /** @private */
4008   function postMessageListener (e) {
4009     // The connection's host might have been reassigned (normally to an ip) due
4010     // to server affinity in a clustered deployment, so allow for posts from both the
4011     // requestedHost and the host.
4012     if (e.origin.indexOf("//" + self.host + (self.port == 80 ? "" : (":" + self.port))) == -1
4013         && e.origin.indexOf("//" + self.requestedHost + (self.port == 80 ? "" : (":" + self.port))) == -1) {
4014       self.orbiter.getLog().error("[CONNECTION] " + self.toString()
4015         + " Ignored message from unknown origin: " + e.origin);
4016       return;
4017     }
4018     
4019     self.processPostMessage(e.data);
4020   }
4021   
4022   this.postMessageInited = true;
4023 };
4024 
4025 //==============================================================================    
4026 // IFRAME MANAGEMENT
4027 //==============================================================================    
4028 /** @private */
4029 net.user1.orbiter.HTTPIFrameConnection.prototype.makeIFrame = function () {
4030   if (typeof this.orbiter.window.document == "undefined") {
4031     var errorMsg = "[HTTPIFrameConnection] Unable to create connection."
4032                  + " No document object found.";
4033     this.orbiter.getLog().error(errorMsg);
4034     throw new Error(errorMsg);
4035   }
4036   var doc = this.orbiter.window.document;
4037   
4038   this.iFrameReady = false;
4039   if (this.iframe != null) {
4040     this.postToIFrame("dispose");
4041     doc.body.removeChild(this.iframe);
4042   }
4043   this.iframe = doc.createElement('iframe');
4044   this.iframe.width = "0px";
4045   this.iframe.height = "0px";
4046   this.iframe.border = "0px";
4047   this.iframe.frameBorder = "0";
4048   this.iframe.style.visibility = "hidden";
4049   this.iframe.style.display = "none";
4050   this.iframe.src = this.url + "/orbiter";
4051   doc.body.appendChild(this.iframe);
4052 }
4053 
4054 /** @private */
4055 net.user1.orbiter.HTTPIFrameConnection.prototype.onIFrameReady = function () {
4056   this.beginReadyHandshake();
4057 }
4058 
4059 /** @private */
4060 net.user1.orbiter.HTTPIFrameConnection.prototype.postToIFrame = function (cmd, data) {
4061   if (this.iframe && this.iFrameReady) {
4062     data = data == undefined ? "" : data;
4063     // In order to post to the iframe, the targetOrigin must match the iframe's origin
4064     this.iframe.contentWindow.postMessage(cmd + "," + data, this.iframe.contentWindow.location.href);
4065   }  
4066 }
4067 
4068 /** @private */
4069 net.user1.orbiter.HTTPIFrameConnection.prototype.processPostMessage = function (postedData) {
4070   var delimiterIndex = postedData.indexOf(",");
4071   var cmd  = postedData.substring(0, delimiterIndex);
4072   var data = postedData.substring(delimiterIndex+1);
4073   
4074   switch (cmd) {
4075     case"ready":
4076       this.iFrameReady = true;
4077       this.onIFrameReady();
4078       break;
4079       
4080     case "hellocomplete":
4081       this.helloCompleteListener(data);
4082       break;
4083     
4084     case "helloerror":
4085       this.helloErrorListener();
4086       break;
4087     
4088     case "outgoingcomplete":
4089       this.outgoingCompleteListener();
4090       break;
4091     
4092     case "outgoingerror":
4093       this.outgoingErrorListener();
4094       break;
4095     
4096     case "incomingcomplete":
4097       this.incomingCompleteListener(data);
4098       break;
4099     
4100     case "incomingerror":
4101       this.incomingErrorListener();
4102       break;
4103   }
4104 }
4105 
4106 //==============================================================================    
4107 // CONNECTION AND DISCONNECTION
4108 //==============================================================================    
4109 net.user1.orbiter.HTTPIFrameConnection.prototype.connect = function () {
4110   if (!this.postMessageInited) {
4111     this.initPostMessage();
4112   }
4113   
4114   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
4115   this.makeIFrame();
4116 };
4117 
4118 /** @private */
4119 net.user1.orbiter.HTTPIFrameConnection.prototype.doRequestDeactivation = function() {
4120   this.postToIFrame("deactivate");
4121 };
4122 
4123 //==============================================================================    
4124 // UPC LISTENERS (IFRAME-SPECIFIC IMPLEMENTATION)
4125 //==============================================================================
4126 
4127 /** @private */
4128 net.user1.orbiter.HTTPIFrameConnection.prototype.u66 = function (serverVersion, 
4129                                                            sessionID, 
4130                                                            upcVersion, 
4131                                                            protocolCompatible) {
4132   net.user1.orbiter.Connection.prototype.u66.call(this,
4133                                                   serverVersion,
4134                                                   sessionID,
4135                                                   upcVersion,
4136                                                   protocolCompatible);
4137   if (this.iframe != null) {
4138     this.postToIFrame("sessionid", sessionID);
4139   }
4140 }
4141 
4142 //==============================================================================    
4143 // HELLO REQUEST MANAGEMENT
4144 //==============================================================================  
4145 
4146 /** @private */
4147 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendHello = function (helloString) {
4148   this.postToIFrame("sendhello", helloString);
4149 };
4150 
4151 /** @private */
4152 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryHello = function () {
4153   this.postToIFrame("retryhello");
4154 }
4155 
4156 //==============================================================================    
4157 // OUTGOING REQUEST MANAGEMENT
4158 //==============================================================================
4159 
4160 /** @private */
4161 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendOutgoing = function (data) {
4162   this.postToIFrame("sendoutgoing", data);
4163 };
4164 
4165 /** @private */
4166 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryOutgoing = function () {
4167   this.postToIFrame("retryoutgoing");
4168 };
4169 
4170 //==============================================================================    
4171 // INCOMING REQUEST MANAGEMENT
4172 //==============================================================================  
4173 
4174 /** @private */
4175 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendIncoming = function () {
4176   this.postToIFrame("sendincoming");
4177 };
4178 
4179 /** @private */
4180 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryIncoming = function () {
4181   this.postToIFrame("retryincoming");
4182 };
4183     
4184 //==============================================================================    
4185 // TOSTRING
4186 //==============================================================================     
4187 net.user1.orbiter.HTTPIFrameConnection.prototype.toString = function () {
4188   var s = "[HTTPIFrameConnection, requested host: " + this.requestedHost 
4189           + ", host: " + (this.host == null ? "" : this.host) 
4190           + ", port: " + this.port 
4191           + ", send-delay: " + this.getSendDelay() + "]";
4192   return s;
4193 };
4194     
4195 //==============================================================================
4196 // DISPOSAL
4197 //==============================================================================
4198 /** @private */ 
4199 net.user1.orbiter.HTTPIFrameConnection.prototype.doDispose = function () {
4200   this.postToIFrame("dispose");
4201 };
4202 //==============================================================================    
4203 // CLASS DECLARATION
4204 //==============================================================================
4205 /**
4206  * @class
4207  * <p>
4208  * The HTTPDirectConnection class is used by Orbiter to communicate with
4209  * Union Server over HTTP; it uses CORS to bypass cross-origin restrictions
4210  * when Union Server is hosted on a domain that does not match the domain at
4211  * which the Orbiter client is hosted. Normally, developers need not use the
4212  * HTTPDirectConnection class directly, and should instead make connections
4213  * via the Orbiter class's connect() method. However, the
4214  * HTTPDirectConnection class is required for fine-grained connection configuration,
4215  * such as defining failover connections for multiple Union Servers
4216  * running at different host addresses.
4217  * </p>
4218  *
4219  * <p>
4220  * By default, Orbiter uses the WebSocketConnection class, not the
4221  * HTTPDirectConnection class, to communicate with Union Server. The
4222  * HTTPDirectConnection class is used as a backup connection
4223  * when the primary WebSocketConnection connection is blocked by a firewall.
4224  * However, on a very heavily loaded server with limited persistent socket
4225  * connections available, communicating with Union Server over HTTP--which uses
4226  * short-lived socket connections--can improve performance at the
4227  * expense of realtime responsiveness. To reduce server load when communicating
4228  * over HTTP, use HTTPDirectConnection's setSendDelay() method to decrease the
4229  * frequency of Orbiter's requests for updates from Union Server. Developers
4230  * that wish to use HTTP connections as the primary form of communication with
4231  * Union Server must do so by manually configuring connections via the
4232  * ConnectionManager class's addConnection() method.</p>
4233  *
4234  * <p>
4235  * In environments that do not support CORS (such as IE8 on Windows), Orbiter
4236  * conducts HTTP communications using HTTPIFrameConnection instead of HTTPDirectConnection.
4237  * </p>
4238  *
4239  * <p>
4240  * For secure HTTP and WebSocket communications, see SecureHTTPDirectConnection,
4241  * SecureHTTPIFrameConnection, and SecureWebSocketConnection.
4242  * </p>
4243  *
4244  *
4245  * For a list of events dispatched by HTTPDirectConnection,
4246  * {@link net.user1.orbiter.Connection}.
4247  *
4248  * @extends net.user1.orbiter.HTTPConnection
4249  *
4250  * @see net.user1.orbiter.Orbiter#connect
4251  * @see net.user1.orbiter.Orbiter#secureConnect
4252  *
4253  * @see net.user1.orbiter.SecureHTTPDirectConnection
4254  * @see net.user1.orbiter.SecureHTTPIFrameConnection
4255  * @see net.user1.orbiter.SecureWebSocketConnection
4256  */
4257 net.user1.orbiter.HTTPDirectConnection = function (host, port, type) {
4258   // Invoke superclass constructor
4259   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
4260   
4261   this.outgoingRequestID = 0;
4262   this.incomingRequestID = 0;
4263   
4264   this.lastOutgoingPostData = null;
4265   this.lastIncomingPostData = null;
4266   this.lastHelloPostData    = null;
4267   
4268   this.pendingRequests = [];
4269 };
4270 
4271 //==============================================================================
4272 // INHERITANCE
4273 //==============================================================================
4274 net.user1.utils.extend(net.user1.orbiter.HTTPDirectConnection, net.user1.orbiter.HTTPConnection);
4275 
4276 
4277 //==============================================================================    
4278 // CONNECTION AND DISCONNECTION
4279 //==============================================================================    
4280 net.user1.orbiter.HTTPDirectConnection.prototype.connect = function () {
4281   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
4282   this.beginReadyHandshake();
4283 };
4284 
4285 //==============================================================================    
4286 // HELLO REQUEST MANAGEMENT
4287 //==============================================================================  
4288 
4289 /** @private Abstract method implementation */
4290 net.user1.orbiter.HTTPDirectConnection.prototype.doSendHello = function (helloString) {
4291   this.newHelloRequest(helloString);
4292 };
4293 
4294 /** @private Abstract method implementation */
4295 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryHello = function () {
4296   this.retryHello();
4297 }
4298 
4299 /** @private */
4300 net.user1.orbiter.HTTPDirectConnection.prototype.newHelloRequest = function (data) {
4301   this.lastHelloPostData = this.createHelloPostData(encodeURIComponent(data));
4302   this.transmitRequest(this.lastHelloPostData, 
4303                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
4304                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
4305 }
4306 
4307 /** @private */
4308 net.user1.orbiter.HTTPDirectConnection.prototype.createHelloPostData = function (data) {
4309   return "mode=d" + "&data=" + data;
4310 }
4311 
4312 /** @private */
4313 net.user1.orbiter.HTTPDirectConnection.prototype.retryHello = function () {
4314   this.transmitRequest(this.lastHelloPostData, 
4315                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
4316                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
4317 }
4318 
4319 /** @private */
4320 net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener = function (xhr, connection) {
4321   if (xhr.readyState == 4) {
4322     connection.removePendingRequest(xhr);
4323     if (xhr.status >= 200 && xhr.status <= 299) {
4324       connection.helloCompleteListener(xhr.responseText);
4325     } else {
4326       connection.helloErrorListener();
4327     }
4328   }
4329 }
4330 
4331 /** @private */
4332 net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener = function (xhr, connection) {
4333   connection.removePendingRequest(xhr);
4334   connection.helloErrorListener();
4335 }
4336 
4337 //==============================================================================    
4338 // OUTGOING REQUEST MANAGEMENT
4339 //==============================================================================
4340 
4341 /** @private Abstract method implementation */
4342 net.user1.orbiter.HTTPDirectConnection.prototype.doSendOutgoing = function (data) {
4343   this.newOutgoingRequest(data);
4344 };
4345 
4346 /** @private Abstract method implementation */
4347 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryOutgoing = function () {
4348   this.retryOutgoing();
4349 };
4350 
4351 /** @private */
4352 net.user1.orbiter.HTTPDirectConnection.prototype.newOutgoingRequest = function (data) {
4353   this.lastOutgoingPostData = this.createOutgoingPostData(encodeURIComponent(data));
4354   this.transmitRequest(this.lastOutgoingPostData, 
4355                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
4356                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
4357 }
4358 
4359 /** @private */
4360 net.user1.orbiter.HTTPDirectConnection.prototype.createOutgoingPostData = function (data) {
4361   this.outgoingRequestID++;
4362   return "rid=" + this.outgoingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=s" + "&data=" + data;
4363 }
4364 
4365 /** @private */
4366 net.user1.orbiter.HTTPDirectConnection.prototype.retryOutgoing = function () {
4367   this.transmitRequest(this.lastOutgoingPostData, 
4368                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
4369                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
4370 }
4371 
4372 /** @private */
4373 net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener = function (xhr, connection) {
4374   if (xhr.readyState == 4) {
4375     connection.removePendingRequest(xhr);
4376     if (xhr.status >= 200 && xhr.status <= 299) {
4377       connection.outgoingCompleteListener();
4378     } else {
4379       connection.outgoingErrorListener();
4380     }
4381   }
4382 }
4383 
4384 /** @private */
4385 net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener = function (xhr, connection) {
4386   connection.removePendingRequest(xhr);
4387   connection.outgoingErrorListener();
4388 }
4389 
4390 //==============================================================================    
4391 // INCOMING REQUEST MANAGEMENT
4392 //==============================================================================  
4393 
4394 /** @private Abstract method implementation */
4395 net.user1.orbiter.HTTPDirectConnection.prototype.doSendIncoming = function () {
4396   this.newIncomingRequest();
4397 };
4398 
4399 /** @private Abstract method implementation */
4400 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryIncoming = function () {
4401   this.retryIncoming();
4402 };
4403 
4404 /** @private */ 
4405 net.user1.orbiter.HTTPDirectConnection.prototype.newIncomingRequest = function () {
4406   this.lastIncomingPostData = this.createIncomingPostData();
4407   this.transmitRequest(this.lastIncomingPostData,
4408                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
4409                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
4410 }
4411 
4412 /** @private */ 
4413 net.user1.orbiter.HTTPDirectConnection.prototype.createIncomingPostData = function () {
4414   this.incomingRequestID++;
4415   return "rid=" + this.incomingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=c";
4416 }
4417 
4418 /** @private */ 
4419 net.user1.orbiter.HTTPDirectConnection.prototype.retryIncoming = function () {
4420   this.transmitRequest(this.lastIncomingPostData,
4421                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
4422                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
4423 }
4424 
4425 /** @private */ 
4426 net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener = function (xhr, connection) {
4427   if (xhr.readyState == 4) {
4428     connection.removePendingRequest(xhr);
4429     if (xhr.status >= 200 && xhr.status <= 299) {
4430       connection.incomingCompleteListener(xhr.responseText);
4431     } else {
4432       connection.incomingErrorListener();
4433     }
4434   }
4435 }
4436 
4437 /** @private */ 
4438 net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener = function (xhr, connection) {
4439   connection.removePendingRequest(xhr);
4440   connection.incomingErrorListener();
4441 }
4442 
4443 //==============================================================================
4444 // XHR MANAGEMENT
4445 //==============================================================================
4446 /** @private */
4447 net.user1.orbiter.HTTPDirectConnection.prototype.transmitRequest = function (data, 
4448                                                       readystatechangeListener, 
4449                                                       errorListener) {
4450   var self = this;
4451   var request;
4452   
4453   if (typeof XDomainRequest != "undefined") {
4454     // IE
4455     request = new XDomainRequest();
4456     request.onload = function () {
4457       request.readyState = 4;  // Emulate standards-based API
4458       request.status = 200;
4459       readystatechangeListener(this, self)
4460     };
4461     request.onerror = function () {
4462       errorListener(this, self);
4463     };
4464     request.ontimeout = function () {
4465       errorListener(this, self);
4466     };
4467     request.onprogress = function () {}; // Do nothing (required)
4468   } else {
4469     // All other standards-based browsers
4470     var request = new XMLHttpRequest();
4471     this.pendingRequests.push(request);
4472     request.onreadystatechange = function () {
4473       readystatechangeListener(this, self);
4474     };
4475     request.onerror = function () {
4476       errorListener(this, self);
4477     };
4478   }
4479   // Call open before setting header
4480   request.open("POST", this.url);
4481   // Standards-based browsers (IE doesn't allow the setting of headers)
4482   if (typeof request.setRequestHeader != "undefined") {
4483     request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
4484   }
4485   request.send(data);
4486 }
4487 
4488 /** @private */
4489 net.user1.orbiter.HTTPDirectConnection.prototype.removePendingRequest = function (request) {
4490   for (var i = this.pendingRequests.length; --i >= 0; ) {
4491     if (this.pendingRequests[i] === request) {
4492       this.pendingRequests.splice(i, 1);
4493     }
4494   }
4495 }
4496 
4497 /** @private Abstract method implementation */
4498 net.user1.orbiter.HTTPDirectConnection.prototype.doRequestDeactivation = function () {
4499   for (var i = this.pendingRequests.length; --i >= 0;) {
4500     try {
4501       this.pendingRequests[i].abort();
4502     } catch (e) {
4503       // Do nothing
4504     }
4505   }
4506   this.pendingRequests = [];
4507 }
4508     
4509 //==============================================================================    
4510 // TOSTRING
4511 //==============================================================================     
4512 net.user1.orbiter.HTTPDirectConnection.prototype.toString = function () {
4513   var s = "[HTTPDirectConnection, requested host: " + this.requestedHost 
4514           + ", host: " + (this.host == null ? "" : this.host) 
4515           + ", port: " + this.port 
4516           + ", send-delay: " + this.getSendDelay() + "]";
4517   return s;
4518 };
4519     
4520 //==============================================================================
4521 // DISPOSAL
4522 //==============================================================================
4523 /** @private */ 
4524 net.user1.orbiter.HTTPDirectConnection.prototype.doDispose = function () {
4525   this.deactivateHTTPRequests();
4526 };
4527 //==============================================================================    
4528 // CLASS DECLARATION
4529 //==============================================================================
4530 /** @class
4531  * <p>
4532  * The SecureHTTPDirectConnection class is identical to HTTPDirectConnection
4533  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
4534  * SSL connection) rather than plain HTTP.</p>
4535  *
4536  * For a list of events dispatched by SecureHTTPDirectConnection, see
4537  * {@link net.user1.orbiter.Connection}.
4538  *
4539  * @extends net.user1.orbiter.HTTPDirectConnection
4540  *
4541  * @see net.user1.orbiter.HTTPDirectConnection
4542  */
4543 net.user1.orbiter.SecureHTTPDirectConnection = function (host, port) {
4544   // Invoke superclass constructor
4545   net.user1.orbiter.HTTPDirectConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
4546 };
4547 
4548 //==============================================================================
4549 // INHERITANCE
4550 //==============================================================================
4551 net.user1.utils.extend(net.user1.orbiter.SecureHTTPDirectConnection, net.user1.orbiter.HTTPDirectConnection);
4552 
4553 /** @private */
4554 net.user1.orbiter.SecureHTTPDirectConnection.prototype.buildURL = function () {
4555   this.url = "https://" + this.host + ":" + this.port;
4556 };
4557 
4558 //==============================================================================
4559 // TOSTRING
4560 //==============================================================================
4561 net.user1.orbiter.SecureHTTPDirectConnection.prototype.toString = function () {
4562   var s = "[SecureHTTPDirectConnection, requested host: " + this.requestedHost
4563           + ", host: " + (this.host == null ? "" : this.host)
4564           + ", port: " + this.port
4565           + ", send-delay: " + this.getSendDelay() + "]";
4566   return s;
4567 };
4568 //==============================================================================    
4569 // CLASS DECLARATION
4570 //==============================================================================
4571 /**
4572  * @class
4573  *
4574  * <p>
4575  * The SecureHTTPIFrameConnection class is identical to HTTPIFrameConnection
4576  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
4577  * SSL connection) rather than plain HTTP.</p>
4578  *
4579  * For a list of events dispatched by SecureHTTPIFrameConnection,
4580  * see {@link net.user1.orbiter.Connection}.
4581  *
4582  * @extends net.user1.orbiter.HTTPIFrameConnection
4583  *
4584  * @see net.user1.orbiter.HTTPIFrameConnection
4585  */
4586 net.user1.orbiter.SecureHTTPIFrameConnection = function (host, port) {
4587   // Invoke superclass constructor
4588   net.user1.orbiter.HTTPIFrameConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
4589 };
4590 
4591 //==============================================================================
4592 // INHERITANCE
4593 //==============================================================================
4594 net.user1.utils.extend(net.user1.orbiter.SecureHTTPIFrameConnection, net.user1.orbiter.HTTPIFrameConnection);
4595 
4596 /** @private */
4597 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.buildURL = function () {
4598   this.url = "https://" + this.host + ":" + this.port;
4599 };
4600 
4601 //==============================================================================
4602 // TOSTRING
4603 //==============================================================================
4604 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.toString = function () {
4605   var s = "[SecureHTTPIFrameConnection, requested host: " + this.requestedHost
4606           + ", host: " + (this.host == null ? "" : this.host)
4607           + ", port: " + this.port
4608           + ", send-delay: " + this.getSendDelay() + "]";
4609   return s;
4610 };
4611 //==============================================================================
4612 // CLASS DECLARATION
4613 //==============================================================================
4614 /** @private */
4615 net.user1.orbiter.MessageListener = function (listener,
4616                                               forRoomIDs,
4617                                               thisArg) {
4618   this.listener   = listener;
4619   this.forRoomIDs = forRoomIDs;
4620   this.thisArg    = thisArg;
4621 };
4622 
4623 //==============================================================================
4624 // INSTANCE METHODS
4625 //==============================================================================
4626 /** @private */
4627 net.user1.orbiter.MessageListener.prototype.getListenerFunction = function () {
4628   return this.listener;
4629 };
4630     
4631 /** @private */
4632 net.user1.orbiter.MessageListener.prototype.getForRoomIDs = function () {
4633   return this.forRoomIDs;
4634 };
4635     
4636 /** @private */
4637 net.user1.orbiter.MessageListener.prototype.getThisArg = function () {
4638   return this.thisArg;
4639 };
4640 
4641 /** @private */
4642 net.user1.orbiter.MessageListener.prototype.toString = function () {
4643   return "[object MessageListener]";
4644 };
4645 //==============================================================================
4646 // CLASS DECLARATION
4647 //==============================================================================
4648 /** @class */
4649 net.user1.orbiter.MessageManager = function (log, connectionManager) {
4650   this.log = log;
4651   this.messageListeners = new Object();
4652   this.removeListenersOnDisconnect = true;
4653   this.numMessagesSent = 0;
4654   this.numMessagesReceived = 0;
4655   this.currentConnection = null;
4656   this.connectionManager = connectionManager;
4657   this.connectionManager.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
4658                                           this.selectConnectionListener, this);
4659 };
4660 
4661 //==============================================================================
4662 // INSTANCE METHODS
4663 //==============================================================================
4664 net.user1.orbiter.MessageManager.prototype.getNumMessagesReceived = function () {
4665   return this.numMessagesReceived;
4666 }
4667   
4668 net.user1.orbiter.MessageManager.prototype.getNumMessagesSent = function () {
4669   return this.numMessagesSent;
4670 }
4671   
4672 net.user1.orbiter.MessageManager.prototype.getTotalMessages = function () {
4673   return this.numMessagesSent + this.numMessagesReceived;
4674 }
4675   
4676 /** @private */
4677 net.user1.orbiter.MessageManager.prototype.selectConnectionListener = function (e) {
4678   if (this.currentConnection != null) {
4679     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
4680                                           this.upcReceivedListener, this);
4681     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
4682                                           this.disconnectListener, this);
4683     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
4684                                           this.connectFailureListener, this);
4685   }
4686 
4687   this.currentConnection = e.getConnection(); 
4688 
4689   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
4690                                         this.upcReceivedListener, this);
4691   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
4692                                         this.disconnectListener, this);
4693   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
4694                                         this.connectFailureListener, this);
4695 }
4696   
4697 /** @private */
4698 net.user1.orbiter.MessageManager.prototype.disconnectListener = function (e) {
4699   this.cleanupAfterClosedConnection(e.target);
4700 }
4701     
4702 /** @private */
4703 net.user1.orbiter.MessageManager.prototype.connectFailureListener = function (e) {
4704   this.cleanupAfterClosedConnection(e.target);
4705 }
4706     
4707 /** @private */
4708 net.user1.orbiter.MessageManager.prototype.cleanupAfterClosedConnection = function (connection) {
4709   var listenerList;
4710   if (this.removeListenersOnDisconnect) {
4711     this.log.info("[MESSAGE_MANAGER] Removing registered message listeners.");
4712     for (var message in this.messageListeners) {
4713       listenerList = this.messageListeners[message];
4714       for (var p in listenerList) {
4715         this.removeMessageListener(message, listenerList[p].getListenerFunction());
4716       } 
4717     }
4718   } else {
4719     this.log.warn("[MESSAGE_MANAGER] Leaving message listeners registered. \n"
4720       + "Be sure to remove any unwanted message listeners manually.");
4721   }
4722   
4723   this.numMessagesReceived = 0;
4724   this.numMessagesSent = 0;
4725 }
4726   
4727 net.user1.orbiter.MessageManager.prototype.sendUPC = function (message) {
4728   // Quit if the connection isn't ready...
4729   if (!this.connectionManager.isReady()) {
4730     this.log.warn("[MESSAGE_MANAGER] Connection not ready. UPC not sent. Message: " 
4731     + message);
4732     return;
4733   }
4734 
4735   // Build the UPC to send.
4736   var theUPC = "<U><M>" + message + "</M>";
4737   var a;
4738   
4739   if (arguments.length > 1) {
4740     theUPC += "<L>";
4741     for (var i = 1; i < arguments.length; i++) {
4742       a = arguments[i];
4743       a = a == undefined ? "" : a.toString();
4744       // Wrap any non-filter argument that contains a start tag ("<") in CDATA
4745       if (a.indexOf("<") != -1) {
4746         if (a.indexOf('<f t=') != 0) {
4747           a = "<![CDATA[" + a + "]]>";
4748         }
4749       }
4750       theUPC += "<A>" + a + "</A>";
4751     }
4752     theUPC += "</L>";
4753   }
4754   theUPC += "</U>";
4755 
4756   // Count the message
4757   this.numMessagesSent++;
4758   
4759   // Send the UPC to the server
4760   this.log.debug("[MESSAGE_MANAGER] UPC sent: " + theUPC);
4761   this.connectionManager.getActiveConnection().send(theUPC);
4762 };
4763 
4764 /** @private */
4765 net.user1.orbiter.MessageManager.prototype.sendUPCObject = function (upc) {
4766   var args = upc.args.slice();
4767   args.unshift(upc.method);
4768   this.sendUPC.apply(this, args);
4769 };
4770 
4771 /** @private */
4772 net.user1.orbiter.MessageManager.prototype.upcReceivedListener = function (e) {
4773   this.numMessagesReceived++;
4774   
4775   var upc = e.getUPC();
4776   this.log.debug("[MESSAGE_MANAGER] UPC received: " + upc );
4777   
4778   var method;
4779   var upcArgs = new Array();
4780   
4781   var closeMTagIndex = upc.indexOf("</M>");
4782   method = upc.substring(6, closeMTagIndex);
4783   
4784   var searchBeginIndex = upc.indexOf("<A>", closeMTagIndex);
4785   var closeATagIndex;
4786   var arg;
4787   while (searchBeginIndex != -1) {
4788     closeATagIndex = upc.indexOf("</A>", searchBeginIndex);
4789     arg = upc.substring(searchBeginIndex+3, closeATagIndex);
4790     if (arg.indexOf("<![CDATA[") == 0) {
4791       arg = arg.substr(9, arg.length-12);
4792     }
4793     upcArgs.push(arg);
4794     searchBeginIndex = upc.indexOf("<A>", closeATagIndex);
4795   }     
4796   
4797   this.notifyMessageListeners(method, upcArgs);
4798 };
4799 
4800 net.user1.orbiter.MessageManager.prototype.addMessageListener = function (message, 
4801                                                                           listener,
4802                                                                           thisArg,
4803                                                                           forRoomIDs) {
4804   if (forRoomIDs != null) {
4805     var typeString = Object.prototype.toString.call(forRoomIDs);
4806     if (typeString != "[object Array]") {
4807       throw new Error("[MESSAGE_MANAGER] Illegal argument type " + typeString
4808                       + " supplied for addMessageListener()'s forRoomIDs"
4809                       + " parameter. Value must be an Array.");
4810     }
4811   }
4812   
4813   // Each message gets a list of MessageListener objects. 
4814   // If this message has no such list, make one.
4815   if (this.messageListeners[message] === undefined) {
4816     this.messageListeners[message] = new Array();
4817   } 
4818   var listenerArray = this.messageListeners[message];
4819   
4820   // Quit if the listener is already registered
4821   if (this.hasMessageListener(message, listener)) {
4822     return false;
4823   }
4824   
4825   // Add the listener
4826   var newListener = new net.user1.orbiter.MessageListener(listener,
4827                                             forRoomIDs === undefined ? null : forRoomIDs,
4828                                             thisArg);
4829   listenerArray.push(newListener);
4830   return true;      
4831 };
4832 
4833 net.user1.orbiter.MessageManager.prototype.removeMessageListener = function (message,
4834                                                            listener) {
4835   // Quit if the message has no listeners
4836   var listenerArray = this.messageListeners[message];
4837   if (listenerArray == null) {
4838     return false;
4839   } 
4840   
4841   // Remove the listener
4842   var foundListener;
4843   for (var i = 0; i < listenerArray.length; i++) {
4844     if (listenerArray[i].getListenerFunction() == listener) {
4845       foundListener = true;
4846       listenerArray.splice(i, 1);
4847       break;
4848     }
4849   }
4850   
4851   // Delete the listeners array if it's now empty
4852   if (listenerArray.length == 0) {
4853     delete this.messageListeners[message];
4854   }
4855   
4856   return foundListener;      
4857 };
4858     
4859 net.user1.orbiter.MessageManager.prototype.hasMessageListener = function (message, 
4860                                                         listener) {
4861   // Quit if the message has no listeners
4862   var listenerArray = this.messageListeners[message];
4863   if (listenerArray == null) {
4864     return false;
4865   } 
4866       
4867    // Check for the listener
4868   for (var i = 0; i < listenerArray.length; i++) {
4869     if (listenerArray[i].getListenerFunction() 
4870       == listener) {
4871       return true;
4872     }
4873   }
4874   return false;
4875 };
4876     
4877 net.user1.orbiter.MessageManager.prototype.getMessageListeners = function (message) {
4878   return this.messageListeners[message] != undefined ? this.messageListeners[message] : [];
4879 };
4880 
4881 /** @private */
4882 net.user1.orbiter.MessageManager.prototype.notifyMessageListeners = function (message, args) {
4883   // Retrieve the list of listeners for this message.
4884   var listeners = this.messageListeners[message];
4885   // If there are no listeners registered, then quit
4886   if (listeners === undefined) {
4887     // Log a warning if it's not a UPC
4888     if (!(message.charAt(0) == "u" && parseInt(message.substring(1)) > 1)) {
4889       this.log.warn("Message delivery failed. No listeners found. Message: " + 
4890                message + ". Arguments: " + args.join());
4891     }
4892     return;
4893   } else {
4894     listeners = listeners.slice(0);    
4895   }
4896   var numListeners = listeners.length; 
4897   for (var i = 0; i < numListeners; i++) {
4898     listeners[i].getListenerFunction().apply(listeners[i].getThisArg(), args);
4899   }
4900 };
4901 
4902 net.user1.orbiter.MessageManager.prototype.dispose = function () {
4903   this.log.info("[MESSAGE_MANAGER] Disposing resources.");
4904   this.log = null;
4905   this.orbiter = null;
4906   this.messageListeners = null;
4907   this.numMessagesSent = 0;
4908   this.numMessagesReceived = 0;
4909   this.currentConnection = null;
4910 }
4911   
4912 net.user1.orbiter.MessageManager.prototype.toString = function () {
4913   return "[object MessageManager]";
4914 };
4915 
4916 //==============================================================================
4917 // UPC CONSTANTS
4918 //==============================================================================
4919 /** @class */
4920 net.user1.orbiter.UPC = new Object();
4921 
4922 // CLIENT TO SERVER
4923 /** @constant */
4924 net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS = "u1";            
4925 /** @constant */
4926 net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS = "u2";            
4927 /** @constant */
4928 net.user1.orbiter.UPC.SET_CLIENT_ATTR = "u3";       
4929 /** @constant */
4930 net.user1.orbiter.UPC.JOIN_ROOM = "u4";             
4931 /** @constant */
4932 net.user1.orbiter.UPC.SET_ROOM_ATTR = "u5";         
4933 /** @constant */
4934 net.user1.orbiter.UPC.LEAVE_ROOM = "u10";           
4935 /** @constant */
4936 net.user1.orbiter.UPC.CREATE_ACCOUNT = "u11"; 
4937 /** @constant */
4938 net.user1.orbiter.UPC.REMOVE_ACCOUNT = "u12";
4939 /** @constant */
4940 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD = "u13";
4941 /** @constant */
4942 net.user1.orbiter.UPC.LOGIN = "u14";            
4943 /** @constant */
4944 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT = "u18";                
4945 /** @constant */
4946 net.user1.orbiter.UPC.SYNC_TIME = "u19";
4947 /** @constant */
4948 net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT = "u21";
4949 /** @constant */
4950 net.user1.orbiter.UPC.CREATE_ROOM = "u24";                       
4951 /** @constant */
4952 net.user1.orbiter.UPC.REMOVE_ROOM = "u25";                       
4953 /** @constant */
4954 net.user1.orbiter.UPC.WATCH_FOR_ROOMS = "u26";            
4955 /** @constant */
4956 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS = "u27"; 
4957 /** @constant */
4958 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT = "u55";
4959 /** @constant */
4960 net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER = "u57"; 
4961 /** @constant */
4962 net.user1.orbiter.UPC.OBSERVE_ROOM = "u58"; 
4963 /** @constant */
4964 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM = "u61"; 
4965 /** @constant */
4966 net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS = "u64"; 
4967 /** @constant */
4968 net.user1.orbiter.UPC.CLIENT_HELLO = "u65"; 
4969 /** @constant */
4970 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR = "u67"; 
4971 /** @constant */
4972 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR = "u69"; 
4973 /** @constant */
4974 net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE = "u70"; 
4975 /** @constant */
4976 net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE = "u71"; 
4977 /** @constant */
4978 net.user1.orbiter.UPC.TERMINATE_SESSION = "u83";
4979 /** @constant */
4980 net.user1.orbiter.UPC.LOGOFF = "u86";  
4981 /** @constant */
4982 net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT = "u91";  
4983 /** @constant */
4984 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS = "u92";  
4985 /** @constant */
4986 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS = "u93";  
4987 /** @constant */
4988 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT = "u94";  
4989 /** @constant */
4990 net.user1.orbiter.UPC.OBSERVE_CLIENT = "u95";  
4991 /** @constant */
4992 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT = "u96";  
4993 /** @constant */
4994 net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT = "u97";  
4995 /** @constant */
4996 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS = "u98";  
4997 /** @constant */
4998 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS = "u99";  
4999 /** @constant */
5000 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT = "u100";  
5001 /** @constant */
5002 net.user1.orbiter.UPC.OBSERVE_ACCOUNT = "u121";  
5003 /** @constant */
5004 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT = "u122"; 
5005 /** @constant */
5006 net.user1.orbiter.UPC.ADD_ROLE = "u133";  
5007 /** @constant */
5008 net.user1.orbiter.UPC.REMOVE_ROLE = "u135";  
5009 /** @constant */
5010 net.user1.orbiter.UPC.KICK_CLIENT = "u149";  
5011 /** @constant */
5012 net.user1.orbiter.UPC.BAN = "u137";  
5013 /** @constant */
5014 net.user1.orbiter.UPC.UNBAN = "u139";  
5015 /** @constant */
5016 net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT = "u141";  
5017 /** @constant */
5018 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES = "u143";  
5019 /** @constant */
5020 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES = "u145";  
5021 /** @constant */
5022 net.user1.orbiter.UPC.GET_NODELIST_SNAPSHOT = "u165";  
5023 /** @constant */
5024 net.user1.orbiter.UPC.GET_GATEWAYS_SNAPSHOT = "u167";
5025 
5026 // SERVER TO CLIENT
5027 /** @constant */
5028 net.user1.orbiter.UPC.JOINED_ROOM = "u6";
5029 /** @constant */
5030 net.user1.orbiter.UPC.RECEIVE_MESSAGE = "u7";
5031 /** @constant */
5032 net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE = "u8";
5033 /** @constant */
5034 net.user1.orbiter.UPC.ROOM_ATTR_UPDATE = "u9";
5035 /** @constant */
5036 net.user1.orbiter.UPC.CLIENT_METADATA = "u29";
5037 /** @constant */
5038 net.user1.orbiter.UPC.CREATE_ROOM_RESULT = "u32";
5039 /** @constant */
5040 net.user1.orbiter.UPC.REMOVE_ROOM_RESULT = "u33";
5041 /** @constant */
5042 net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT = "u34";
5043 /** @constant */
5044 net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM = "u36";
5045 /** @constant */
5046 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM = "u37";
5047 /** @constant */
5048 net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT = "u38";
5049 /** @constant */
5050 net.user1.orbiter.UPC.ROOM_ADDED = "u39";
5051 /** @constant */
5052 net.user1.orbiter.UPC.ROOM_REMOVED = "u40";
5053 /** @constant */
5054 net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT = "u42";
5055 /** @constant */
5056 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT = "u43";
5057 /** @constant */
5058 net.user1.orbiter.UPC.LEFT_ROOM = "u44";
5059 /** @constant */
5060 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT = "u46";
5061 /** @constant */
5062 net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT = "u47";
5063 /** @constant */
5064 net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT = "u48";
5065 /** @constant */
5066 net.user1.orbiter.UPC.LOGIN_RESULT = "u49";
5067 /** @constant */
5068 net.user1.orbiter.UPC.SERVER_TIME_UPDATE = "u50";
5069 /** @constant */
5070 net.user1.orbiter.UPC.ROOM_SNAPSHOT = "u54";
5071 /** @constant */
5072 net.user1.orbiter.UPC.OBSERVED_ROOM = "u59";
5073 /** @constant */
5074 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT = "u60";
5075 /** @constant */
5076 net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM = "u62";
5077 /** @constant */
5078 net.user1.orbiter.UPC.CLIENT_READY = "u63";
5079 /** @constant */
5080 net.user1.orbiter.UPC.SERVER_HELLO = "u66";
5081 /** @constant */
5082 net.user1.orbiter.UPC.JOIN_ROOM_RESULT = "u72";
5083 /** @constant */
5084 net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT = "u73";
5085 /** @constant */
5086 net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT = "u74";
5087 /** @constant */
5088 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT = "u75";
5089 /** @constant */
5090 net.user1.orbiter.UPC.LEAVE_ROOM_RESULT = "u76";
5091 /** @constant */
5092 net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT = "u77";
5093 /** @constant */
5094 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT = "u78";
5095 /** @constant */
5096 net.user1.orbiter.UPC.ROOM_ATTR_REMOVED = "u79";
5097 /** @constant */
5098 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT = "u80";
5099 /** @constant */
5100 net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED = "u81";
5101 /** @constant */
5102 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT = "u82";
5103 /** @constant */
5104 net.user1.orbiter.UPC.SESSION_TERMINATED = "u84";
5105 /** @constant */
5106 net.user1.orbiter.UPC.SESSION_NOT_FOUND = "u85";
5107 /** @constant */
5108 net.user1.orbiter.UPC.LOGOFF_RESULT = "u87";
5109 /** @constant */
5110 net.user1.orbiter.UPC.LOGGED_IN = "u88";
5111 /** @constant */
5112 net.user1.orbiter.UPC.LOGGED_OFF = "u89";
5113 /** @constant */
5114 net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED = "u90";
5115 /** @constant */
5116 net.user1.orbiter.UPC.CLIENTLIST_SNAPSHOT = "u101";
5117 /** @constant */
5118 net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER = "u102";
5119 /** @constant */
5120 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER = "u103";
5121 /** @constant */
5122 net.user1.orbiter.UPC.CLIENT_SNAPSHOT = "u104";
5123 /** @constant */
5124 net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT = "u105";
5125 /** @constant */
5126 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT = "u106";
5127 /** @constant */
5128 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT = "u107";
5129 /** @constant */
5130 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT = "u108";
5131 /** @constant */
5132 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT = "u109";
5133 /** @constant */
5134 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "u110";
5135 /** @constant */
5136 net.user1.orbiter.UPC.ACCOUNT_ADDED = "u111";
5137 /** @constant */
5138 net.user1.orbiter.UPC.ACCOUNT_REMOVED = "u112";
5139 /** @constant */
5140 net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT = "u113";
5141 /** @constant */
5142 net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT = "u114";
5143 /** @constant */
5144 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT = "u115";
5145 /** @constant */
5146 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT = "u116";
5147 /** @constant */
5148 net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT = "u117";
5149 /** @constant */
5150 net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT = "u118";
5151 /** @constant */
5152 net.user1.orbiter.UPC.CLIENT_OBSERVED = "u119";
5153 /** @constant */
5154 net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT = "u120";
5155 /** @constant */
5156 net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT = "u123";
5157 /** @constant */
5158 net.user1.orbiter.UPC.ACCOUNT_OBSERVED = "u124";
5159 /** @constant */
5160 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT = "u125";
5161 /** @constant */
5162 net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT = "u126";
5163 /** @constant */
5164 net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE = "u127";
5165 /** @constant */
5166 net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE = "u128";
5167 /** @constant */
5168 net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM = "u129";
5169 /** @constant */
5170 net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM = "u130";
5171 /** @constant */
5172 net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE = "u131";
5173 /** @constant */
5174 net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE = "u132";
5175 /** @constant */
5176 net.user1.orbiter.UPC.ADD_ROLE_RESULT = "u134";
5177 /** @constant */
5178 net.user1.orbiter.UPC.REMOVE_ROLE_RESULT = "u136";
5179 /** @constant */
5180 net.user1.orbiter.UPC.BAN_RESULT = "u138";
5181 /** @constant */
5182 net.user1.orbiter.UPC.UNBAN_RESULT = "u140";
5183 /** @constant */
5184 net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT = "u142";
5185 /** @constant */
5186 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT = "u144";
5187 /** @constant */
5188 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "u146";
5189 /** @constant */
5190 net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED = "u147";
5191 /** @constant */
5192 net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED = "u148";
5193 /** @constant */
5194 net.user1.orbiter.UPC.KICK_CLIENT_RESULT = "u150";
5195 /** @constant */
5196 net.user1.orbiter.UPC.SERVERMODULELIST_SNAPSHOT = "u152";
5197 /** @constant */
5198 net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT_RESULT = "u155";
5199 /** @constant */
5200 net.user1.orbiter.UPC.UPC_STATS_SNAPSHOT = "u156";
5201 /** @constant */
5202 net.user1.orbiter.UPC.RESET_UPC_STATS_RESULT = "u158";
5203 /** @constant */
5204 net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS_RESULT = "u160";
5205 /** @constant */
5206 net.user1.orbiter.UPC.PROCESSED_UPC_ADDED = "u161";
5207 /** @constant */
5208 net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT = "u163";
5209 /** @constant */
5210 net.user1.orbiter.UPC.CONNECTION_REFUSED = "u164";
5211 /** @constant */
5212 net.user1.orbiter.UPC.NODELIST_SNAPSHOT = "u166";
5213 /** @constant */
5214 net.user1.orbiter.UPC.GATEWAYS_SNAPSHOT = "u168";
5215 //==============================================================================
5216 // LOADED FLAG
5217 //==============================================================================
5218 /** 
5219  * @constant 
5220  * 
5221  * Indicates that Orbiter has finished loading.
5222  */
5223 net.user1.orbiter.LOADED = true;
5224 
5225 })((typeof window == "undefined") ? this : window);
5226