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     = "Orbiter";
159 net.user1.orbiter.Product.clientVersion  = new net.user1.orbiter.VersionNumber(2,1,2,12);
160 net.user1.orbiter.Product.upcVersion     = new net.user1.orbiter.VersionNumber(1,10,3);
161 //==============================================================================
162 // A COLLECTION OF OBJECT UTILITIES
163 //==============================================================================
164 /** @class */
165 net.user1.utils.ObjectUtil = new Object();
166 
167 net.user1.utils.ObjectUtil.combine = function () {
168   var source = arguments.length == 1 ? arguments[0] : arguments;
169   var master = new Object();
170   
171   var object;
172   for (var i = 0; i < source.length; i++) {
173     object = source[i];
174     for (var key in object) {
175       if (object.hasOwnProperty(key)) {
176         master[key] = object[key];
177       }
178     }
179   }
180   return master;
181 };
182 
183 net.user1.utils.ObjectUtil.length = function (object) {
184   var len = 0;
185   for (var p in object) {
186     len++;
187   } 
188   return len;
189 };
190 
191 //==============================================================================
192 // A COLLECTION OF ARRAY UTILITIES
193 //==============================================================================
194 /** @class */
195 net.user1.utils.ArrayUtil = new Object();
196 
197 net.user1.utils.ArrayUtil.indexOf = function (arr, obj) {
198   if (arr.indexOf ) {
199     return arr.indexOf(obj);
200   }
201 
202   for (var i = arr.length; --i >= 0; ) {
203     if (arr[i] === obj) {
204       return i;
205     }
206   }
207   
208   return -1;
209 };
210 
211 net.user1.utils.ArrayUtil.remove = function (array, item) {
212   var itemIndex;
213   
214   if (item == null) {
215     return false;
216   } else {
217     itemIndex = net.user1.utils.ArrayUtil.indexOf(array, item);
218     if (itemIndex == -1) {
219       return false;
220     } else {
221       array.splice(itemIndex, 1);
222       return true;
223     }
224   }
225 };
226 
227 net.user1.utils.ArrayUtil.isArray = function (value) {
228   return Object.prototype.toString.call(value) === '[object Array]';
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   
1073 //==============================================================================
1074 // INSTANCE METHODS
1075 //==============================================================================
1076 
1077 net.user1.orbiter.ConnectionManagerEvent.prototype.getConnection = function () {
1078   return this.connection;
1079 }
1080 
1081 net.user1.orbiter.ConnectionManagerEvent.prototype.getStatus = function () {
1082   return this.status;
1083 }
1084 
1085 net.user1.orbiter.ConnectionManagerEvent.prototype.toString = function () {
1086   return "[object ConnectionManagerEvent]";
1087 }  
1088 
1089 //==============================================================================
1090 // CLASS DECLARATION
1091 //==============================================================================
1092 /** @class
1093 
1094 The ConnectionManager class dispatches the following events:
1095 
1096 <ul class="summary">
1097 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT}</li>
1098 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION}</li>
1099 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE}</li>
1100 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.DISCONNECT}</li>
1101 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT}</li>
1102 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT}</li>
1103 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.READY}</li>
1104 </ul>
1105 
1106 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
1107 
1108     @extends net.user1.events.EventDispatcher
1109 
1110  * @see net.user1.orbiter.Orbiter#connect
1111  */
1112 net.user1.orbiter.ConnectionManager = function (orbiter) {
1113     // Call superconstructor
1114     net.user1.events.EventDispatcher.call(this);
1115     
1116     // Variables
1117     this.orbiter             = orbiter;
1118     this.connectAttemptCount = 0;
1119     this.connectAbortCount   = 0;
1120     this.readyCount          = 0;      
1121     this.connectFailedCount  = 0;
1122     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1123     this.readyTimeout        = 0;
1124     this.connections         = new Array();
1125     this.activeConnection    = null;
1126     this.inProgressConnection = null;
1127     this.currentConnectionIndex = 0;
1128     this.attemptedConnections = null;
1129     this.setReadyTimeout(net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT);
1130     
1131     // Initialization
1132     // Make all Orbiter instances in this VM share the same server affinity 
1133     this.setGlobalAffinity(true);  
1134 };
1135     
1136 //==============================================================================    
1137 // INHERITANCE
1138 //============================================================================== 
1139 net.user1.utils.extend(net.user1.orbiter.ConnectionManager, net.user1.events.EventDispatcher);
1140 
1141 //==============================================================================
1142 // STATIC VARIABLES
1143 //==============================================================================
1144 net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT = 10000;
1145 
1146 // =============================================================================
1147 // CONNECT AND DISCONNECT
1148 // =============================================================================
1149 net.user1.orbiter.ConnectionManager.prototype.connect = function () {
1150   if (this.connections.length == 0) {
1151     this.orbiter.getLog().error("[CONNECTION_MANAGER] No connections defined. Connection request ignored.");
1152     return;
1153   }
1154   
1155   this.connectAttemptCount++;
1156   this.attemptedConnections = new Array();
1157 
1158   switch (this.connectionState) {
1159     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1160       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection attempt already in " 
1161                             + "progress. Existing attempt must be aborted before"  
1162                             + " new connection attempt begins...");
1163       this.disconnect();
1164       break;
1165 
1166     case net.user1.orbiter.ConnectionState.READY:
1167       this.orbiter.getLog().info("[CONNECTION_MANAGER] Existing connection to Union" 
1168                             + " must be disconnected before new connection" 
1169                             + " attempt begins.");
1170       this.disconnect();
1171       break;
1172   }
1173   this.setConnectionState(net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS);
1174   
1175   this.orbiter.getLog().debug("[CONNECTION_MANAGER] Searching for most recent valid connection.");
1176   var originalConnectionIndex = this.currentConnectionIndex;
1177   while (!this.getCurrentConnection().isValid()) {
1178     this.advance();
1179     if (this.currentConnectionIndex == originalConnectionIndex) {
1180       // Couldn't find a valid connection, so start the connection with
1181       // the first connection in the connection list
1182       this.orbiter.getLog().debug("[CONNECTION_MANAGER] No valid connection found. Starting connection attempt with first connection.");
1183       this.currentConnectionIndex = 0;
1184       break;
1185     }
1186   }  
1187   
1188   this.dispatchBeginConnect();
1189   this.connectCurrentConnection();
1190 };
1191 
1192 net.user1.orbiter.ConnectionManager.prototype.disconnect = function () {
1193   if (this.connections.length == 0) {
1194     this.dispatchConnectFailure("No connections defined. Disconnection attempt failed.");
1195     return;
1196   }
1197   
1198   switch (this.connectionState) {
1199     // Currently connected
1200     case net.user1.orbiter.ConnectionState.READY:
1201       this.orbiter.getLog().info("[CONNECTION_MANAGER] Closing existing connection: "
1202                             + this.getActiveConnection().toString());
1203       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1204       this.disconnectConnection(this.getActiveConnection());
1205       break;
1206 
1207     // Currently attempting to connect
1208     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1209       this.orbiter.getLog().info("[CONNECTION_MANAGER] Aborting existing connection attempt: "
1210                             + this.getInProgressConnection().toString());
1211       this.connectAbortCount++;
1212       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1213       this.disconnectConnection(this.getInProgressConnection());
1214       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection abort complete.");
1215       break;
1216 
1217     // Currently attempting to disconnect
1218     case net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS:
1219       this.orbiter.getLog().info("[CONNECTION_MANAGER] Disconnection request ignored."
1220                             + " Already disconnecting.");
1221       break;
1222   }
1223 };
1224     
1225 /** @private */
1226 net.user1.orbiter.ConnectionManager.prototype.disconnectConnection = function (connection) {
1227   connection.disconnect();
1228 };
1229     
1230 /** @private */
1231 net.user1.orbiter.ConnectionManager.prototype.connectCurrentConnection = function () {
1232   // If there are no Connections defined, fail immediately 
1233   if (this.connections.length == 0) {
1234     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1235     this.connectFailedCount++;
1236     this.dispatchConnectFailure("No connections defined. Connection attempt failed.");
1237     return;
1238   }
1239   
1240   this.inProgressConnection = this.getCurrentConnection();
1241   
1242   // If the requested connection has already been attempted this round,
1243   // ignore it.
1244   if (net.user1.utils.ArrayUtil.indexOf(this.attemptedConnections, this.inProgressConnection) != -1) {
1245     this.advanceAndConnect();
1246     return;
1247   }
1248   
1249   this.dispatchSelectConnection(this.inProgressConnection);
1250   this.orbiter.getLog().info("[CONNECTION_MANAGER] Attempting connection via "
1251                         + this.inProgressConnection.toString() + ". (Connection "
1252                         + (this.attemptedConnections.length+1) + " of "
1253                         + this.connections.length + ". Attempt " + this.connectAttemptCount +" since last successful connection).");
1254   this.addConnectionListeners(this.inProgressConnection);
1255   this.inProgressConnection.connect();
1256 };
1257 
1258 /** @private */
1259 net.user1.orbiter.ConnectionManager.prototype.advanceAndConnect = function () {
1260   if (!this.connectAttemptComplete()) {
1261     this.advance();
1262     this.connectCurrentConnection();
1263   } else {
1264     // Tried all connections, so give up and dispatch CONNECT_FAILURE
1265     this.connectFailedCount++;
1266     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1267     this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for all specified hosts and ports.");
1268     this.dispatchConnectFailure("Connection failed for all specified hosts and ports.");
1269   }
1270 };
1271     
1272 /** @private */
1273 net.user1.orbiter.ConnectionManager.prototype.connectAttemptComplete = function () {
1274   return this.attemptedConnections.length == this.connections.length;
1275 };
1276 
1277 /** @private */
1278 net.user1.orbiter.ConnectionManager.prototype.advance = function () {
1279   this.currentConnectionIndex++;
1280   if (this.currentConnectionIndex == this.connections.length) {
1281     this.currentConnectionIndex = 0;
1282   }
1283 };
1284     
1285 // =============================================================================
1286 // CONNECTION OBJECT MANAGEMENT
1287 // =============================================================================
1288 net.user1.orbiter.ConnectionManager.prototype.addConnection = function (connection) {
1289   if (connection != null) {
1290     this.orbiter.getLog().info("[CONNECTION_MANAGER] New connection added. "
1291                           + connection.toString() + ".");
1292     connection.setOrbiter(this.orbiter);
1293     this.connections.push(connection);
1294   }
1295 };
1296     
1297 net.user1.orbiter.ConnectionManager.prototype.removeConnection = function (connection) {
1298   if (connection != null) {
1299     connection.disconnect();
1300     this.removeConnectionListeners(connection);
1301     return net.user1.utils.ArrayUtil.remove(this.connections, connection);
1302   } else {
1303     return false;
1304   }
1305 };
1306 
1307 net.user1.orbiter.ConnectionManager.prototype.removeAllConnections = function () {
1308   if (this.connections.length == 0) {
1309     this.orbiter.getLog().info("[CONNECTION_MANAGER] removeAllConnections() ignored. " +
1310                                " No connections to remove.");
1311     return;
1312   }
1313   
1314   this.orbiter.getLog().info("[CONNECTION_MANAGER] Removing all connections...");
1315   this.disconnect();
1316   while (this.connections.length > 0) {
1317     this.removeConnection(this.connections[0]);
1318   }
1319   this.currentConnectionIndex = 0;
1320   this.orbiter.getLog().info("[CONNECTION_MANAGER] All connections removed.");
1321 };
1322     
1323 // =============================================================================
1324 // CONNECTION ACCESS
1325 // =============================================================================
1326 net.user1.orbiter.ConnectionManager.prototype.getActiveConnection = function () {
1327   return this.activeConnection;
1328 };
1329     
1330 net.user1.orbiter.ConnectionManager.prototype.getInProgressConnection = function () {
1331   return this.inProgressConnection;
1332 };
1333     
1334 net.user1.orbiter.ConnectionManager.prototype.getConnections = function () {
1335   return this.connections.slice();
1336 };
1337 
1338 /** @private */    
1339 net.user1.orbiter.ConnectionManager.prototype.getCurrentConnection = function () {
1340   return this.connections[this.currentConnectionIndex];
1341 };
1342     
1343 // =============================================================================
1344 // CONNECTION LISTENER REGISTRATION
1345 // =============================================================================
1346 /** @private */
1347 net.user1.orbiter.ConnectionManager.prototype.addConnectionListeners = function(connection) {
1348   if (connection != null) {
1349     connection.addEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1350     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1351     connection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1352     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1353     connection.addEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1354   }
1355 };
1356     
1357 /** @private */    
1358 net.user1.orbiter.ConnectionManager.prototype.removeConnectionListeners = function (connection) {
1359   if (connection != null) {
1360     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1361     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1362     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1363     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1364     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1365   }
1366 };
1367     
1368 // =============================================================================
1369 // CONNECTION STATE ACCESS
1370 // =============================================================================
1371 net.user1.orbiter.ConnectionManager.prototype.isReady = function () {
1372   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
1373 }
1374 
1375 net.user1.orbiter.ConnectionManager.prototype.setConnectionState = function (state) {
1376   var changed = false;
1377   if (state != this.connectionState) {
1378     changed = true;
1379   }
1380   this.connectionState = state;
1381   if (changed) {
1382     this.dispatchConnectionStateChange();
1383   }
1384 };
1385 
1386 net.user1.orbiter.ConnectionManager.prototype.getConnectionState = function () {
1387   return this.connectionState;
1388 };
1389     
1390 // =============================================================================
1391 // CONNECTION COUNT MANAGEMENT
1392 // =============================================================================
1393 net.user1.orbiter.ConnectionManager.prototype.getReadyCount = function () {
1394   return this.readyCount;
1395 };
1396   
1397 net.user1.orbiter.ConnectionManager.prototype.getConnectFailedCount = function () {
1398   return this.connectFailedCount;
1399 };
1400   
1401 net.user1.orbiter.ConnectionManager.prototype.getConnectAttemptCount = function () {
1402   return this.connectAttemptCount;
1403 };
1404   
1405 net.user1.orbiter.ConnectionManager.prototype.getConnectAbortCount = function () {
1406   return this.connectAbortCount;
1407 };
1408     
1409 // =============================================================================
1410 // CURRENT CONNECTION LISTENERS
1411 // =============================================================================
1412 /** @private */
1413 net.user1.orbiter.ConnectionManager.prototype.readyListener = function (e) {
1414   this.setConnectionState(net.user1.orbiter.ConnectionState.READY);
1415   this.inProgressConnection = null;
1416   this.activeConnection = e.target;
1417   this.readyCount++;
1418   this.connectFailedCount = 0;
1419   this.connectAttemptCount = 0;
1420   this.connectAbortCount = 0;
1421   this.dispatchReady();
1422 };
1423 
1424 /** @private */
1425 net.user1.orbiter.ConnectionManager.prototype.connectFailureListener = function (e) {
1426   var failedConnection = e.target;
1427   this.orbiter.getLog().warn("[CONNECTION_MANAGER] Connection failed for "
1428                         + failedConnection.toString() 
1429                         + ". Status: [" + e.getStatus() + "]");
1430   
1431   this.removeConnectionListeners(failedConnection);
1432   this.inProgressConnection = null;
1433   
1434   if (this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
1435     this.dispatchConnectFailure("Connection closed by client.");
1436   } else {
1437     if (failedConnection.getHost() != failedConnection.getRequestedHost()) {
1438       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for affinity address [" + failedConnection.getHost() + "]. Removing affinity.");
1439       this.clearAffinity(failedConnection.getRequestedHost());
1440     }
1441 
1442     this.attemptedConnections.push(failedConnection);
1443     this.advanceAndConnect();
1444   }
1445 };
1446 
1447 /** @private */
1448 net.user1.orbiter.ConnectionManager.prototype.disconnectListener = function (e) {
1449   this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1450   this.removeConnectionListeners(e.target);
1451   this.activeConnection = null;
1452   this.dispatchDisconnect(e.target);
1453 };
1454 
1455 /** @private */
1456 net.user1.orbiter.ConnectionManager.prototype.clientKillConnectListener = function (e) {
1457   this.dispatchClientKillConnect(e.target);
1458   // This event is always followed by a DISCONNECT event
1459 };
1460 
1461 /** @private */
1462 net.user1.orbiter.ConnectionManager.prototype.serverKillConnectListener = function (e) {
1463   this.dispatchServerKillConnect(e.target);
1464   // This event is always followed by a DISCONNECT event
1465 };
1466 
1467 // =============================================================================
1468 // READY TIMEOUT MANAGEMENT
1469 // =============================================================================
1470     
1471 net.user1.orbiter.ConnectionManager.prototype.setReadyTimeout = function (milliseconds) {
1472   if (milliseconds > 0) {
1473     this.readyTimeout = milliseconds;
1474     this.orbiter.getLog().info("[CONNECTION_MANAGER] Ready timeout set to " + milliseconds + " ms.");
1475     if (milliseconds < 3000) {
1476       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Current ready timeout (" 
1477                            + milliseconds + ") may not allow sufficient time"
1478                            + " to connect to Union Server over a typical"
1479                            + " internet connection.");
1480     }
1481   } else {
1482     this.orbiter.getLog().warn("[CONNECTION_MANAGER] Invalid ready timeout specified: " 
1483              + milliseconds + ". Duration must be greater than zero.");
1484   }
1485 };
1486     
1487 net.user1.orbiter.ConnectionManager.prototype.getReadyTimeout = function () {
1488   return this.readyTimeout;
1489 };
1490 
1491 // =============================================================================
1492 // SERVER AFFINITY
1493 // =============================================================================
1494 net.user1.orbiter.ConnectionManager.prototype.getAffinity = function (host) {
1495   var address = this.affinityData.read("affinity", host+"address");
1496   var until = parseFloat(this.affinityData.read("affinity", host+"until"));
1497   
1498   if (address != null) {
1499     var now = new Date().getTime();
1500     if (now >= until) {
1501       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Affinity duration expired for address [" 
1502                                  + address + "], host [" + host + "]. Removing affinity.");
1503       this.clearAffinity(host);
1504     } else {
1505       return address;
1506     }
1507   }
1508 
1509   return host;
1510 };
1511 
1512 /**
1513  * @private
1514  */
1515 net.user1.orbiter.ConnectionManager.prototype.setAffinity = function (host, address, duration) {
1516   var until = new Date().getTime() + (duration*60*1000);
1517   // Don't use JSON stringify for affinity values because not all JavaScript
1518   // environments support JSON natively (e.g., non-browser VMs)
1519   this.affinityData.write("affinity", host+"address", address);
1520   this.affinityData.write("affinity", host+"until", until);
1521 
1522   this.orbiter.getLog().info("[CONNECTION_MANAGER] Assigning affinity address [" 
1523     + address + "] for supplied host [" +host + "]. Duration (minutes): "
1524     + duration);
1525 };
1526 
1527 /**
1528  * @private
1529  */
1530 net.user1.orbiter.ConnectionManager.prototype.clearAffinity = function (host) {
1531   this.affinityData.remove("affinity", host+"address");
1532   this.affinityData.remove("affinity", host+"until");
1533 };
1534     
1535 net.user1.orbiter.ConnectionManager.prototype.setGlobalAffinity = function (enabled) {
1536   if (enabled) {
1537     this.orbiter.getLog().info("[CONNECTION_MANAGER] Global server affinity selected."
1538      + " Using current environment's shared server affinity."); 
1539     this.affinityData = net.user1.utils.LocalData;
1540   } else {
1541     this.orbiter.getLog().info("[CONNECTION_MANAGER] Local server affinity selected."
1542      + " The current client will maintain its own, individual server affinity."); 
1543     this.affinityData = new net.user1.utils.MemoryStore();
1544   }
1545 };
1546 
1547 // =============================================================================
1548 // EVENT DISPATCHING
1549 // =============================================================================
1550     
1551 /** @private */
1552 net.user1.orbiter.ConnectionManager.prototype.dispatchBeginConnect = function () {
1553   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT));
1554 };
1555     
1556 /** @private */
1557 net.user1.orbiter.ConnectionManager.prototype.dispatchSelectConnection = function (connection) {
1558   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
1559       connection));
1560 };
1561     
1562 /** @private */
1563 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectFailure = function (status) {
1564   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE,
1565       null, status));
1566 };
1567     
1568 /** @private */
1569 net.user1.orbiter.ConnectionManager.prototype.dispatchDisconnect = function (connection) {
1570   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT,
1571       connection));
1572 };
1573     
1574 /** @private */
1575 net.user1.orbiter.ConnectionManager.prototype.dispatchServerKillConnect = function (connection) {
1576   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT,
1577       connection));
1578 };
1579     
1580 /** @private */
1581 net.user1.orbiter.ConnectionManager.prototype.dispatchClientKillConnect = function (connection) {
1582   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT,
1583       connection));
1584 };
1585     
1586 /** @private */
1587 net.user1.orbiter.ConnectionManager.prototype.dispatchReady = function () {
1588   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.READY));
1589 };
1590 
1591 /** @private */
1592 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectionStateChange = function () {
1593   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECTION_STATE_CHANGE));
1594 };
1595 
1596 // =============================================================================
1597 // DISPOSAL
1598 // =============================================================================    
1599 net.user1.orbiter.ConnectionManager.prototype.dispose = function () {
1600   this.removeAllConnections();
1601   this.attemptedConnections = null;
1602   this.activeConnection = null;
1603   this.inProgressConnection = null;
1604   this.connections = null;
1605 };
1606 
1607 
1608 
1609 
1610 
1611 
1612 
1613 
1614 
1615 
1616 
1617 
1618 
1619 
1620 
1621 
1622 
1623 
1624 
1625 
1626 
1627 
1628 //==============================================================================
1629 // CLASS DECLARATION
1630 //==============================================================================
1631 /** @class */
1632 net.user1.orbiter.ConnectionMonitor = function (orbiter) {
1633   // Instance variables
1634   this.connectionTimeout = 0;
1635   this.heartbeatIntervalID = -1;
1636   this.heartbeatCounter = 0;
1637   this.heartbeatEnabled = true;
1638   this.heartbeats = new net.user1.utils.UDictionary();
1639   
1640   this.oldestHeartbeat = 0;
1641   this.heartBeatFrequency = -1;
1642   
1643   this.sharedPing = false;
1644 
1645   this.autoReconnectMinMS = 0;
1646   this.autoReconnectMaxMS = 0;
1647   this.autoReconnectFrequency = -1;
1648   this.autoReconnectDelayFirstAttempt = false;
1649   this.autoReconnectTimeoutID = -1;
1650   this.autoReconnectAttemptLimit = -1;
1651   
1652   this.orbiter = orbiter;
1653   this.msgManager = orbiter.getMessageManager();
1654   this.log = orbiter.getLog();
1655   
1656   this.disposed = false;
1657   
1658   // Initialization
1659   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
1660   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
1661   this.disableHeartbeatLogging();
1662 };
1663 
1664 //==============================================================================
1665 // STATIC VARIABLES
1666 //==============================================================================
1667 net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY = 10000;
1668 net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY = 20;
1669 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY = -1;
1670 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT = -1;
1671 net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT = 60000;
1672 
1673 //==============================================================================
1674 // CONNECTION MONITORING
1675 //==============================================================================
1676 /** @private */
1677 net.user1.orbiter.ConnectionMonitor.prototype.connectReadyListener = function (e) {
1678   this.msgManager.addMessageListener(net.user1.orbiter.Messages.CLIENT_HEARTBEAT, this.heartbeatMessageListener, this);
1679   this.startHeartbeat();
1680   this.stopReconnect();
1681 }
1682 
1683 /** @private */
1684 net.user1.orbiter.ConnectionMonitor.prototype.connectCloseListener = function (e) {
1685   this.stopHeartbeat();
1686 
1687   var numAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1688   if (numAttempts == 0) {
1689     this.selectReconnectFrequency();
1690   }
1691 
1692   if (this.autoReconnectFrequency > -1) {
1693     if (this.autoReconnectTimeoutID != -1) {
1694       return;
1695     } else {
1696       // Defer reconnection until after all other listeners have processed the
1697       // CLOSE event
1698       var self = this;
1699       setTimeout(function () {
1700         // If another listener disposed of Orbiter, or disabled autoreconnect, quit
1701         if (!self.disposed && self.autoReconnectFrequency != -1) {
1702           self.log.warn("[CONNECTION_MONITOR] Disconnection detected.");
1703           if (self.autoReconnectDelayFirstAttempt
1704               && (
1705                   (numAttempts == 0)
1706                   ||
1707                   (numAttempts == 1 && self.orbiter.getConnectionManager().getReadyCount() == 0)
1708                  )
1709              ) {
1710             self.log.info("[CONNECTION_MONITOR] Delaying reconnection attempt"
1711               + " by " + self.autoReconnectFrequency + " ms...");
1712             self.scheduleReconnect(self.autoReconnectFrequency);
1713           } else {
1714             self.doReconnect();
1715           }
1716         }
1717       }, 1);
1718     }
1719   }
1720 }
1721     
1722 //==============================================================================
1723 // HEARTBEAT
1724 //==============================================================================
1725 
1726 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeat = function () {
1727   this.log.info("[CONNECTION_MONITOR] Heartbeat enabled.");
1728   this.heartbeatEnabled = true;
1729   this.startHeartbeat();
1730 }
1731 
1732 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeat = function () {
1733   this.log.info("[CONNECTION_MONITOR] Heartbeat disabled.");
1734   this.heartbeatEnabled = false;
1735   this.stopHeartbeat();
1736 }
1737 
1738 /** @private */
1739 net.user1.orbiter.ConnectionMonitor.prototype.startHeartbeat = function () {
1740   if (!this.heartbeatEnabled) {
1741     this.log.info("[CONNECTION_MONITOR] Heartbeat is currently disabled. Ignoring start request.");
1742     return;
1743   }
1744   
1745   this.stopHeartbeat();
1746   
1747   this.heartbeats = new net.user1.utils.UDictionary();
1748   
1749   var currentObj = this;
1750   var callback   = this.heartbeatTimerListener;
1751   this.heartbeatIntervalID = setInterval(function () {
1752     callback.call(currentObj);
1753   }, this.heartBeatFrequency);
1754   
1755 }
1756 
1757 /** @private */
1758 net.user1.orbiter.ConnectionMonitor.prototype.stopHeartbeat = function () {
1759   clearInterval(this.heartbeatIntervalID);
1760   this.heartbeats = null;
1761 }
1762 
1763 /** @private */
1764 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatTimerListener = function () {
1765   if (!this.orbiter.isReady()) {
1766     this.log.info("[CONNECTION_MONITOR] Orbiter is not connected. Stopping heartbeat.");
1767     this.stopHeartbeat();
1768     return;
1769   }
1770 
1771   var timeSinceOldestHeartbeat;
1772   var now = new Date().getTime();
1773   
1774   this.heartbeats[this.heartbeatCounter] = now;
1775   this.orbiter.getMessageManager().sendUPC("u2",
1776                                  net.user1.orbiter.Messages.CLIENT_HEARTBEAT, 
1777                                  this.orbiter.getClientID(),
1778                                  "",
1779                                  this.heartbeatCounter);
1780   this.heartbeatCounter++;
1781   
1782   // Assign the oldest heartbeat
1783   if (net.user1.utils.ObjectUtil.length(this.heartbeats) == 1) {
1784     this.oldestHeartbeat = now;
1785   } else { 
1786     this.oldestHeartbeat = Number.MAX_VALUE;
1787     for (var p in this.heartbeats) {
1788       if (this.heartbeats[p] < this.oldestHeartbeat) {
1789         this.oldestHeartbeat = this.heartbeats[p];
1790       }
1791     }
1792   }
1793   // Close connection if too much time has passed since the last response
1794   timeSinceOldestHeartbeat = now - this.oldestHeartbeat;
1795   if (timeSinceOldestHeartbeat > this.connectionTimeout) {
1796     this.log.warn("[CONNECTION_MONITOR] No response from server in " + 
1797                   timeSinceOldestHeartbeat + "ms. Starting automatic disconnect.");
1798     this.orbiter.disconnect();
1799   }
1800 }
1801 
1802 /** @private */
1803 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatMessageListener = function (fromClientID, id) {
1804   var ping = new Date().getTime() - this.heartbeats[parseInt(id)];
1805   if (typeof this.orbiter.self().setAttribute === "undefined") {
1806     // OrbiterMicro
1807     this.orbiter.self().ping = ping;
1808     this.orbiter.getMessageManager().sendUPC("u3",
1809                                              this.orbiter.getClientID(),
1810                                              "",
1811                                              "_PING",
1812                                              ping.toString(),
1813                                              "",
1814                                              this.sharedPing ? "4" : "0");
1815   } else {
1816     // Orbiter
1817     this.orbiter.self().setAttribute("_PING",
1818                                      ping.toString(),
1819                                      null,
1820                                      this.sharedPing);
1821   }
1822   delete this.heartbeats[parseInt(id)];
1823 }
1824 
1825 //==============================================================================
1826 // RECONNECTION
1827 //==============================================================================
1828 /** @private */
1829 net.user1.orbiter.ConnectionMonitor.prototype.reconnectTimerListener = function (e) {
1830   this.stopReconnect();
1831   if (this.orbiter.getConnectionManager().connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
1832     this.doReconnect();
1833   }
1834 }
1835 
1836 /** @private */
1837 net.user1.orbiter.ConnectionMonitor.prototype.stopReconnect = function () {
1838   clearTimeout(this.autoReconnectTimeoutID);
1839   this.autoReconnectTimeoutID = -1
1840 }
1841 
1842 /** @private */
1843 net.user1.orbiter.ConnectionMonitor.prototype.scheduleReconnect = function (milliseconds) {
1844   // Reset the timer
1845   this.stopReconnect();
1846   var currentObj = this;
1847   var callback   = this.reconnectTimerListener;
1848   this.autoReconnectTimeoutID = setTimeout(function () {
1849     currentObj.autoReconnectTimeoutID = -1;
1850     callback.call(currentObj);
1851   }, milliseconds);
1852 };
1853 
1854 /** @private */
1855 net.user1.orbiter.ConnectionMonitor.prototype.selectReconnectFrequency = function () {
1856   if (this.autoReconnectMinMS == -1) {
1857     this.autoReconnectFrequency = -1;
1858   } else if (this.autoReconnectMinMS == this.autoReconnectMaxMS) {
1859     this.autoReconnectFrequency = this.autoReconnectMinMS;
1860   } else {
1861     this.autoReconnectFrequency = getRandInt(this.autoReconnectMinMS, this.autoReconnectMaxMS);
1862     this.log.info("[CONNECTION_MONITOR] Random auto-reconnect frequency selected: [" +
1863                   this.autoReconnectFrequency + "] ms.");
1864   }
1865 
1866   function getRandInt (min, max) {
1867     return min + Math.floor(Math.random()*(max+1 - min));
1868   }
1869 };
1870 
1871 /** @private */
1872 net.user1.orbiter.ConnectionMonitor.prototype.doReconnect = function () {
1873   var numActualAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1874   var numReconnectAttempts;
1875 
1876   if (this.orbiter.getConnectionManager().getReadyCount() == 0) {
1877     numReconnectAttempts = numActualAttempts - 1;
1878   } else {
1879     numReconnectAttempts = numActualAttempts;
1880   }
1881 
1882   if (this.autoReconnectAttemptLimit != -1
1883       && numReconnectAttempts > 0
1884       && numReconnectAttempts % (this.autoReconnectAttemptLimit) == 0) {
1885     this.log.warn("[CONNECTION_MONITOR] Automatic reconnect attempt limit reached."
1886                   + " No further automatic connection attempts will be made until"
1887                   + " the next manual connection attempt.");
1888     return;
1889   }
1890 
1891   this.scheduleReconnect(this.autoReconnectFrequency);
1892 
1893   this.log.warn("[CONNECTION_MONITOR] Attempting automatic reconnect. (Next attempt in "
1894                 + this.autoReconnectFrequency + "ms.)");
1895   this.orbiter.connect();
1896 }
1897 
1898 //==============================================================================
1899 // CONFIGURATION
1900 //==============================================================================
1901 
1902 net.user1.orbiter.ConnectionMonitor.prototype.restoreDefaults = function () {
1903   this.setAutoReconnectFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY);
1904   this.setAutoReconnectAttemptLimit(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT);
1905   this.setConnectionTimeout(net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT);
1906   this.setHeartbeatFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY);
1907 }
1908 
1909 net.user1.orbiter.ConnectionMonitor.prototype.setHeartbeatFrequency = function (milliseconds) {
1910   if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY) {
1911     this.heartBeatFrequency = milliseconds;
1912     this.log.info("[CONNECTION_MONITOR] Heartbeat frequency set to " 
1913                   + milliseconds + " ms.");
1914     // Log a warning for low heartbeat frequencies...
1915     if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY && milliseconds < 1000) {
1916       this.log.info("[CONNECTION_MONITOR] HEARTBEAT FREQUENCY WARNING: " 
1917                + milliseconds + " ms. Current frequency will generate "
1918                + (Math.floor((1000/milliseconds)*10)/10) 
1919                + " messages per second per connected client.");
1920     }
1921     
1922     // If the connection is ready, then restart
1923     // the heartbeat when the heartbeat frequency changes.
1924     if (this.orbiter.isReady()) {
1925       this.startHeartbeat();
1926     }
1927   } else {
1928     this.log.warn("[CONNECTION_MONITOR] Invalid heartbeat frequency specified: " 
1929              + milliseconds + ". Frequency must be "
1930              + net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY + " or greater.");
1931   }
1932 }
1933 
1934 net.user1.orbiter.ConnectionMonitor.prototype.getHeartbeatFrequency = function () {
1935   return this.heartBeatFrequency;
1936 }
1937 
1938 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectFrequency = function (minMS, maxMS, delayFirstAttempt) {
1939   maxMS = (typeof maxMS == "undefined") ? -1 : maxMS;
1940   delayFirstAttempt = (typeof delayFirstAttempt == "undefined") ? false : delayFirstAttempt;
1941 
1942   if (minMS == 0 || minMS < -1) {
1943     this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect minMS specified: ["
1944       + minMS + "]. Value must not be zero or less than -1. Value adjusted"
1945       + " to [-1] (no reconnect).");
1946     minMS = -1;
1947   }
1948   if (minMS == -1) {
1949     this.stopReconnect();
1950   } else {
1951     if (maxMS == -1) {
1952       maxMS = minMS;
1953     }
1954     if (maxMS < minMS) {
1955       this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect maxMS specified: ["
1956                     + maxMS + "]." + " Value of maxMS must be greater than or equal "
1957                     + "to minMS. Value adjusted to [" + minMS + "].");
1958       maxMS = minMS;
1959     }
1960   }
1961 
1962   this.autoReconnectDelayFirstAttempt = delayFirstAttempt;
1963   this.autoReconnectMinMS = minMS;
1964   this.autoReconnectMaxMS = maxMS;
1965 
1966   this.log.info("[CONNECTION_MONITOR] Assigning auto-reconnect frequency settings: [minMS: "
1967                 + minMS + ", maxMS: " + maxMS + ", delayFirstAttempt: "
1968                 + delayFirstAttempt.toString() + "].");
1969   if (minMS > 0 && minMS < 1000) {
1970     this.log.info("[CONNECTION_MONITOR] RECONNECT FREQUENCY WARNING: "
1971                   + minMS + " minMS specified. Current frequency will cause "
1972                   + (Math.floor((1000/minMS)*10)/10).toString()
1973                   + " reconnection attempts per second.");
1974   }
1975   this.selectReconnectFrequency();
1976 }
1977 
1978 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectFrequency = function () {
1979   return this.autoReconnectFrequency;
1980 }
1981 
1982 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectAttemptLimit = function (attempts) {
1983   if (attempts < -1 || attempts == 0) {
1984     this.log.warn("[CONNECTION_MONITOR] Invalid Auto-reconnect attempt limit specified: " 
1985              + attempts + ". Limit must -1 or greater than 1.");
1986     return;
1987   }
1988     
1989   this.autoReconnectAttemptLimit = attempts;
1990   
1991   if (attempts == -1) {
1992     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to none."); 
1993   } else {
1994     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to " 
1995                   + attempts + " attempt(s).");
1996   }
1997 };
1998     
1999 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectAttemptLimit = function () {
2000   return this.autoReconnectAttemptLimit;
2001 }
2002 
2003 net.user1.orbiter.ConnectionMonitor.prototype.setConnectionTimeout = function (milliseconds) {
2004   if (milliseconds > 0) {
2005     this.connectionTimeout = milliseconds;
2006     this.log.info("[CONNECTION_MONITOR] Connection timeout set to " 
2007                   + milliseconds + " ms.");
2008   } else {
2009     this.log.warn("[CONNECTION_MONITOR] Invalid connection timeout specified: " 
2010                              + milliseconds + ". Frequency must be greater " 
2011                              + "than zero.");
2012   }
2013 }
2014 
2015 net.user1.orbiter.ConnectionMonitor.prototype.getConnectionTimeout = function () {
2016   return this.connectionTimeout;
2017 }
2018 
2019 net.user1.orbiter.ConnectionMonitor.prototype.sharePing = function (share) {
2020   this.sharedPing = share;
2021 }
2022 
2023 net.user1.orbiter.ConnectionMonitor.prototype.isPingShared = function () {
2024   return this.sharedPing;
2025 }
2026 
2027 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeatLogging = function () {
2028   this.log.addSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2029   this.log.addSuppressionTerm("<A>_PING</A>");
2030   this.log.addSuppressionTerm("[_PING]");
2031   this.log.addSuppressionTerm("<![CDATA[_PING]]>");
2032 }
2033 
2034 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeatLogging = function () {
2035   this.log.removeSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2036   this.log.removeSuppressionTerm("<A>_PING</A>");
2037   this.log.removeSuppressionTerm("[_PING]");
2038   this.log.removeSuppressionTerm("<![CDATA[_PING]]>");
2039 }
2040 
2041 // =============================================================================
2042 // DISPOSAL
2043 // =============================================================================
2044 
2045 net.user1.orbiter.ConnectionMonitor.prototype.dispose = function () {
2046   this.disposed = true;
2047   
2048   this.stopHeartbeat();
2049   this.stopReconnect();
2050 
2051   this.heartbeats = null;
2052   
2053   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
2054   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
2055   this.orbiter = null;
2056   this.msgManager.removeMessageListener("u7", this.u7);
2057   this.msgManager(null);
2058   this.log = null;
2059 };
2060 
2061 
2062 
2063 
2064 
2065 
2066 
2067 
2068 
2069 
2070 
2071 
2072 
2073 
2074 
2075 
2076 
2077 //==============================================================================
2078 // CLASS DECLARATION
2079 //==============================================================================
2080 /** @class
2081     @extends net.user1.events.Event
2082 */
2083 net.user1.orbiter.OrbiterEvent = function (type, 
2084                                            serverUPCVersion,
2085                                            connectionRefusal) {
2086   net.user1.events.Event.call(this, type);
2087 
2088   this.serverUPCVersion = serverUPCVersion;
2089   this.connectionRefusal = connectionRefusal;
2090 };
2091 
2092 //==============================================================================
2093 // INHERITANCE
2094 //==============================================================================
2095 net.user1.utils.extend(net.user1.orbiter.OrbiterEvent, net.user1.events.Event);
2096  
2097 //==============================================================================
2098 // STATIC VARIABLES
2099 //==============================================================================
2100 /** @constant */
2101 net.user1.orbiter.OrbiterEvent.READY = "READY";
2102 /** @constant */
2103 net.user1.orbiter.OrbiterEvent.CLOSE = "CLOSE";
2104 /** @constant */
2105 net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE = "PROTOCOL_INCOMPATIBLE";
2106 /** @constant */
2107 net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED = "CONNECT_REFUSED";
2108 
2109 //==============================================================================
2110 // INSTANCE METHODS
2111 //==============================================================================  
2112 net.user1.orbiter.OrbiterEvent.prototype.getServerUPCVersion = function () {
2113   return this.serverUPCVersion;
2114 }
2115 
2116 net.user1.orbiter.OrbiterEvent.prototype.getConnectionRefusal = function () {
2117   return this.connectionRefusal;
2118 }
2119 
2120 net.user1.orbiter.OrbiterEvent.prototype.toString = function () {
2121   return "[object OrbiterEvent]";
2122 }  
2123 
2124 //==============================================================================
2125 // CLASS DECLARATION
2126 //==============================================================================
2127 /** @class
2128 
2129 The Snapshot class dispatches the following events:
2130 
2131 <ul class="summary">
2132 <li class="fixedFont">{@link net.user1.orbiter.SnapshotEvent.LOAD}</li>
2133 <li class="fixedFont">{@link net.user1.orbiter.SnapshotEvent.STATUS}</li>
2134 </ul>
2135 
2136 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2137 
2138     @extends net.user1.events.EventDispatcher
2139 */
2140 net.user1.orbiter.snapshot.Snapshot = function () {
2141   // Call superconstructor
2142   net.user1.events.EventDispatcher.call(this);
2143   this.method;
2144   this.args = new Array();
2145   this.hasStatus;
2146   this.statusReceived;
2147   this.loaded;
2148   this._updateInProgress;
2149   this._status;
2150   this.onLoad = function () {};
2151 };
2152 
2153 //==============================================================================    
2154 // INHERITANCE
2155 //============================================================================== 
2156 net.user1.utils.extend(net.user1.orbiter.snapshot.Snapshot, net.user1.events.EventDispatcher);
2157 
2158 //==============================================================================
2159 // INSTANCE METHODS
2160 //==============================================================================   
2161 net.user1.orbiter.snapshot.Snapshot.prototype.updateInProgress = function () {
2162   return this._updateInProgress;
2163 };
2164     
2165 /**
2166  * @private
2167  */    
2168 net.user1.orbiter.snapshot.Snapshot.prototype.setUpdateInProgress = function (value) {
2169   this._updateInProgress = value;
2170 };
2171     
2172 /**
2173  * @private
2174  */    
2175 net.user1.orbiter.snapshot.Snapshot.prototype.dispatchLoaded = function () {
2176   this.dispatchEvent(new net.user1.orbiter.snapshot.SnapshotEvent(net.user1.orbiter.snapshot.SnapshotEvent.LOAD, this));
2177 };
2178     
2179 /**
2180  * @private
2181  */    
2182 net.user1.orbiter.snapshot.Snapshot.prototype.dispatchStatus = function () {
2183   this.dispatchEvent(new net.user1.orbiter.snapshot.SnapshotEvent(net.user1.orbiter.snapshot.SnapshotEvent.STATUS, this));
2184 };
2185 
2186 net.user1.orbiter.snapshot.Snapshot.prototype.getStatus = function () {
2187   return this._status;
2188 };
2189 
2190 /**
2191  * @private
2192  */    
2193 net.user1.orbiter.snapshot.Snapshot.prototype.setStatus = function (value) {
2194   this._status = value;
2195 };
2196 //==============================================================================
2197 // CLASS DECLARATION
2198 //==============================================================================
2199 /** 
2200  * @class
2201  * @extends net.user1.events.Event
2202  */
2203 net.user1.orbiter.snapshot.SnapshotEvent = function (type,
2204                                             snapshot) {
2205   net.user1.events.Event.call(this, type);
2206   this.snapshot = snapshot;
2207 };
2208   
2209 //==============================================================================
2210 // INHERITANCE
2211 //==============================================================================
2212 net.user1.utils.extend(net.user1.orbiter.snapshot.SnapshotEvent, net.user1.events.Event);
2213 
2214 //==============================================================================
2215 // STATIC VARIABLES
2216 //==============================================================================
2217 
2218 /** @constant */
2219 net.user1.orbiter.snapshot.SnapshotEvent.LOAD = "LOAD";
2220 /** @constant */
2221 net.user1.orbiter.snapshot.SnapshotEvent.STATUS = "STATUS";
2222 
2223 net.user1.orbiter.snapshot.SnapshotEvent.prototype.toString = function () {
2224   return "[object SnapshotEvent]";
2225 };
2226 //==============================================================================
2227 // CLASS DECLARATION
2228 //==============================================================================
2229   /**
2230    * @private 
2231    */  
2232 net.user1.orbiter.SnapshotManager = function (messageManager) {
2233   this.messageManager = messageManager;
2234   this.pendingSnapshots = new Object();
2235   this.requestIDCounter = 0;
2236 };
2237     
2238 //==============================================================================
2239 // UPDATE SNAPSHOT
2240 //==============================================================================    
2241     
2242 net.user1.orbiter.SnapshotManager.prototype.updateSnapshot = function (snapshot) {
2243   var args;
2244   if (snapshot != null) {
2245     if (!snapshot.updateInProgress()) {
2246       this.requestIDCounter++;
2247       snapshot.setUpdateInProgress(true);
2248       snapshot.loaded = false;
2249       snapshot.statusReceived = false;
2250       snapshot.setStatus(null);
2251       this.pendingSnapshots[this.requestIDCounter.toString()] = snapshot;
2252       args = snapshot.args.slice(0);
2253       args.unshift(this.requestIDCounter);
2254       args.unshift(snapshot.method);
2255       this.messageManager.sendUPC.apply(this.messageManager, args);
2256     }
2257   }
2258 };
2259 
2260 //==============================================================================
2261 // RECEIVE SNAPSHOT RESULT
2262 //==============================================================================    
2263 
2264 net.user1.orbiter.SnapshotManager.prototype.receiveSnapshotResult = function (requestID, status) {
2265   var snapshot = this.pendingSnapshots[requestID];
2266   if (snapshot == null) {
2267     throw new Error("[SNAPSHOT_MANAGER] Received snapshot result for unknown "
2268                     + "request ID: [" + requestID + "]");
2269   }
2270   snapshot.setStatus(status);
2271   this.setStatusReceived(snapshot, requestID);
2272 };
2273 
2274 //==============================================================================
2275 // RECEIVE CLIENTCOUNT SNAPSHOT
2276 //==============================================================================    
2277 
2278 net.user1.orbiter.SnapshotManager.prototype.receiveClientCountSnapshot =  function (requestID,
2279                                                                                     numClients) {
2280   var snapshot = this.pendingSnapshots[requestID];
2281   if (snapshot == null) {
2282     throw new Error("[SNAPSHOT_MANAGER] Received client-count snapshot for unknown "
2283                     + "request ID: [" + requestID + "]");
2284   }
2285   snapshot.setCount(numClients);
2286   this.setLoaded(snapshot, requestID);
2287 };
2288 
2289 //==============================================================================
2290 // RECEIVE CLIENT SNAPSHOT
2291 //==============================================================================    
2292 
2293 net.user1.orbiter.SnapshotManager.prototype.receiveClientSnapshot = function (requestID, manifest) {
2294   var snapshot = this.pendingSnapshots[requestID];
2295   if (snapshot == null) {
2296     throw new Error("[SNAPSHOT_MANAGER] Received client snapshot for unknown "
2297                     + "request ID: [" + requestID + "]");
2298   }
2299   snapshot.setManifest(manifest);
2300   this.setLoaded(snapshot, requestID);
2301 };
2302 
2303 //==============================================================================
2304 // RECEIVE ACCOUNT SNAPSHOT
2305 //==============================================================================    
2306 
2307 net.user1.orbiter.SnapshotManager.prototype.receiveAccountSnapshot = function (requestID, manifest) {
2308   var snapshot = this.pendingSnapshots[requestID];
2309   if (snapshot == null) {
2310     throw new Error("[SNAPSHOT_MANAGER] Received account snapshot for unknown "
2311                     + "request ID: [" + requestID + "]");
2312   }
2313   snapshot.setManifest(manifest);
2314   this.setLoaded(snapshot, requestID);
2315 }
2316 
2317 //==============================================================================
2318 // RECEIVE ROOMLIST SNAPSHOT
2319 //==============================================================================    
2320 
2321 net.user1.orbiter.SnapshotManager.prototype.receiveRoomListSnapshot = function (requestID,
2322                                                                                 roomList, 
2323                                                                                 qualifier,
2324                                                                                 recursive) {
2325   var snapshot = this.pendingSnapshots[requestID];
2326   if (snapshot == null) {
2327     throw new Error("[SNAPSHOT_MANAGER] Received roomlist snapshot for unknown "
2328                     + "request ID: [" + requestID + "]");
2329   }
2330   snapshot.setRoomList(roomList);
2331   snapshot.setQualifier(qualifier == "" ? null : qualifier);
2332   snapshot.setRecursive(recursive);
2333   this.setLoaded(snapshot, requestID);
2334 };
2335 
2336 //==============================================================================
2337 // RECEIVE ROOM SNAPSHOT
2338 //==============================================================================    
2339 
2340 net.user1.orbiter.SnapshotManager.prototype.receiveRoomSnapshot = function (requestID, manifest) {
2341   var snapshot = this.pendingSnapshots[requestID];
2342   if (snapshot == null) {
2343     throw new Error("[SNAPSHOT_MANAGER] Received room snapshot for unknown "
2344                     + "request ID: [" + requestID + "]");
2345   }
2346   snapshot.setManifest(manifest);
2347   this.setLoaded(snapshot, requestID);
2348 };
2349 
2350 //==============================================================================
2351 // RECEIVE CLIENTLIST SNAPSHOT
2352 //==============================================================================    
2353 
2354 net.user1.orbiter.SnapshotManager.prototype.receiveClientListSnapshot = function (requestID, clientList) {
2355   var snapshot = this.pendingSnapshots[requestID];
2356   if (snapshot == null) {
2357     throw new Error("[SNAPSHOT_MANAGER] Received clientlist snapshot for unknown "
2358                     + "request ID: [" + requestID + "]");
2359   }
2360   snapshot.setClientList(clientList);
2361   this.setLoaded(snapshot, requestID);
2362 };
2363 
2364 //==============================================================================
2365 // RECEIVE ACCOUNTLIST SNAPSHOT
2366 //==============================================================================    
2367 
2368 net.user1.orbiter.SnapshotManager.prototype.receiveAccountListSnapshot = function (requestID, accountList) {
2369   var snapshot = this.pendingSnapshots[requestID];
2370   if (snapshot == null) {
2371     throw new Error("[SNAPSHOT_MANAGER] Received accountlist snapshot for unknown "
2372                     + "request ID: [" + requestID + "]");
2373   }
2374   snapshot.setAccountList(accountList);
2375   this.setLoaded(snapshot, requestID);
2376 };
2377 
2378 //==============================================================================
2379 // RECEIVE BANNEDLIST SNAPSHOT
2380 //==============================================================================    
2381 
2382 net.user1.orbiter.SnapshotManager.prototype.receiveBannedListSnapshot = function (requestID, bannedList) {
2383   var snapshot = this.pendingSnapshots[requestID];
2384   if (snapshot == null) {
2385     throw new Error("[SNAPSHOT_MANAGER] Received bannedlist snapshot for unknown "
2386                     + "request ID: [" + requestID + "]");
2387   }
2388   snapshot.setBannedList(bannedList);
2389   this.setLoaded(snapshot, requestID);
2390 };
2391 
2392 //==============================================================================
2393 // RECEIVE SERVERMODULELIST SNAPSHOT
2394 //==============================================================================    
2395 
2396 net.user1.orbiter.SnapshotManager.prototype.receiveServerModuleListSnapshot = function (requestID, moduleList) {
2397   var snapshot = this.pendingSnapshots[requestID];
2398   if (snapshot == null) {
2399     throw new Error("[SNAPSHOT_MANAGER] Received server module list snapshot for unknown "
2400                     + "request ID: [" + requestID + "]");
2401   }
2402   snapshot.setModuleList(moduleList);
2403   this.setLoaded(snapshot, requestID);
2404 };
2405 
2406 //==============================================================================
2407 // RECEIVE UPCSTATS SNAPSHOT
2408 //==============================================================================    
2409 
2410 net.user1.orbiter.SnapshotManager.prototype.receiveUPCStatsSnapshot = function (requestID, 
2411                                                                                 totalUPCsProcessed,
2412                                                                                 numUPCsInQueue,
2413                                                                                 lastQueueWaitTime,
2414                                                                                 longestUPCProcesses) {
2415   var snapshot = this.pendingSnapshots[requestID];
2416   if (snapshot == null) {
2417     throw new Error("[SNAPSHOT_MANAGER] Received UPC stats snapshot for unknown "
2418                     + "request ID: [" + requestID + "]");
2419   }
2420   snapshot.setTotalUPCsProcessed(totalUPCsProcessed);
2421   snapshot.setNumUPCsInQueue(numUPCsInQueue);
2422   snapshot.setLastQueueWaitTime(lastQueueWaitTime);
2423   snapshot.setLongestUPCProcesses(longestUPCProcesses);
2424   this.setLoaded(snapshot, requestID);
2425 };
2426 
2427 //==============================================================================
2428 // RECEIVE NODELIST SNAPSHOT
2429 //==============================================================================    
2430 
2431 net.user1.orbiter.SnapshotManager.prototype.receiveNodeListSnapshot = function (requestID, nodeList) {
2432   var snapshot = this.pendingSnapshots[requestID];
2433   if (snapshot == null) {
2434     throw new Error("[SNAPSHOT_MANAGER] Received server node list snapshot for unknown "
2435                     + "request ID: [" + requestID + "]");
2436   }
2437   snapshot.setNodeList(nodeList);
2438   this.setLoaded(snapshot, requestID);
2439 };
2440 
2441 
2442 //==============================================================================
2443 // RECEIVE GATEWAYS SNAPSHOT
2444 //==============================================================================    
2445 
2446 net.user1.orbiter.SnapshotManager.prototype.receiveGatewaysSnapshot = function (requestID, gateways) {
2447   var snapshot = this.pendingSnapshots[requestID];
2448   if (snapshot == null) {
2449     throw new Error("[SNAPSHOT_MANAGER] Received gateways snapshot for unknown "
2450                     + "request ID: [" + requestID + "]");
2451   }
2452   snapshot.setGateways(gateways);
2453   this.setLoaded(snapshot, requestID);
2454 };
2455 
2456 //==============================================================================
2457 // LOADED AND STATUS ASSIGNMENT
2458 //==============================================================================    
2459 
2460 net.user1.orbiter.SnapshotManager.prototype.setLoaded = function (snapshot, requestID) {
2461   snapshot.loaded = true;
2462   if (snapshot.hasStatus == false
2463       || (snapshot.hasStatus == true && snapshot.statusReceived)) {
2464     snapshot.setUpdateInProgress(false);
2465     delete this.pendingSnapshots[requestID];
2466   }
2467   
2468   if (snapshot.hasOwnProperty("onLoad")) {
2469     snapshot["onLoad"]();
2470   }
2471   snapshot.dispatchLoaded();
2472 };
2473     
2474 net.user1.orbiter.SnapshotManager.prototype.setStatusReceived = function (snapshot, requestID) {
2475   if (snapshot.loaded) {
2476     snapshot.setUpdateInProgress(false);
2477     delete this.pendingSnapshots[requestID];
2478   }
2479   snapshot.dispatchStatus();
2480 };
2481 
2482 
2483 
2484 
2485 
2486 
2487 
2488 
2489 
2490 
2491 
2492 
2493 
2494 
2495 
2496 
2497 
2498 
2499 //==============================================================================
2500 // BOOLEAN GROUP TYPE CONSTANTS
2501 //==============================================================================
2502 /** @class */
2503 net.user1.orbiter.filters.BooleanGroupType = new Object();
2504 /** @constant */
2505 net.user1.orbiter.filters.BooleanGroupType.AND = "AND";
2506 /** @constant */
2507 net.user1.orbiter.filters.BooleanGroupType.OR = "OR";
2508 //==============================================================================
2509 // CLASS DECLARATION
2510 //==============================================================================
2511 /** 
2512  * @class
2513  */
2514 net.user1.orbiter.filters.BooleanGroup = function (type) {
2515   this.type = type;
2516   this.comparisons = new Array();
2517 };
2518 
2519 net.user1.orbiter.filters.BooleanGroup.prototype.addComparison = function (comparison) {
2520   if (comparison == null) return;
2521   this.comparisons.push(comparison);
2522 };
2523 
2524 net.user1.orbiter.filters.BooleanGroup.prototype.toXMLString = function () {
2525   var s = type == net.user1.orbiter.filters.BooleanGroupType.AND ? "<and>\n" : "<or>\n";
2526   
2527   var comparison;
2528   for (var i = 0; i < this.comparisons.length; i++) {
2529     comparison = this.comparisons[i];
2530     s += comparison.toXMLString() + "\n";
2531   }
2532   s += this.type == net.user1.orbiter.filters.BooleanGroupType.AND ? "</and>" : "</or>";
2533   return s;
2534 }
2535 //==============================================================================
2536 // CLASS DECLARATION
2537 //==============================================================================
2538 /** 
2539  * @class
2540  * @extends net.user1.events.Event
2541  */
2542 net.user1.orbiter.AccountEvent = function (type, 
2543                                            status,
2544                                            userID,
2545                                            clientID,
2546                                            role) {
2547   net.user1.events.Event.call(this, type);
2548   
2549   this.status = status;
2550   this.userID = userID;
2551   this.clientID = clientID;
2552   this.role = role;
2553   this.account = null;
2554 };
2555 
2556 //==============================================================================
2557 // INHERITANCE
2558 //==============================================================================
2559 net.user1.utils.extend(net.user1.orbiter.AccountEvent, net.user1.events.Event);
2560 
2561 //==============================================================================
2562 // STATIC VARIABLES
2563 //==============================================================================
2564 
2565 /** @constant */
2566 net.user1.orbiter.AccountEvent.LOGIN_RESULT = "LOGIN_RESULT";
2567 /** @constant */
2568 net.user1.orbiter.AccountEvent.LOGIN  = "LOGIN";
2569 /** @constant */
2570 net.user1.orbiter.AccountEvent.LOGOFF_RESULT = "LOGOFF_RESULT";
2571 /** @constant */
2572 net.user1.orbiter.AccountEvent.LOGOFF = "LOGOFF";
2573 /** @constant */
2574 net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT = "CHANGE_PASSWORD_RESULT";
2575 /** @constant */
2576 net.user1.orbiter.AccountEvent.CHANGE_PASSWORD = "CHANGE_PASSWORD";
2577 /** @constant */
2578 net.user1.orbiter.AccountEvent.OBSERVE = "OBSERVE";
2579 /** @constant */
2580 net.user1.orbiter.AccountEvent.STOP_OBSERVING = "STOP_OBSERVING";
2581 /** @constant */
2582 net.user1.orbiter.AccountEvent.OBSERVE_RESULT = "OBSERVE_RESULT";
2583 /** @constant */
2584 net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";    
2585 /** @constant */
2586 net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT = "ADD_ROLE_RESULT";
2587 /** @constant */
2588 net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT = "REMOVE_ROLE_RESULT";
2589 /** @constant */
2590 net.user1.orbiter.AccountEvent.SYNCHRONIZE = "SYNCHRONIZE";
2591 
2592   
2593 //==============================================================================
2594 // INSTANCE METHODS
2595 //==============================================================================    
2596 net.user1.orbiter.AccountEvent.prototype.getAccount = function () {
2597   if (this.target instanceof net.user1.orbiter.AccountManager) {
2598     return this.account;
2599   } else if (this.target instanceof net.user1.orbiter.UserAccount) {
2600     return this.target;
2601   } else {
2602     throw new Error("[AccountEvent] Unexpected target type: " + this.target);
2603   }
2604 };
2605 
2606 /**
2607  * @private
2608  */    
2609 net.user1.orbiter.AccountEvent.prototype.setAccount = function (value) {
2610   this.account = value;
2611 };
2612 
2613 net.user1.orbiter.AccountEvent.prototype.getUserID = function () {
2614   return this.userID;
2615 };
2616 
2617 net.user1.orbiter.AccountEvent.prototype.getRole = function () {
2618   return this.role;
2619 };
2620 
2621 net.user1.orbiter.AccountEvent.prototype.getClientID = function () {
2622   return this.clientID;
2623 };
2624 
2625 net.user1.orbiter.AccountEvent.prototype.getStatus = function () {
2626   return this.status;
2627 };
2628 
2629 net.user1.orbiter.AccountEvent.prototype.toString = function () {
2630   return "[object AccountEvent]";
2631 };
2632 //==============================================================================
2633 // CLASS DECLARATION
2634 //==============================================================================
2635 /** 
2636  * @class
2637  * @extends net.user1.orbiter.snapshot.Snapshot
2638  */
2639 net.user1.orbiter.snapshot.AccountListSnapshot = function () {
2640   // Call superconstructor
2641   net.user1.orbiter.snapshot.Snapshot.call(this);
2642   this.accountList = null;
2643   this.method = net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT;
2644 };
2645 
2646 //==============================================================================
2647 // INHERITANCE
2648 //==============================================================================
2649 net.user1.utils.extend(net.user1.orbiter.snapshot.AccountListSnapshot, net.user1.orbiter.snapshot.Snapshot);
2650     
2651 //==============================================================================
2652 // INSTANCE METHODS
2653 //==============================================================================
2654 /**
2655  * @private
2656  */    
2657 net.user1.orbiter.snapshot.AccountListSnapshot.prototype.setAccountList = function (value) {
2658   this.accountList = value;
2659 }
2660 
2661 net.user1.orbiter.snapshot.AccountListSnapshot.prototype.getAccountList = function () {
2662   if (!this.accountList) {
2663     return null;
2664   }
2665   return this.accountList.slice();
2666 }
2667 //==============================================================================
2668 // CLASS DECLARATION
2669 //==============================================================================
2670 /** @class
2671 
2672 The AccountManager class dispatches the following events:
2673 
2674 <ul class="summary">
2675 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT}</li>
2676 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT}</li>
2677 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT}</li>
2678 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED}</li>
2679 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED}</li>
2680 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF_RESULT}</li>
2681 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>
2682 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN_RESULT}</li>
2683 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>
2684 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD}</li>
2685 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE}</li>
2686 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING}</li>
2687 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT}</li>
2688 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT}</li>
2689 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE_RESULT}</li>
2690 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT}</li>
2691 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE}</li>
2692 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT}</li>
2693 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT}</li>
2694 </ul>
2695 
2696 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2697 
2698     @extends net.user1.events.EventDispatcher
2699 */
2700 net.user1.orbiter.AccountManager = function (log) {
2701   // Call superconstructor
2702   net.user1.events.EventDispatcher.call(this);
2703   
2704   this.watchedAccounts   = new net.user1.orbiter.AccountSet();
2705   this.observedAccounts  = new net.user1.orbiter.AccountSet();
2706   this.accountCache      = new net.user1.utils.LRUCache(10000);
2707   this.log               = log;
2708   this._isWatchingForAccounts = false;
2709   this.accountCache;
2710   this.messageManager;
2711   this.clientManager;
2712   this.roomManager;
2713 };
2714 
2715 //==============================================================================    
2716 // INHERITANCE
2717 //============================================================================== 
2718 net.user1.utils.extend(net.user1.orbiter.AccountManager, net.user1.events.EventDispatcher);
2719 
2720 // =============================================================================
2721 // DEPENDENCIES
2722 // =============================================================================
2723     /**
2724      * @private
2725      */    
2726 net.user1.orbiter.AccountManager.prototype.setMessageManager = function (value) {
2727   this.messageManager = value;
2728 }
2729 
2730 /**
2731  * @private
2732  */    
2733 net.user1.orbiter.AccountManager.prototype.setClientManager = function (value) {
2734   this.clientManager = value;
2735 }
2736 
2737 /**
2738  * @private
2739  */    
2740 net.user1.orbiter.AccountManager.prototype.setRoomManager = function (value) {
2741   this.roomManager = value;
2742 }
2743     
2744 // =============================================================================
2745 // REMOTE ACCOUNT CREATION/REMOVAL
2746 // =============================================================================
2747     
2748 net.user1.orbiter.AccountManager.prototype.createAccount = function (userID, password) {
2749   if (userID == null || userID == "") {
2750     this.log.warn("[ACCOUNT_MANAGER] Create account failed. No userID supplied.");
2751   } else if (password == null) {
2752     this.log.warn("[ACCOUNT_MANAGER] Create account failed. No password supplied.");
2753   } else {
2754     this.messageManager.sendUPC(net.user1.orbiter.UPC.CREATE_ACCOUNT, userID, password);
2755   }
2756 };
2757     
2758 net.user1.orbiter.AccountManager.prototype.removeAccount = function (userID, password) {
2759   if (userID == null || userID == "") {
2760     this.log.warn("[ACCOUNT_MANAGER] Remove account failed. No userID supplied.");
2761   } else {
2762     if (password == null) {
2763       this.log.warn("[ACCOUNT_MANAGER] Remove account: no password supplied." +
2764                             " Removal will fail unless sender is an administrator.");
2765     } 
2766     this.messageManager.sendUPC(net.user1.orbiter.UPC.REMOVE_ACCOUNT, userID, password);
2767   }
2768 }  
2769     
2770 // =============================================================================
2771 // CHANGE PASSWORD
2772 // =============================================================================
2773     
2774 net.user1.orbiter.AccountManager.prototype.changePassword = function (userID, newPassword, oldPassword) {
2775   if (userID == null || userID == "") {
2776     this.log.warn("[ACCOUNT_MANAGER] Change password failed. No userID supplied.");
2777   } else if (newPassword == null || newPassword == "") {
2778     this.log.warn("[ACCOUNT_MANAGER] Change password failed for account [" 
2779                           + userID + "]. No new password supplied.");
2780   } else {
2781     if (oldPassword == null || oldPassword == "") {
2782       this.log.warn("[ACCOUNT_MANAGER] Change account password for account ["
2783                             + userID + "]: no old password supplied."
2784                             + " Operation will fail unless sender is an administrator.");
2785       oldPassword = "";
2786     }
2787     this.messageManager.sendUPC(net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD, userID, oldPassword, newPassword);
2788   }
2789 };
2790     
2791 // =============================================================================
2792 // ADD/REMOVE ROLE
2793 // =============================================================================
2794     
2795 net.user1.orbiter.AccountManager.prototype.addRole = function (userID, role) {
2796   if (userID == null || userID == "") {
2797     this.log.warn("[ACCOUNT_MANAGER] Add role failed. No userID supplied.");
2798   } else if (role == null || role == "") {
2799     this.log.warn("[ACCOUNT_MANAGER] Add role failed for account [" 
2800                           + userID + "]. No role supplied.");
2801   } else {
2802     this.messageManager.sendUPC(net.user1.orbiter.UPC.ADD_ROLE, userID, role);
2803   }
2804 };
2805     
2806 net.user1.orbiter.AccountManager.prototype.removeRole = function (userID, role) {
2807   if (userID == null || userID == "") {
2808     this.log.warn("[ACCOUNT_MANAGER] Remove role failed. No userID supplied.");
2809   } else if (role == null || role == "") {
2810     this.log.warn("[ACCOUNT_MANAGER] Remove role failed for account [" 
2811                           + userID + "]. No role supplied.");
2812   } else {
2813     this.messageManager.sendUPC(net.user1.orbiter.UPC.REMOVE_ROLE, userID, role);
2814   }
2815 };
2816     
2817 // =============================================================================
2818 // LOCAL ACCOUNT CREATION/REMOVAL
2819 // =============================================================================
2820     
2821 /**
2822  * @private
2823  */        
2824 net.user1.orbiter.AccountManager.prototype.requestAccount = function (userID) {
2825   var account;
2826   
2827   if (userID == null || userID == "") {
2828     return null;
2829   } else {
2830     account = this.getAccount(userID);
2831     if (account == null) {
2832       account = new net.user1.orbiter.UserAccount(userID, this.log, this, this.clientManager, this.roomManager);
2833       account.setAttributeManager(new net.user1.orbiter.AttributeManager(account, this.messageManager, this.log));
2834       this.accountCache.put(userID, account);
2835     }
2836     return account;
2837   }
2838 };
2839     
2840 /**
2841  * @private
2842  */            
2843 net.user1.orbiter.AccountManager.prototype.deserializeWatchedAccounts = function (ids) {
2844   var idList = ids.split(Tokens.RS);
2845   var idHash = new net.user1.utils.UDictionary();
2846   var len = idList.length;
2847   
2848   // Generate a hash of clientID keys to dummy values for quick lookup
2849   for (var i = len; --i >= 0;) {
2850     idHash[idList[i]] = 1; 
2851   }
2852   
2853   // Remove all local accounts that are not in the new list from the server
2854   var accountStillExists;
2855   for (var accountID in watchedAccounts.getAll()) {
2856     if (!idHash.hasOwnProperty(accountID)) {
2857       removeWatchedAccount(accountID);
2858     }
2859   }
2860       
2861   // Add accounts from the new list that are not known locally. Do not add
2862   // clients for the accounts because "watch for accounts" does not
2863   // include client knowledge.
2864   if (ids != "") {  // Empty string means no accounts are on the server
2865     for (accountID in idHash) {
2866       if (accountID != "") {
2867         if (!this.watchedAccounts.containsUserID(accountID)) { 
2868           this.addWatchedAccount(this.requestAccount(accountID));
2869         }
2870       } else {
2871         throw new Error("[CORE_MESSAGE_LISTENER] Received empty account id in user list (u127).");
2872       }
2873     }
2874   }
2875   
2876   this.fireSynchronize();
2877 };
2878     
2879 // =============================================================================
2880 // OBSERVED ACCOUNTS
2881 // =============================================================================
2882     
2883 net.user1.orbiter.AccountManager.prototype.observeAccount = function (userID) {
2884   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_ACCOUNT, userID);
2885 };      
2886     
2887 // This method is internal because the developer is expected to access
2888 // stopObserving() through the UserAccount directly. AccountManager's 
2889 // observeAccount() exists only to allow developers to observe a
2890 // user that is currently unknown.
2891 /**
2892  * @private
2893  */        
2894 net.user1.orbiter.AccountManager.prototype.stopObservingAccount = function (userID) {
2895   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT, userID);
2896 };      
2897     
2898 /**
2899  * @private
2900  */        
2901 net.user1.orbiter.AccountManager.prototype.addObservedAccount = function (account) {
2902   this.observedAccounts.add(account);
2903   this.fireObserveAccount(account.getUserID());
2904 }
2905     
2906 /**
2907  * @private
2908  */        
2909 net.user1.orbiter.AccountManager.prototype.removeObservedAccount = function (userID) {
2910   var account = this.observedAccounts.removeByUserID(userID);
2911   this.fireStopObservingAccount(userID);
2912   return account;
2913 }
2914     
2915 /**
2916  * @private
2917  */        
2918 net.user1.orbiter.AccountManager.prototype.removeAllObservedAccounts = function () {
2919   this.observedAccounts.removeAll();
2920 }
2921 
2922 net.user1.orbiter.AccountManager.prototype.isObservingAccount = function (userID) {
2923   return this.observedAccounts.containsUserID(userID);
2924 }
2925     
2926 //==============================================================================
2927 // WATCH FOR ACCOUNTS
2928 //==============================================================================
2929 
2930 net.user1.orbiter.AccountManager.prototype.watchForAccounts = function () {
2931   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS);
2932 }      
2933 
2934 net.user1.orbiter.AccountManager.prototype.stopWatchingForAccounts = function () {
2935   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT);
2936 }      
2937 
2938 net.user1.orbiter.AccountManager.prototype.isWatchingForAccounts = function () {
2939   return this._isWatchingForAccounts;
2940 }
2941     
2942 /**
2943  * @private
2944  */        
2945 net.user1.orbiter.AccountManager.prototype.setIsWatchingForAccounts = function (value) {
2946   this._isWatchingForAccounts = value;
2947 }
2948 
2949 /**
2950  * @private
2951  */        
2952 net.user1.orbiter.AccountManager.prototype.addWatchedAccount = function (account) {
2953   this.watchedAccounts.add(account);
2954   this.fireAccountAdded(account.getUserID(), account);
2955 }
2956 
2957 /**
2958  * @private
2959  */        
2960 net.user1.orbiter.AccountManager.prototype.removeWatchedAccount = function (userID) {
2961   return this.watchedAccounts.removeByUserID(userID);
2962 }
2963 
2964 /**
2965  * @private
2966  */        
2967 net.user1.orbiter.AccountManager.prototype.removeAllWatchedAccounts = function () {
2968   this.watchedAccounts.removeAll();
2969 }
2970 
2971 net.user1.orbiter.AccountManager.prototype.hasWatchedAccount = function (userID) {
2972   return this.watchedAccounts.containsUserID(userID);
2973 }
2974     
2975 // =============================================================================
2976 // CLIENT ACCESS
2977 // =============================================================================
2978         
2979 /**
2980  * @private
2981  */        
2982 net.user1.orbiter.AccountManager.prototype.getClientsForObservedAccounts = function () {
2983   var clients = new Object();
2984   var client;
2985   
2986   var accounts = this.observedAccounts.getAll();
2987   var account;
2988   for (var userID in accounts) {
2989     account = accounts[userID];
2990     client = account.getInternalClient();
2991     if (client != null) {
2992       clients[client.getClientID()] = client;
2993     }
2994   }
2995   
2996   return clients;
2997 }
2998     
2999 // =============================================================================
3000 // LOCAL ACCOUNT ACCESS
3001 // =============================================================================
3002     
3003 net.user1.orbiter.AccountManager.prototype.getAccount = function (userID) {
3004   // Look in account cache
3005   var account = this.accountCache.get(userID);
3006   if (account) {
3007     return account;
3008   }
3009   
3010   // Look in observed accounts
3011   account = this.observedAccounts.getByUserID(userID);
3012   if (account) {
3013     return account;
3014   }
3015   
3016   // Look in watched accounts
3017   account = this.watchedAccounts.getByUserID(userID);
3018   if (account) {
3019     return account;
3020   }
3021 
3022   // Look in connected accounts
3023   var connectedAccounts = new Object();
3024   var clients = this.clientManager.getInternalClients();
3025   var client;
3026   for (var clientID in clients) {
3027     account = clients[clientID].getAccount();
3028     if (account != null && account.getUserID() == userID) {
3029       return account;
3030     }
3031   }
3032   
3033   return null;
3034 };
3035     
3036 net.user1.orbiter.AccountManager.prototype.selfAccount = function () {
3037   return this.clientManager.self().getAccount();
3038 };
3039     
3040 net.user1.orbiter.AccountManager.prototype.getAccounts = function () {
3041   var connectedAccounts = new Object();
3042   var account;
3043   
3044   var clients = this.clientManager.getInternalClients();
3045   var client;
3046   for (var clientID in clients) {
3047     account = client.getAccount();
3048     if (account != null) {
3049       connectedAccounts[account.getUserID()] = account;
3050     }
3051   }
3052   
3053   return net.user1.utils.ObjectUtil.combine(connectedAccounts, this.observedAccounts.getAll(), this.watchedAccounts.getAll());
3054 };
3055     
3056 net.user1.orbiter.AccountManager.prototype.accountIsKnown = function (userID) {
3057   for (var knownUserID in this.getAccounts()) {
3058     if (knownUserID == userID) {
3059       return true;
3060     }
3061   }
3062   return false;
3063 };
3064     
3065 net.user1.orbiter.AccountManager.prototype.getNumAccounts = function () {
3066   return this.getAccounts().length;
3067 };
3068 
3069 net.user1.orbiter.AccountManager.prototype.getNumAccountsOnServer = function () {
3070   return this.watchedAccounts.length();
3071 };
3072     
3073 net.user1.orbiter.AccountManager.prototype.getNumLoggedInAccounts = function () {
3074   var count; 
3075   var account;
3076   var accounts = this.getAccounts();
3077   for (var userID in accounts) {
3078     account = accounts[userID];
3079     if (account.isLoggedIn()) {
3080       count++;
3081     }
3082   }
3083   return count;
3084 };
3085     
3086 // =============================================================================
3087 // LOGIN/LOGOFF
3088 // =============================================================================
3089 
3090 net.user1.orbiter.AccountManager.prototype.login = function (userID, password) {
3091   if (this.clientManager.self().getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN) {
3092     this.log.warn("[ACCOUNT_MANAGER] User [" + userID + "]: Login attempt" 
3093              + " ignored. Already logged in. Current client must logoff before"
3094              + " logging in again.");
3095     this.fireLoginResult(userID, net.user1.orbiter.Status.ERROR);
3096   } else if (userID == null) { 
3097     this.log.warn("[ACCOUNT_MANAGER] Login attempt" 
3098                   + " failed. No userID supplied.");
3099   } else if (password == null) {
3100     this.log.warn("[ACCOUNT_MANAGER] Login attempt failed for user " 
3101                           + "[" + userID + "] failed. No password supplied.");
3102   } else {
3103     this.messageManager.sendUPC(net.user1.orbiter.UPC.LOGIN, userID, password);
3104   }
3105 };
3106     
3107 net.user1.orbiter.AccountManager.prototype.logoff = function (userID, password) {
3108   if (userID == null) {
3109     // Current client
3110     if (this.clientManager.self().getConnectionState() != net.user1.orbiter.ConnectionState.LOGGED_IN) {
3111       this.log.warn("[ACCOUNT_MANAGER] Logoff failed. The current user is not logged in.");
3112     } else {
3113       this.clientManager.self().getAccount().logoff();
3114     }
3115   } else if (userID == "") {
3116     // Invalid client
3117     this.log.warn("[ACCOUNT_MANAGER] Logoff failed. Supplied userID must not be the empty string.");
3118   } else {
3119     // UserID supplied
3120     if (password == null || password == "") {
3121       if (this.clientManager.self().getConnectionState() != net.user1.orbiter.ConnectionState.LOGGED_IN) {
3122         this.log.warn("[ACCOUNT_MANAGER] Logoff: no password supplied." +
3123                       " Operation will fail unless sender is an administrator.");
3124       }
3125       password = "";
3126     }
3127     this.messageManager.sendUPC(net.user1.orbiter.UPC.LOGOFF, userID, password);
3128   }
3129 }  
3130     
3131 //==============================================================================
3132 // EVENT DISPATCHING
3133 //==============================================================================
3134     
3135 /**
3136  * @private
3137  */
3138 net.user1.orbiter.AccountManager.prototype.fireCreateAccountResult = function (userID, status) {
3139   var e = new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT, 
3140                                   userID, this.getAccount(userID), status);
3141   this.dispatchEvent(e);
3142 };
3143 
3144 /**
3145  * @private
3146  */
3147 net.user1.orbiter.AccountManager.prototype.fireRemoveAccountResult = function (userID, status) {
3148   var e = new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT, 
3149                                   userID, this.getAccount(userID), status);
3150   this.dispatchEvent(e);
3151 };
3152 
3153 /**
3154  * @private
3155  */
3156 net.user1.orbiter.AccountManager.prototype.fireChangePasswordResult = function (userID, status) {
3157   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT, 
3158                            status, userID);
3159   e.setAccount(this.getAccount(userID));
3160   this.dispatchEvent(e);
3161 };
3162 
3163 /**
3164  * @private
3165  */
3166 net.user1.orbiter.AccountManager.prototype.fireAccountAdded = function (userID, account) {
3167   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED, 
3168                                        userID, account));
3169 };   
3170 
3171 /**
3172  * @private
3173  */
3174 net.user1.orbiter.AccountManager.prototype.fireAccountRemoved = function (userID, account) {
3175   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED, 
3176                          userID, account));
3177 };
3178 
3179 /**
3180  * @private
3181  */
3182 net.user1.orbiter.AccountManager.prototype.fireLogoffResult = function (userID, status) {
3183   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF_RESULT, 
3184                            status, userID);
3185   e.setAccount(this.getAccount(userID));
3186   this.dispatchEvent(e);
3187 };  
3188 
3189 /**
3190  * @private
3191  */
3192 net.user1.orbiter.AccountManager.prototype.fireLogoff = function (account, clientID) {
3193   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
3194                            net.user1.orbiter.Status.SUCCESS, account.getUserID(), clientID);
3195   e.setAccount(account);
3196   this.dispatchEvent(e);
3197 };
3198 
3199 /**
3200  * @private
3201  */
3202 net.user1.orbiter.AccountManager.prototype.fireLoginResult = function (userID, status) {
3203   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN_RESULT,
3204                            status, userID);
3205   e.setAccount(this.getAccount(userID));
3206   this.dispatchEvent(e);
3207 };
3208 
3209 /**
3210  * @private
3211  */
3212 net.user1.orbiter.AccountManager.prototype.fireLogin = function (account, clientID) {
3213   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
3214                                              net.user1.orbiter.Status.SUCCESS, account.getUserID(), clientID);
3215   e.setAccount(account);
3216   this.dispatchEvent(e);
3217 };
3218 
3219 /**
3220  * @private
3221  */
3222 net.user1.orbiter.AccountManager.prototype.fireChangePassword = function (userID) {
3223   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
3224                                              net.user1.orbiter.Status.SUCCESS, userID);
3225   e.setAccount(this.getAccount(userID));
3226   this.dispatchEvent(e);
3227 };
3228 
3229 /**
3230  * @private
3231  */
3232 net.user1.orbiter.AccountManager.prototype.fireObserveAccount = function (userID) {
3233   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE, 
3234                            null, userID);
3235   e.setAccount(this.getAccount(userID));
3236   this.dispatchEvent(e);
3237 };
3238 
3239 /**
3240  * @private
3241  */
3242 net.user1.orbiter.AccountManager.prototype.fireStopObservingAccount = function (userID) {
3243   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING, 
3244                            null, userID);
3245   e.setAccount(this.getAccount(userID));
3246   this.dispatchEvent(e);
3247 };
3248 
3249 /**
3250  * @private
3251  */
3252 net.user1.orbiter.AccountManager.prototype.fireStopWatchingForAccountsResult = function (status) {
3253   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT, 
3254                      null, null, status));
3255 }    
3256 
3257 /**
3258  * @private
3259  */
3260 net.user1.orbiter.AccountManager.prototype.fireWatchForAccountsResult = function (status) {
3261   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT, 
3262                      null, null, status));
3263 };
3264 
3265 /**
3266  * @private
3267  */
3268 net.user1.orbiter.AccountManager.prototype.fireObserveAccountResult = function (userID, status) {
3269   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE_RESULT, 
3270                            status, userID);
3271   e.setAccount(this.getAccount(userID));
3272   this.dispatchEvent(e);
3273 };
3274 
3275 /**
3276  * @private
3277  */
3278 net.user1.orbiter.AccountManager.prototype.fireStopObservingAccountResult = function (userID, status) {
3279   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT, 
3280                            status, userID);
3281   e.setAccount(this.getAccount(userID));
3282   this.dispatchEvent(e);
3283 };
3284 
3285 /**
3286  * @private
3287  */
3288 net.user1.orbiter.AccountManager.prototype.fireAddRoleResult = function (userID, role, status) {
3289   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT, 
3290                            status, userID, null, role);
3291   e.setAccount(this.getAccount(userID));
3292   this.dispatchEvent(e);
3293 };
3294 
3295 /**
3296  * @private
3297  */
3298 net.user1.orbiter.AccountManager.prototype.fireRemoveRoleResult  = function (userID, role, status) {
3299   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT, 
3300                            status, userID, null, role);
3301   e.setAccount(this.getAccount(userID));
3302   this.dispatchEvent(e);
3303 };
3304 
3305 /**
3306  * @private
3307  */
3308 net.user1.orbiter.AccountManager.prototype.fireSynchronize = function () {
3309   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE));
3310 }    
3311     
3312 //==============================================================================
3313 // CLEANUP AND DISPOSAL
3314 //==============================================================================
3315 
3316 /**
3317  * @private
3318  */    
3319 net.user1.orbiter.AccountManager.prototype.cleanup = function () {
3320   this.log.info("[ACCOUNT_MANAGER] Cleaning resources.");
3321   this.removeAllObservedAccounts();
3322   this.removeAllWatchedAccounts();
3323   this.setIsWatchingForAccounts(false);
3324 };
3325     
3326 //==============================================================================
3327 // CLASS DECLARATION
3328 //==============================================================================
3329 /** @class
3330     @extends net.user1.events.Event
3331 */
3332 net.user1.orbiter.AccountManagerEvent = function (type, 
3333                                                   userID,
3334                                                   account,
3335                                                   status) {
3336   net.user1.events.Event.call(this, type);
3337   
3338   this.account = account;
3339   this.userID = userID;
3340   this.status = status;
3341 };
3342 
3343 //==============================================================================
3344 // INHERITANCE
3345 //==============================================================================
3346 net.user1.utils.extend(net.user1.orbiter.AccountManagerEvent, net.user1.events.Event);
3347 
3348 //==============================================================================
3349 // STATIC VARIABLES
3350 //==============================================================================
3351 
3352 /** @constant */
3353 net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT = "CREATE_ACCOUNT_RESULT";
3354 /** @constant */
3355 net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT = "REMOVE_ACCOUNT_RESULT";
3356 /** @constant */
3357 net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED = "ACCOUNT_ADDED";
3358 /** @constant */
3359 net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED = "ACCOUNT_REMOVED";    
3360 /** @constant */
3361 net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT = "WATCH_FOR_ACCOUNTS_RESULT";
3362 /** @constant */
3363 net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "STOP_WATCHING_FOR_ACCOUNTS_RESULT";
3364 /** @constant */
3365 net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE = "SYNCHRONIZE";    
3366 
3367 //==============================================================================
3368 // INSTANCE METHODS
3369 //==============================================================================   
3370 net.user1.orbiter.AccountManagerEvent.prototype.getStatus = function () {
3371   return this.status;
3372 };
3373    
3374 net.user1.orbiter.AccountManagerEvent.prototype.getUserID = function () {
3375   return this.userID;
3376 };
3377 
3378 net.user1.orbiter.AccountManagerEvent.prototype.getAccount = function () {
3379   return this.account;
3380 };
3381 
3382 net.user1.orbiter.AccountManagerEvent.prototype.toString = function () {
3383   return "[object AccountManagerEvent]";
3384 };
3385 //==============================================================================
3386 // CLASS DECLARATION
3387 //==============================================================================
3388 /**
3389  * @private
3390  */
3391 net.user1.orbiter.AccountSet = function () {
3392   this.accounts = new net.user1.utils.UDictionary();
3393 };
3394     
3395 net.user1.orbiter.AccountSet.prototype.add = function (account) {
3396   this.accounts[account.getUserID()] = account;
3397 };
3398 
3399 net.user1.orbiter.AccountSet.prototype.remove = function (account) {
3400   var account = this.accounts[account.getUserID()]; 
3401   delete this.accounts[account.getUserID()];
3402   return account;
3403 }
3404 
3405 net.user1.orbiter.AccountSet.prototype.removeAll = function () {
3406   this.accounts = new net.user1.utils.UDictionary();
3407 }
3408 
3409 net.user1.orbiter.AccountSet.prototype.removeByUserID = function (userID) {
3410   var account = this.accounts[userID]; 
3411   delete this.accounts[userID];
3412   return account;
3413 }
3414 
3415 net.user1.orbiter.AccountSet.prototype.contains = function (account) {
3416   return this.accounts[account.getUserID()] != null;
3417 }
3418 
3419 net.user1.orbiter.AccountSet.prototype.containsUserID = function (userID) {
3420   if (userID == "" || userID == null) {
3421     return false;
3422   }
3423   return this.getByUserID(userID) != null;
3424 }
3425 
3426 net.user1.orbiter.AccountSet.prototype.getByUserID = function (userID) {
3427   return this.accounts[userID];
3428 }
3429 
3430 net.user1.orbiter.AccountSet.prototype.getByClient = function (client) {
3431   var account;
3432   for (var userID in this.accounts) {
3433     account = this.accounts[userID];
3434     if (account.getInternalClient() == client) {
3435       return account;
3436     }
3437   }
3438   return null;
3439 }
3440 
3441 net.user1.orbiter.AccountSet.prototype.getAll = function () {
3442   return this.accounts;
3443 }
3444 
3445 net.user1.orbiter.AccountSet.prototype.length = function () {
3446   var count;
3447   for (var userID in this.accounts) {
3448     count++;
3449   }
3450   return count;
3451 }
3452 //==============================================================================
3453 // CLASS DECLARATION
3454 //==============================================================================
3455 /** 
3456  * @class
3457  * @extends net.user1.orbiter.snapshot.Snapshot
3458  */
3459 net.user1.orbiter.snapshot.AccountSnapshot = function (userID) {
3460   // Call superconstructor
3461   net.user1.orbiter.snapshot.Snapshot.call(this);
3462   this.manifest = null;
3463   this.method = net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT;
3464   this.args   = [userID];
3465   this.hasStatus = true;
3466 };
3467 
3468 //==============================================================================
3469 // INHERITANCE
3470 //==============================================================================
3471 net.user1.utils.extend(net.user1.orbiter.snapshot.AccountSnapshot, net.user1.orbiter.snapshot.Snapshot);
3472 
3473 //==============================================================================
3474 // INSTANCE METHODS
3475 //==============================================================================
3476 /**
3477  * @private
3478  */    
3479 net.user1.orbiter.snapshot.AccountSnapshot.prototype.setManifest = function (value) {
3480   this.manifest = value;
3481 };
3482 
3483 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getAttribute = function (name, scope) {
3484   if (!this.manifest) {
3485     return null;
3486   }
3487   return this.manifest.persistentAttributes.getAttribute(name, scope);
3488 };
3489 
3490 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getAttributes = function () {
3491   if (!this.manifest) {
3492     return null;
3493   }
3494   return this.manifest.persistentAttributes.getAll();
3495 };
3496 
3497 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getUserID = function () {
3498   if (!this.manifest) {
3499     return null;
3500   }
3501   return this.manifest.userID;
3502 };
3503 //==============================================================================
3504 // CLASS DECLARATION
3505 //==============================================================================
3506 /** 
3507  * @class
3508  * @extends net.user1.orbiter.filters.BooleanGroup
3509  */
3510 net.user1.orbiter.filters.AndGroup = function () {
3511   net.user1.orbiter.filters.BooleanGroup.call(this, net.user1.orbiter.filters.BooleanGroupType.AND);
3512 };
3513 
3514 //==============================================================================
3515 // INHERITANCE
3516 //==============================================================================
3517 net.user1.utils.extend(net.user1.orbiter.filters.AndGroup, net.user1.orbiter.filters.BooleanGroup);
3518 //==============================================================================
3519 // CLASS DECLARATION
3520 //==============================================================================
3521 /** 
3522  * @class
3523  * @extends net.user1.orbiter.filters.AndGroup
3524  */
3525 net.user1.orbiter.filters.Filter = function (filterType) {
3526   net.user1.orbiter.filters.AndGroup.call(this);
3527   this.filterType = filterType;
3528 };
3529 
3530 //==============================================================================
3531 // INHERITANCE
3532 //==============================================================================
3533 net.user1.utils.extend(net.user1.orbiter.filters.Filter, net.user1.orbiter.filters.AndGroup);
3534 
3535 net.user1.orbiter.filters.Filter.prototype.toXMLString = function () {
3536   var s = '<f t="' + this.filterType + '">\n';
3537 
3538   var comparison;
3539   for (var i = 0; i < this.comparisons.length; i++) {
3540     comparison = this.comparisons[i];
3541     s += comparison.toXMLString() + "\n";
3542   }
3543   s += '</f>';
3544   return s;      
3545 };
3546 net.user1.utils.ArrayUtil.combine = function () {
3547   var source = arguments.length == 1 ? arguments[0] : arguments;
3548   var master = [];
3549   
3550   var array;
3551   var element;
3552   for (var i = 0; i < source.length; i++) {
3553     array = source[i];
3554     if (net.user1.utils.ArrayUtil.isArray(array)) {
3555       for (var j = 0; j < array.length; j++) {
3556         element = array[j];
3557         if (net.user1.utils.ArrayUtil.indexOf(master, element) == -1) {
3558           master.push(element);
3559         }
3560       }
3561     }
3562   }
3563   return master;
3564 };
3565 //==============================================================================
3566 // CLASS DECLARATION
3567 //==============================================================================
3568 /** 
3569  * @class
3570  */
3571 net.user1.orbiter.Attribute = function (name,
3572                                         value, 
3573                                         oldValue,
3574                                         scope,
3575                                         byClient) {
3576       /**
3577        * @field
3578        */
3579       this.name = name;
3580       /**
3581        * @field
3582        */
3583       this.value = value;
3584       /**
3585        * @field
3586        */
3587       this.oldValue = oldValue;
3588       /**
3589        * @field
3590        */
3591       this.scope = (scope == net.user1.orbiter.Tokens.GLOBAL_ATTR) || (scope == null) ? null : scope;
3592       /**
3593        * @field
3594        */
3595       this.byClient = byClient;
3596     }
3597 
3598 net.user1.orbiter.Attribute.prototype.toString = function () {
3599   return "Attribute: " + (this.scope == null ? "" : this.scope + ".") + this.name + " = " + this.value + "." + " Old value: " + this.oldValue;
3600 };
3601 //==============================================================================
3602 // CLASS DECLARATION
3603 //==============================================================================
3604 /** @class
3605 
3606 The AttributeCollection class dispatches the following events:
3607 
3608 <ul class="summary">
3609 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li>
3610 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li>
3611 </ul>
3612 
3613 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
3614 
3615     @extends net.user1.events.EventDispatcher
3616 */
3617 net.user1.orbiter.AttributeCollection = function () {
3618   // Call superconstructor
3619   net.user1.events.EventDispatcher.call(this);
3620   
3621   this.attributes = new Object();
3622 };
3623 
3624 //==============================================================================    
3625 // INHERITANCE
3626 //============================================================================== 
3627 net.user1.utils.extend(net.user1.orbiter.AttributeCollection, net.user1.events.EventDispatcher);
3628 
3629 // =============================================================================
3630 // ATTRIBUTE ASSIGNMENT
3631 // =============================================================================
3632 /**
3633  * @private
3634  */    
3635 net.user1.orbiter.AttributeCollection.prototype.setAttribute = function (name, value, scope, byClient) {
3636   var scopeExists
3637   var attrExists;
3638   var oldVal;
3639  
3640   // null scope means global scope
3641   scope = scope == null ? net.user1.orbiter.Tokens.GLOBAL_ATTR : scope;
3642   // Check if the scope and attr exist already
3643   scopeExists =  this.attributes.hasOwnProperty(scope);
3644   attrExists  = scopeExists ? this.attributes[scope].hasOwnProperty(name) : false;
3645   
3646   // Find old value, if any
3647   if (attrExists) {
3648     oldVal = this.attributes[scope][name];
3649     if (oldVal == value) {
3650       // Attribute value is unchanged, so abort
3651       return false;
3652     }
3653   }
3654 
3655   // Make the scope record if necessary
3656   if (!scopeExists) {
3657     this.attributes[scope] = new Object();
3658   }
3659   
3660   // Set the attribute value
3661   this.attributes[scope][name] = value;
3662   
3663   // Notify listeners
3664   this.fireUpdateAttribute(name, value, scope, oldVal, byClient);
3665   
3666   return true;
3667 };
3668 
3669 // =============================================================================
3670 // ATTRIBUTE DELETION
3671 // =============================================================================
3672 /**
3673  * @private
3674  */
3675 net.user1.orbiter.AttributeCollection.prototype.deleteAttribute = function (name, scope, byClient) {
3676   var lastAttr = true;
3677   var value;
3678 
3679   // If the attribute exists...
3680   if (this.attributes.hasOwnProperty(scope) 
3681       && this.attributes[scope].hasOwnProperty(name)) {
3682     value = this.attributes[scope][name];
3683     delete this.attributes[scope][name];
3684     // Check if this is the last attribute. If it is, remove the room scope object.
3685     for (var p in this.attributes[scope]) {
3686       lastAttr = false;
3687       break;
3688     }
3689     if (lastAttr) {
3690       delete this.attributes[scope];
3691     }
3692     
3693     // Notify listeners
3694     this.fireDeleteAttribute(name, value, scope, byClient);
3695     return true;
3696   }
3697   return false;
3698 };
3699 
3700 /**
3701  * @private
3702  */
3703 net.user1.orbiter.AttributeCollection.prototype.clear = function () {
3704   this.attributes = new Object();
3705 };
3706 
3707 // =============================================================================
3708 // ATTRIBUTE RETRIEVAL
3709 // =============================================================================
3710 
3711 net.user1.orbiter.AttributeCollection.prototype.getByScope = function (scope) {
3712   var obj = new Object();
3713 
3714   if (scope == null) {
3715     for (var attrscope in this.attributes) {
3716       obj[attrscope] = new Object();
3717       for (var attrname in this.attributes[attrscope]) {
3718         obj[attrscope][attrname] = this.attributes[attrscope][attrname];
3719       }
3720     }
3721   } else {
3722     for (var name in this.attributes[scope]) {
3723       obj[name] = this.attributes[scope][name];
3724     }
3725   }
3726 
3727   return obj;
3728 };
3729 
3730 net.user1.orbiter.AttributeCollection.prototype.getAttributesNamesForScope = function (scope) {
3731   var names = new Array();
3732   for (var name in this.attributes[scope]) {
3733     names.push(name);
3734   }
3735   return names;
3736 };
3737 
3738 net.user1.orbiter.AttributeCollection.prototype.getAll = function () {
3739   var attrs = new Object();
3740   for (var attrScope in this.attributes) {
3741     for (var attrName in this.attributes[attrScope]) {
3742       attrs[attrScope == net.user1.orbiter.Tokens.GLOBAL_ATTR ? attrName : (attrScope + "." + attrName)] = this.attributes[attrScope][attrName];
3743     }
3744   }
3745   return attrs;
3746 }  
3747 
3748 net.user1.orbiter.AttributeCollection.prototype.getAttribute = function (attrName, attrScope) {
3749   // Use the global scope when no scope is specified
3750   if (attrScope == null) {
3751     attrScope = net.user1.orbiter.Tokens.GLOBAL_ATTR;
3752   }
3753   
3754   // Find and return the attribute.
3755   if (this.attributes.hasOwnProperty(attrScope) 
3756       && this.attributes[attrScope].hasOwnProperty(attrName)) {
3757     return this.attributes[attrScope][attrName];
3758   } else {
3759     // No attribute was found, so quit.
3760     return null;
3761   }
3762 };
3763 
3764 net.user1.orbiter.AttributeCollection.prototype.getScopes = function () {
3765   var scopes = new Array();
3766   for (var scope in this.attributes) {
3767     scopes.push(scope);
3768   }
3769   return scopes;
3770 };
3771 
3772 // =============================================================================
3773 // COLLECTION INSPECTION
3774 // =============================================================================
3775 
3776 net.user1.orbiter.AttributeCollection.prototype.contains = function (name, scope) {
3777   return this.attributes.hasOwnProperty(scope) ? this.attributes[scope].hasOwnProperty(name) : false;
3778 };
3779 
3780 // =============================================================================
3781 // MERGING
3782 // =============================================================================
3783 
3784 /**
3785  * @private
3786  */    
3787 net.user1.orbiter.AttributeCollection.prototype.add = function (collection) {
3788   var scopes = collection.getScopes();
3789   var scope;
3790   
3791   var names;
3792   var name;
3793   
3794   for (var i = 0; i <= scopes.length; i++) {
3795     scope = scopes[i];
3796     names = collection.getAttributesNamesForScope(scope);
3797     for (var j = 0; j < names.length; j++) {
3798       name = names[j];
3799       this.setAttribute(name, collection.getAttribute(name, scope), scope);
3800     }
3801   }
3802 };
3803 
3804 /**
3805  * @private
3806  */    
3807 net.user1.orbiter.AttributeCollection.prototype.synchronizeScope = function (scope,
3808                                                                              collection) {
3809   // Delete all existing attributes that are not in the new collection
3810   var names = this.getAttributesNamesForScope(scope);
3811   var name;
3812   
3813   for (var i = 0; i < names.length; i++) {
3814     name = names[i];
3815     if (!collection.contains(name, scope)) {
3816       this.deleteAttribute(name, scope);
3817     }
3818   }
3819   
3820   // Set all new attributes (unchanged attributes are ignored)
3821   var names = collection.getAttributesNamesForScope(scope);
3822   for (i = 0; i < names.length; i++) {
3823     name = names[i];
3824     this.setAttribute(name, collection.getAttribute(name, scope), scope);
3825   } 
3826 };
3827 
3828 // =============================================================================
3829 // EVENT DISPATCHING
3830 // =============================================================================
3831 /**
3832  * @private
3833  */
3834 net.user1.orbiter.AttributeCollection.prototype.fireUpdateAttribute = function (attrName, 
3835                                                                                 attrVal, 
3836                                                                                 attrScope,
3837                                                                                 oldVal,
3838                                                                                 byClient) {
3839   var changedAttr = new net.user1.orbiter.Attribute(attrName, attrVal, oldVal, attrScope, byClient);
3840   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.UPDATE,
3841                                                changedAttr);
3842   this.dispatchEvent(e);
3843 };
3844 
3845 /**
3846  * @private
3847  */
3848 net.user1.orbiter.AttributeCollection.prototype.fireDeleteAttribute = function (attrName,
3849                                                                                 attrValue,
3850                                                                                 attrScope,
3851                                                                                 byClient) {
3852   var changedAttr = new net.user1.orbiter.Attribute(attrName, null, attrValue, attrScope, byClient);
3853   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.DELETE,
3854                                                changedAttr);
3855   this.dispatchEvent(e);
3856 };
3857 //==============================================================================
3858 // CLASS DECLARATION
3859 //==============================================================================
3860 /** 
3861  * @class
3862  */
3863 net.user1.orbiter.filters.AttributeComparison = function (name,
3864                                                           value,
3865                                                           compareType) {
3866   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
3867     throw new Error("Invalid attribute name specified for AttributeComparison: "
3868                     + name);
3869   }                                           
3870   this.name = name;
3871   this.value = value;
3872   this.compareType = compareType;
3873 };
3874     
3875 net.user1.orbiter.filters.AttributeComparison.prototype.toXMLString = function () {
3876   return '<a c="' + this.compareType + '"><n><![CDATA[' + this.name + ']]></n><v><![CDATA[' + this.value.toString() + ']]></v></a>';
3877 };
3878 //==============================================================================
3879 // CLASS DECLARATION
3880 //==============================================================================
3881 /** @class
3882     @extends net.user1.events.Event
3883 */
3884 net.user1.orbiter.AttributeEvent = function (type, 
3885                                              changedAttr,
3886                                              status) {
3887   net.user1.events.Event.call(this, type);
3888   
3889   this.changedAttr = changedAttr;
3890   this.status = status;
3891 };
3892 
3893 //==============================================================================
3894 // INHERITANCE
3895 //==============================================================================
3896 net.user1.utils.extend(net.user1.orbiter.AttributeEvent, net.user1.events.Event);
3897 
3898 //==============================================================================
3899 // STATIC VARIABLES
3900 //==============================================================================
3901 
3902 /** @constant */
3903 net.user1.orbiter.AttributeEvent.UPDATE = "UPDATE";
3904 /** @constant */
3905 net.user1.orbiter.AttributeEvent.DELETE = "DELETE";
3906 /** @constant */
3907 net.user1.orbiter.AttributeEvent.DELETE_RESULT = "DELETE_RESULT";
3908 /** @constant */
3909 net.user1.orbiter.AttributeEvent.SET_RESULT = "SET_RESULT";
3910 
3911 //==============================================================================
3912 // INSTANCE METHODS
3913 //==============================================================================   
3914 net.user1.orbiter.AttributeEvent.prototype.getChangedAttr = function () {
3915   return this.changedAttr;
3916 }
3917 
3918 net.user1.orbiter.AttributeEvent.prototype.getStatus = function () {
3919   return this.status;
3920 }
3921 
3922 net.user1.orbiter.AttributeEvent.prototype.toString = function () {
3923   return "[object AttributeEvent]";
3924 }  
3925 //==============================================================================
3926 // CLASS DECLARATION
3927 //==============================================================================
3928 /** 
3929  * @class
3930  * @extends net.user1.orbiter.filters.Filter
3931  */
3932 net.user1.orbiter.filters.AttributeFilter = function () {
3933   net.user1.orbiter.filters.Filter.call(this, "A");
3934 };
3935 
3936 
3937 //==============================================================================
3938 // INHERITANCE
3939 //==============================================================================
3940 net.user1.utils.extend(net.user1.orbiter.filters.AttributeFilter, net.user1.orbiter.filters.Filter);
3941 //==============================================================================
3942 // CLASS DECLARATION
3943 //==============================================================================
3944 /**
3945 * @private
3946 */
3947 net.user1.orbiter.AttributeManager = function (owner,
3948                                                messageManager,
3949                                                log) {
3950   // Call superconstructor
3951   net.user1.events.EventDispatcher.call(this);
3952   
3953   this.attributes = null;
3954   this.owner = owner;
3955   this.messageManager = messageManager;
3956   this.log = log;
3957   this.setAttributeCollection(new net.user1.orbiter.AttributeCollection());
3958 };
3959 
3960 //==============================================================================    
3961 // INHERITANCE
3962 //============================================================================== 
3963 net.user1.utils.extend(net.user1.orbiter.AttributeManager, net.user1.events.EventDispatcher);
3964 
3965 //==============================================================================
3966 // DEPENDENCIES
3967 //==============================================================================
3968 
3969 net.user1.orbiter.AttributeManager.prototype.getAttributeCollection = function () {
3970   return this.attributes;
3971 };
3972 
3973 net.user1.orbiter.AttributeManager.prototype.setAttributeCollection = function (value) {
3974   this.unregisterAttributeListeners();
3975   this.attributes = value;
3976   this.registerAttributeListeners();
3977 };
3978 
3979 //==============================================================================
3980 // SERVER-SIDE ASSIGNMENT
3981 //==============================================================================
3982 
3983 net.user1.orbiter.AttributeManager.prototype.setAttribute = function (setRequest) {
3984   this.messageManager.sendUPCObject(setRequest);
3985 }
3986 
3987 //==============================================================================
3988 // SERVER-SIDE DELETION
3989 //==============================================================================
3990 
3991 net.user1.orbiter.AttributeManager.prototype.deleteAttribute = function (deleteRequest) {
3992   this.messageManager.sendUPCObject(deleteRequest);
3993 }
3994 
3995 //==============================================================================
3996 // LOCAL RETRIEVAL
3997 //==============================================================================
3998 
3999 net.user1.orbiter.AttributeManager.prototype.getAttribute = function (attrName, attrScope) {
4000   // Quit if there are no attrbutes.
4001   if (this.attributes == null) {
4002     return null;
4003   } else {
4004     return this.attributes.getAttribute(attrName, attrScope);
4005   }
4006 };
4007 
4008 net.user1.orbiter.AttributeManager.prototype.getAttributes = function () {
4009   return this.attributes.getAll();
4010 }  
4011 
4012 net.user1.orbiter.AttributeManager.prototype.getAttributesByScope = function (scope) {
4013   return this.attributes.getByScope(scope);
4014 };
4015 
4016 //==============================================================================
4017 // LOCAL ASSIGNMENT
4018 //==============================================================================
4019 
4020 /**
4021  * @private
4022  */        
4023 net.user1.orbiter.AttributeManager.prototype.setAttributeLocal = function (attrName, 
4024                                                                            attrVal,
4025                                                                            attrScope,
4026                                                                            byClient) {
4027   var changed = this.attributes.setAttribute(attrName, attrVal, attrScope, byClient);
4028   if (!changed) {
4029     this.log.info(this.owner + " New attribute value for [" + attrName + "] matches old value. Not changed.");
4030   }
4031 };
4032 
4033 //==============================================================================
4034 // LOCAL REMOVAL
4035 //==============================================================================
4036   
4037 /**
4038  * @private
4039  */        
4040 net.user1.orbiter.AttributeManager.prototype.removeAttributeLocal = function (attrName,
4041                                                                               attrScope,
4042                                                                               byClient) {
4043   var deleted = this.attributes.deleteAttribute(attrName, attrScope, byClient);
4044   if (!deleted) {
4045     this.log.info(owner + " Delete attribute failed for [" + attrName + "]. No such attribute.");
4046   }
4047 };
4048   
4049 /**
4050  * @private
4051  */        
4052 net.user1.orbiter.AttributeManager.prototype.removeAll = function () {
4053   this.attributes.clear();
4054 }
4055 
4056 //==============================================================================
4057 // EVENT REGISTRATION
4058 //==============================================================================
4059 
4060 net.user1.orbiter.AttributeManager.prototype.registerAttributeListeners = function () {
4061   if (this.attributes != null) {
4062     // Can't use migrateListeners() here because we need to specify the listener priority (int.MAX_VALUE)
4063     this.attributes.addEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateAttributeListener, this, net.user1.utils.integer.MAX_VALUE);
4064     this.attributes.addEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteAttributeListener, this, net.user1.utils.integer.MAX_VALUE);
4065   }
4066 };
4067 
4068 net.user1.orbiter.AttributeManager.prototype.unregisterAttributeListeners = function () {
4069   if (this.attributes != null) {
4070     this.attributes.removeEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateAttributeListener, this);
4071     this.attributes.removeEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteAttributeListener, this);
4072   }
4073 }
4074 
4075 //==============================================================================
4076 // EVENT LISTENERS
4077 //==============================================================================
4078 
4079 net.user1.orbiter.AttributeManager.prototype.updateAttributeListener = function (e) {
4080   var attr = e.getChangedAttr();
4081   
4082   this.log.info(this.owner + " Setting attribute [" 
4083                 + ((attr.scope == null) ? "" : attr.scope + ".") 
4084                 + attr.name + "]. New value: [" + attr.value + "]. Old value: [" 
4085                 + attr.oldValue + "].");
4086   this.owner.dispatchEvent(e);
4087 };
4088 
4089 net.user1.orbiter.AttributeManager.prototype.deleteAttributeListener = function (e) {
4090   this.owner.dispatchEvent(e);
4091 }
4092 
4093 //==============================================================================
4094 // EVENT DISPATCHING
4095 //==============================================================================
4096 
4097 /**
4098  * @private
4099  */        
4100 net.user1.orbiter.AttributeManager.prototype.fireSetAttributeResult = function (attrName,
4101                                                                                 attrScope,
4102                                                                                 status) {
4103   var attr = new net.user1.orbiter.Attribute(attrName, null, null, attrScope);
4104 
4105   // Trigger event on listeners.
4106   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.SET_RESULT,
4107                                                attr, status);
4108   this.owner.dispatchEvent(e);
4109 };
4110 
4111 /**
4112  * @private
4113  */
4114 net.user1.orbiter.AttributeManager.prototype.fireDeleteAttributeResult = function (attrName,
4115                                                                                    attrScope, 
4116                                                                                    status) {
4117   var attr = new net.user1.orbiter.Attribute(attrName, null, null, attrScope);
4118   
4119   // Trigger event on listeners.
4120   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.DELETE_RESULT,
4121                                                attr, status);
4122   this.owner.dispatchEvent(e);
4123 };
4124 
4125 // =============================================================================
4126 // DISPOSAL
4127 // =============================================================================
4128 
4129 /**
4130  * @private
4131  */
4132 net.user1.orbiter.AttributeManager.prototype.dispose = function () {
4133   this.messageManager = null;  
4134   this.attributes = null;
4135   this.owner = null;
4136   this.log = null;
4137 };
4138 //==============================================================================
4139 //  ATTRIBUTE_OPTIONS CONSTANTS
4140 //==============================================================================
4141 /** @class
4142     @private */
4143 net.user1.orbiter.AttributeOptions = new Object();
4144 
4145 /** @private */
4146 net.user1.orbiter.AttributeOptions.FLAG_SHARED     = 1 << 2;
4147 /** @private */
4148 net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT = 1 << 3;
4149 /** @private */
4150 net.user1.orbiter.AttributeOptions.FLAG_IMMUTABLE  = 1 << 5;
4151 /** @private */
4152 net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   = 1 << 8;
4153 //==============================================================================
4154 // CLASS DECLARATION
4155 //==============================================================================
4156 /** 
4157  * @class
4158  * @extends net.user1.orbiter.snapshot.Snapshot
4159  */
4160 net.user1.orbiter.snapshot.BannedListSnapshot = function () {
4161   // Call superconstructor
4162   net.user1.orbiter.snapshot.Snapshot.call(this);
4163   this.bannedList = null;
4164   this.method = net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT;
4165 }
4166 
4167 //==============================================================================
4168 // INHERITANCE
4169 //==============================================================================
4170 net.user1.utils.extend(net.user1.orbiter.snapshot.BannedListSnapshot, net.user1.orbiter.snapshot.Snapshot);
4171 
4172 //==============================================================================
4173 // INSTANCE METHODS
4174 //==============================================================================
4175 /**
4176  * @private
4177  */    
4178 net.user1.orbiter.snapshot.BannedListSnapshot.prototype.setBannedList = function (value) {
4179   this.bannedList = value;
4180 };
4181 
4182 net.user1.orbiter.snapshot.BannedListSnapshot.prototype.getBannedList = function () {
4183   if (!this.bannedList) {
4184     return null;
4185   }
4186   return this.bannedList.slice();
4187 };
4188 //==============================================================================
4189 // CLASS DECLARATION
4190 //==============================================================================
4191 /** 
4192  * @class
4193  */
4194 net.user1.utils.CacheNode = function () {
4195   /** @field */
4196   this.next;
4197   /** @field */
4198   this.prev;
4199   /** @field */
4200   this.key;
4201   /** @field */
4202   this.value;
4203 };
4204 //==============================================================================
4205 // CLASS DECLARATION
4206 //==============================================================================
4207 /** @class
4208 
4209 The Client class dispatches the following events:
4210 
4211 <ul class="summary">
4212   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.JOIN_ROOM}</li> 
4213   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.LEAVE_ROOM}</li> 
4214   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_ROOM}</li> 
4215   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM}</li> 
4216   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE}</li> 
4217   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING}</li> 
4218   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_RESULT}</li> 
4219   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT}</li> 
4220   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>    
4221   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>    
4222   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.SYNCHRONIZE}</li> 
4223   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li> 
4224   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li> 
4225   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.SET_RESULT}</li> 
4226   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE_RESULT}</li> 
4227 </ul>
4228 
4229 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
4230 
4231     @extends net.user1.events.EventDispatcher
4232 */
4233 net.user1.orbiter.Client = function (clientID, 
4234                                      clientManager,
4235                                      messageManager,
4236                                      roomManager,
4237                                      connectionManager,
4238                                      server,
4239                                      log) {
4240   // Call superconstructor
4241   net.user1.events.EventDispatcher.call(this);
4242 
4243   this.clientID = "";
4244   this._isSelf = false;
4245   this.account = null;
4246   this.disposed = false;
4247   
4248   this.messageManager    = messageManager;
4249   this.clientManager     = clientManager;
4250   this.roomManager       = roomManager;
4251   this.connectionManager = connectionManager;
4252   this.server            = server;
4253   this.log               = log;
4254   this.occupiedRoomIDs   = new Array();
4255   this.observedRoomIDs   = new Array();
4256   this.customClients     = new Object();
4257   this.attributeManager  = new net.user1.orbiter.AttributeManager(this, this.messageManager, this.log);
4258   this.connectionState   = net.user1.orbiter.ConnectionState.UNKNOWN;
4259 
4260   this.setClientID(clientID);
4261 };
4262 
4263 //==============================================================================    
4264 // INHERITANCE
4265 //============================================================================== 
4266 net.user1.utils.extend(net.user1.orbiter.Client, net.user1.events.EventDispatcher);
4267     
4268 //==============================================================================
4269 // STATIC VARIABLES
4270 //==============================================================================
4271 
4272 /** @private */    
4273 net.user1.orbiter.Client.FLAG_ADMIN = 1 << 2;
4274     
4275 // =============================================================================
4276 // CLIENT ID
4277 // =============================================================================
4278 net.user1.orbiter.Client.prototype.getClientID = function () {
4279   return this.clientID;
4280 };
4281 
4282 /**
4283  * @private
4284  */
4285 net.user1.orbiter.Client.prototype.setClientID = function (id) {
4286   if (this.clientID != id) {
4287     this.clientID = id;
4288   }
4289 };
4290 
4291 net.user1.orbiter.Client.prototype.isSelf = function () {
4292   return this._isSelf;
4293 };
4294 
4295 /**
4296  * @private
4297  */
4298 net.user1.orbiter.Client.prototype.setIsSelf = function () {
4299   this._isSelf = true;
4300 };
4301 
4302 // =============================================================================
4303 // CONNECTION STATUS
4304 // =============================================================================
4305 
4306 net.user1.orbiter.Client.prototype.getConnectionState = function () {
4307   if (this.isSelf()) {
4308     if (this.disposed
4309         || this.clientManager.getInternalClient(this.getClientID()) == null) {
4310       return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
4311     } else {
4312       return this.account != null ? this.account.getConnectionState() : this.connectionManager.getConnectionState();
4313     }
4314   } else {
4315     if (this.connectionState != net.user1.orbiter.ConnectionState.UNKNOWN) {
4316       return this.connectionState;
4317     } else if (this.disposed
4318                || this.clientManager.getInternalClient(this.getClientID()) == null) {
4319       return net.user1.orbiter.ConnectionState.UNKNOWN;
4320     } else {
4321       return this.account != null ? this.account.getConnectionState() : net.user1.orbiter.ConnectionState.READY;
4322     }
4323   }
4324 };
4325 
4326 // Normally, this client's connection state is not assigned directly; it 
4327 // it is deduced within getConnectionState(). But when Union
4328 // sends a u103, we know that this client has definitely disconnected from 
4329 // the server, and this client object will never be reused, so CoreMessageListener
4330 // permanently assigns its connection state to NOT_CONNECTED.
4331 net.user1.orbiter.Client.prototype.setConnectionState = function (newState) {
4332   this.connectionState = newState;
4333 };
4334 
4335 // =============================================================================
4336 // ROLES
4337 // =============================================================================
4338 net.user1.orbiter.Client.prototype.isAdmin = function () {
4339   var rolesAttr = this.getAttribute(Tokens.ROLES_ATTR);
4340   var roles;
4341   if (rolesAttr != null) {
4342     return parseInt(rolesAttr) & net.user1.orbiter.Client.FLAG_ADMIN;
4343   } else {
4344     this.log.warn("[" + this.toString() + "] Could not determine admin status because the client is not synchronized.");
4345     return false;
4346   }
4347 };
4348 
4349 // =============================================================================
4350 // OBSERVATION
4351 // =============================================================================
4352 
4353 net.user1.orbiter.Client.prototype.observe = function () {
4354   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_CLIENT, this.clientID);
4355 };
4356 
4357 net.user1.orbiter.Client.prototype.stopObserving = function () {
4358   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT, this.clientID);
4359 };
4360 
4361 // =============================================================================
4362 // KICK / BAN
4363 // =============================================================================
4364 
4365 net.user1.orbiter.Client.prototype.kick = function () {
4366   if (this.getClientID() == null) {
4367     this.log.warn(this + " Kick attempt failed. Client not currently connected.");
4368   }
4369   this.messageManager.sendUPC(net.user1.orbiter.UPC.KICK_CLIENT, getClientID());
4370 };
4371 
4372 net.user1.orbiter.Client.prototype.ban = function (duration, reason) {
4373   if (this.getClientID() == null) {
4374     this.log.warn(this + " Ban attempt failed. Client not currently connected.");
4375   }
4376   this.messageManager.sendUPC(net.user1.orbiter.UPC.BAN, null, getClientID(), duration.toString(), reason);
4377 };
4378 
4379 // =============================================================================
4380 // CUSTOM CLASS MANAGEMENT
4381 // =============================================================================
4382 
4383 net.user1.orbiter.Client.prototype.setClientClass = function (scope, 
4384                                                               clientClass) {
4385   var fallbackClasses = Array.prototype.slice.call(arguments).slice(2);
4386   if (!this.isSelf()) {
4387     throw new Error("Custom client class assignment failed for : "
4388                     + clientClass + ". A custom" 
4389                     + " class can be set for the current client ("
4390                     + " i.e., ClientManager.self()) only.");
4391   }
4392   
4393   fallbackClasses.unshift(clientClass);
4394   var classList = fallbackClasses.join(" ");
4395   setAttribute(Tokens.CUSTOM_CLASS_ATTR, classList, scope);
4396 };
4397 
4398 /**
4399  * @private
4400  */    
4401 net.user1.orbiter.Client.prototype.getCustomClient = function (scope) {
4402   var customClient;
4403 
4404   // If the custom client already exists for the specified scope, return it.
4405   customClient = this.customClients[scope];
4406   if (customClient != null) {
4407     return customClient;
4408   }
4409   
4410   // Look for a custom class for the given scope, and create a custom client
4411   if (scope == null) {
4412     return this.setGlobalCustomClient();
4413   } else {
4414     return this.setCustomClientForScope(scope);
4415   }
4416 };
4417 
4418 /**
4419  * @private
4420  */    
4421 net.user1.orbiter.Client.prototype.setGlobalCustomClient = function () {
4422   var defaultClientClass;
4423   var globalDefaultClientClass;
4424   var customClient;
4425   
4426   // If this client has a default custom client class, use it
4427   defaultClientClass = this.getClientClass(null);
4428   if (defaultClientClass != null) {
4429     return this.createCustomClient(defaultClientClass, null);
4430   }          
4431   
4432   // No global class was set on the client, so check for a system-wide default
4433   globalDefaultClientClass = this.clientManager.getDefaultClientClass();
4434   if (globalDefaultClientClass == null) {
4435     // No global custom client class exists
4436     return null;
4437   } else {
4438     // Global default class exists
4439     return this.createCustomClient(globalDefaultClientClass, null);
4440   }
4441 };
4442 
4443 /**
4444  * @private
4445  */    
4446 net.user1.orbiter.Client.prototype.setCustomClientForScope = function (scope) {
4447   var theRoom;
4448   var clientClass;
4449   var roomDefaultClientClass;
4450   var globalDefaultClientClass;
4451   
4452   // If this client has a default custom client class, use it
4453   clientClass = this.getClientClass(scope);
4454   if (clientClass != null) {
4455     return this.createCustomClient(clientClass, scope);
4456   }          
4457   
4458   // No class was set on the client for the scope, so check for a room default
4459   theRoom = this.roomManager.getRoom(scope);
4460   if (theRoom != null) {
4461     roomDefaultClientClass = theRoom.getDefaultClientClass();
4462     if (roomDefaultClientClass != null) {
4463       return this.createCustomClient(roomDefaultClientClass, scope);
4464     }
4465   }
4466   
4467   // No class was set on the room for the scope, so check for a system-wide default
4468   // If a custom global client already exists, return it.
4469   var customClient = this.customClients[null];
4470   if (customClient != null) {
4471     return customClient;
4472   } else {
4473     globalDefaultClientClass = this.clientManager.getDefaultClientClass();
4474     if (globalDefaultClientClass == null) {
4475       // No global custom client class exists
4476       return null;
4477     } else {
4478       // Global default class exists
4479       return this.createCustomClient(globalDefaultClientClass, null);
4480     }
4481   }
4482 };
4483 
4484 /**
4485  * @private
4486  */    
4487 net.user1.orbiter.Client.prototype.getClientClass = function (scope) {
4488   var clientClassNames = this.getAttribute(net.user1.orbiter.Tokens.CUSTOM_CLASS_ATTR, scope);
4489   var clientClassList;
4490 
4491   // Convert the custom class names to an array for processing
4492   if (clientClassNames != null) {
4493     clientClassList = clientClassNames.split(" ");
4494   }
4495 
4496   // Search for a matching class definition. The first definition that's 
4497   // found is returned.
4498   var className;
4499   if (clientClassList != null) {
4500     for (var i = 0; i < clientClassList.length; i++) {
4501       try {
4502         var theClass = net.user1.utils.resolveMemberExpression(className);
4503         if (!theClass instanceof Function) {
4504           this.log.debug(this.toString() + ": Definition for client class [" + className + "] is not a constructor function.");
4505           continue;
4506         }
4507         return theClass;
4508       } catch (e) {
4509         this.log.debug(this.toString() + ": No definition found for client class [" + className + "]");
4510         continue;
4511       }
4512     }
4513   }
4514   return null;
4515 };
4516 
4517 /**
4518  * @private
4519  */    
4520 net.user1.orbiter.Client.prototype.createCustomClient = function (wrapperClass, scope) {
4521   var customClient;
4522 
4523   // Wrap the client
4524   customClient = new wrapperClass();
4525   this.customClients[scope] = customClient;
4526   
4527   // Do custom client setup
4528   if (customClient instanceof CustomClient) {
4529     customClient.setClient(this);
4530     customClient.init();
4531     return customClient;
4532   } else {
4533     this.log.debug("[CLIENT_MANAGER] Custom client class [" + wrapperClass + "] does not "  
4534            + " extend CustomClient. Assuming specified class will manually "  
4535            + " compose its own Client instance for client ID: " + clientID
4536            + ". See Client.setClientClass()."); 
4537     return customClient;
4538   }
4539 };
4540 
4541 // =============================================================================
4542 // ROOM MANAGEMENT
4543 // =============================================================================
4544   
4545 /**
4546  * @private
4547  */
4548 net.user1.orbiter.Client.prototype.removeOccupiedRoomID = function (roomID) {
4549   if (this.isInRoom(roomID) && roomID != null) {
4550     this.occupiedRoomIDs.splice(net.user1.utils.ArrayUtil.indexOf(this.occupiedRoomIDs, roomID), 1);
4551     return true;
4552   } else {
4553     return false;
4554   }
4555 };
4556 
4557 /**
4558  * @private
4559  */
4560 net.user1.orbiter.Client.prototype.removeObservedRoomID = function (roomID) {
4561   if (this.isObservingRoom(roomID) && roomID != null) {
4562     this.observedRoomIDs.splice(net.user1.utils.ArrayUtil.indexOf(this.observedRoomIDs, roomID), 1);
4563     return true;
4564   } else {
4565     return false;
4566   }
4567 };
4568 
4569 /**
4570  * @private
4571  */
4572 net.user1.orbiter.Client.prototype.addOccupiedRoomID = function (roomID) {
4573   if (!this.isInRoom(roomID) && roomID != null) {
4574     this.log.info(this.toString() + " added occupied room ID [" + roomID + "].");
4575     this.occupiedRoomIDs.push(roomID);
4576   }
4577 };
4578 
4579 /**
4580  * @private
4581  */
4582 net.user1.orbiter.Client.prototype.addObservedRoomID = function (roomID) {
4583   if (!this.isObservingRoom(roomID) && roomID != null) {
4584     this.log.info("Client [" + this.getClientID() + "] added observed room ID [" + roomID + "].");
4585     this.observedRoomIDs.push(roomID);
4586   }
4587 };
4588 
4589 net.user1.orbiter.Client.prototype.isInRoom = function (roomID) {
4590   return net.user1.utils.ArrayUtil.indexOf(this.getOccupiedRoomIDs(), roomID) != -1;
4591 };
4592 
4593 net.user1.orbiter.Client.prototype.isObservingRoom = function (roomID) {
4594   return net.user1.utils.ArrayUtil.indexOf(this.getObservedRoomIDs(), roomID) != -1;
4595 };
4596 
4597 net.user1.orbiter.Client.prototype.getOccupiedRoomIDs = function () {
4598   var ids;
4599   if (this.clientManager.isObservingClient(this.getClientID())) {
4600     // This client is under observation, so its occupiedRoomIDs array is
4601     // 100% accurate.
4602     return this.occupiedRoomIDs == null ? [] : this.occupiedRoomIDs.slice(0);
4603   } else {
4604     // This client is not under observation, so the current client can only
4605     // deduce this client's occupied room list based on its current sphere of awareness.
4606     ids = [];
4607     var knownRooms = this.roomManager.getRooms();
4608     var numKnownRooms = knownRooms.length;
4609     var room;
4610     for (var i = 0; i < numKnownRooms; i++) {
4611       room = knownRooms[i];
4612       if (room.clientIsInRoom(this.getClientID())) {
4613         ids.push(room.getRoomID());
4614       }
4615     }
4616     return ids;
4617   }
4618 };
4619 
4620 net.user1.orbiter.Client.prototype.getObservedRoomIDs = function () {
4621   var ids;
4622   if (this.clientManager.isObservingClient(this.getClientID())) {
4623     // This client is under observation, so its occupiedRoomIDs array is
4624     // 100% accurate.
4625     return this.observedRoomIDs == null ? [] : this.observedRoomIDs.slice(0);
4626   } else {
4627     // This client is not under observation, so the current client can only
4628     // deduce this client's occupied room list based on its current sphere of awareness.
4629     ids = [];
4630     var knownRooms = this.roomManager.getRooms();
4631     var numKnownRooms = knownRooms.length;
4632     var room;
4633     for (var i = 0; i < numKnownRooms; i++) {
4634       room = knownRooms[i];
4635       if (room.clientIsObservingRoom(this.getClientID())) {
4636         ids.push(room.getRoomID());
4637       }
4638     }
4639     return ids;
4640   }
4641 };
4642 
4643 net.user1.orbiter.Client.prototype.getUpdateLevels = function (roomID) {
4644   var levels;
4645   var levelsAttr = this.getAttribute("_UL", roomID);
4646   
4647   if (levelsAttr != null) {
4648     levels = new net.user1.orbiter.UpdateLevels();
4649     levels.fromInt(parseInt(levelsAttr));
4650     return levels;
4651   } else {
4652     return null;
4653   }
4654 };
4655 
4656 // =============================================================================
4657 // BUILT-IN ATTRIBUTE RETRIEVAL
4658 // =============================================================================
4659 
4660 net.user1.orbiter.Client.prototype.getIP = function () {
4661   return this.getAttribute("_IP");
4662 };
4663 
4664 net.user1.orbiter.Client.prototype.getConnectTime = function () {
4665   var ct = this.getAttribute("_CT");
4666   return ct == null ? NaN : parseFloat(ct);
4667 };
4668 
4669 net.user1.orbiter.Client.prototype.getPing = function () {
4670   var ping = this.getAttribute("_PING");
4671   return ping == null ? -1 : parseInt(ping);
4672 };
4673 
4674 net.user1.orbiter.Client.prototype.getTimeOnline = function () {
4675   return this.server == null ? NaN : this.server.getServerTime() - this.getConnectTime();
4676 };
4677 
4678 // =============================================================================
4679 // MESSAGING
4680 // ============================================================================= 
4681 
4682 net.user1.orbiter.Client.prototype.sendMessage = function (messageName) {
4683   if (this.clientManager == null) {
4684     return;
4685   }
4686   // Delegate to ClientManager
4687   var rest = Array.prototype.slice.call(arguments).slice(1);
4688   var args = [messageName, 
4689               [this.getClientID()],
4690               null];
4691   this.clientManager.sendMessage.apply(this.clientManager, args.concat(rest));
4692 };
4693 
4694 // =============================================================================
4695 // ATTRIBUTES: PUBLIC API
4696 // =============================================================================
4697 net.user1.orbiter.Client.prototype.setAttribute = function (attrName, 
4698                                                             attrValue, 
4699                                                             attrScope, 
4700                                                             isShared, 
4701                                                             evaluate) {
4702   attrScope = attrScope == undefined ? null : attrScope;
4703   isShared = isShared == undefined ? true : isShared;
4704   evaluate = evaluate == undefined ? false : evaluate;
4705 
4706   // Create an integer to hold the attribute options.
4707   var attrOptions = (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
4708                     | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
4709   // Make the SetClientAttr UPC first so inputs are validated
4710   var setClientAttr = new net.user1.orbiter.upc.SetClientAttr(attrName, attrValue, attrOptions, attrScope, this.getClientID());
4711   
4712   // Set the attribute locally now, unless:
4713   // -it is another client's attribute
4714   // -it is the current client's attribute, and the value has changed
4715   if (!(!this.isSelf() || evaluate)) {
4716     // Set the attribute locally
4717     this.attributeManager.setAttributeLocal(attrName, attrValue, attrScope, this);
4718   }
4719 
4720   // Set the attribute on the server.
4721   this.messageManager.sendUPCObject(setClientAttr);
4722 };
4723 
4724 net.user1.orbiter.Client.prototype.deleteAttribute = function (attrName, attrScope) {
4725   var deleteRequest = new net.user1.orbiter.upc.RemoveClientAttr(this.getClientID(), null, attrName, attrScope);
4726   this.attributeManager.deleteAttribute(deleteRequest);
4727 };
4728 
4729 net.user1.orbiter.Client.prototype.getAttribute = function (attrName, attrScope) {
4730   return this.attributeManager.getAttribute(attrName, attrScope);
4731 };
4732 
4733 net.user1.orbiter.Client.prototype.getAttributes = function () {
4734   return this.attributeManager.getAttributes();
4735 };
4736 
4737 net.user1.orbiter.Client.prototype.getAttributesByScope = function (scope) {
4738   return this.attributeManager.getAttributesByScope(scope);
4739 };
4740 
4741 // =============================================================================
4742 // SYNCHRONIZATION
4743 // =============================================================================
4744   
4745 /**
4746  * @private
4747  */        
4748 net.user1.orbiter.Client.prototype.synchronize = function (clientManifest) {
4749   var scopes;
4750   this.synchronizeOccupiedRoomIDs(clientManifest.occupiedRoomIDs);
4751   this.synchronizeObservedRoomIDs(clientManifest.observedRoomIDs);
4752   
4753   // Synchronize Client attributes
4754   scopes = clientManifest.transientAttributes.getScopes();
4755   for (var i = scopes.length; --i >= 0;) {
4756     this.attributeManager.getAttributeCollection().synchronizeScope(scopes[i], clientManifest.transientAttributes);
4757   }
4758   // Synchronize UserAccount attributes
4759   if (this.account != null) {
4760     scopes = clientManifest.persistentAttributes.getScopes();
4761     for (i = scopes.length; --i >= 0;) {
4762       this.account.getAttributeManager().getAttributeCollection().synchronizeScope(scopes[i], clientManifest.persistentAttributes);
4763     }
4764   }
4765 };
4766     
4767 /**
4768  * @private
4769  */        
4770 net.user1.orbiter.Client.prototype.synchronizeOccupiedRoomIDs = function (newOccupiedRoomIDs) {
4771   if (newOccupiedRoomIDs == null) {
4772     // Nothing to synchronize
4773     return;
4774   }
4775   
4776   // Remove any rooms that are not in the new list
4777   var roomID;
4778   for (var i = this.occupiedRoomIDs.length; --i >= 0;) {
4779     roomID = this.occupiedRoomIDs[i];
4780     if (net.user1.utils.ArrayUtil.indexOf(newOccupiedRoomIDs, roomID) == -1) {
4781       this.removeOccupiedRoomID(roomID);
4782     }
4783   }
4784   
4785   // Add any rooms that are not in the old list (existing room IDs are ignored)
4786   for (i = newOccupiedRoomIDs.length; --i >= 0;) {
4787     roomID = newOccupiedRoomIDs[i];
4788     this.addOccupiedRoomID(roomID);
4789   }
4790 };
4791     
4792 /**
4793  * @private
4794  */        
4795 net.user1.orbiter.Client.prototype.synchronizeObservedRoomIDs = function (newObservedRoomIDs) {
4796   if (newObservedRoomIDs == null) {
4797     // Nothing to synchronize
4798     return;
4799   }
4800   // Remove any rooms that are not in the new list
4801   var roomID;
4802   for (var i = this.observedRoomIDs.length; --i >= 0;) {
4803     roomID = this.observedRoomIDs[i];
4804     if (net.user1.utils.ArrayUtil.indexOf(newObservedRoomIDs, roomID) == -1) {
4805       this.removeObservedRoomID(roomID);
4806     }
4807   }
4808   
4809   // Add any rooms that are not in the old list (existing room IDs are ignored)
4810   for (i = newObservedRoomIDs.length; --i >= 0;) {
4811     roomID = newObservedRoomIDs[i];
4812     this.addObservedRoomID(roomID);
4813   }
4814 };
4815 
4816 // =============================================================================
4817 // DEPENDENCIES
4818 // =============================================================================
4819 
4820 /**
4821  * @private
4822  */        
4823 net.user1.orbiter.Client.prototype.getAttributeManager = function () {
4824   return this.attributeManager;
4825 };
4826 
4827 net.user1.orbiter.Client.prototype.getClientManager = function () {
4828   return this.clientManager;
4829 };
4830 
4831 net.user1.orbiter.Client.prototype.getAccount = function () {
4832   return this.account;
4833 };
4834 
4835 /**
4836  * @private
4837  */
4838 net.user1.orbiter.Client.prototype.setAccount = function (value) {
4839   if (value == null) {
4840     this.account = null;
4841   } else {
4842     if (this.account != value) {
4843       this.account = value;
4844       this.account.setClient(this);
4845     }
4846   }
4847 };
4848 
4849 // =============================================================================
4850 // TOSTRING
4851 // =============================================================================
4852 
4853 net.user1.orbiter.Client.prototype.toString = function () {
4854   return "[CLIENT clientID: " + this.getClientID() + ", userID: " + (this.account == null ? "" : this.account.getUserID())  + "]";
4855 };
4856 
4857 // =============================================================================
4858 // EVENT DISPATCHING
4859 // =============================================================================
4860 
4861 /**
4862  * @private
4863  */
4864 net.user1.orbiter.Client.prototype.fireJoinRoom = function (room, roomID) {
4865   this.log.debug(this + " triggering ClientEvent.JOIN_ROOM event.");
4866   // Trigger event on listeners.
4867   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.JOIN_ROOM,
4868                                             null, room, roomID, this);
4869   this.dispatchEvent(e);
4870 };
4871 
4872 /**
4873  * @private
4874  */
4875 net.user1.orbiter.Client.prototype.fireLeaveRoom = function (room, roomID) {
4876   this.log.debug(this + " triggering ClientEvent.LEAVE_ROOM event.");
4877   // Trigger event on listeners.
4878   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.LEAVE_ROOM,
4879                                             null, room, roomID, this);
4880   this.dispatchEvent(e);
4881 };
4882 
4883 /**
4884  * @private
4885  */
4886 net.user1.orbiter.Client.prototype.fireObserveRoom = function (room, roomID) {
4887   this.log.debug(this + " triggering ClientEvent.OBSERVE_ROOM event.");
4888   // Trigger event on listeners.
4889   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_ROOM,
4890                                             null, room, roomID, this);
4891   this.dispatchEvent(e);
4892 };
4893 
4894 /**
4895  * @private
4896  */
4897 net.user1.orbiter.Client.prototype.fireStopObservingRoom = function (room, roomID) {
4898   this.log.debug(this + " triggering ClientEvent.STOP_OBSERVING_ROOM event.");
4899   // Trigger event on listeners.
4900   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM,
4901                                             null, room, roomID, this);
4902   this.dispatchEvent(e);
4903 };
4904 
4905 /**
4906  * @private
4907  */
4908 net.user1.orbiter.Client.prototype.fireObserve = function () {
4909   // Trigger event on listeners.
4910   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE, null, null, null, this);
4911   this.dispatchEvent(e);
4912 };
4913 
4914 /**
4915  * @private
4916  */
4917 net.user1.orbiter.Client.prototype.fireStopObserving = function () {
4918   // Trigger event on listeners.
4919   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING, null, null, null, this);
4920   this.dispatchEvent(e);
4921 };
4922 
4923 /**
4924  * @private
4925  */
4926 net.user1.orbiter.Client.prototype.fireObserveResult = function (status) {
4927   // Trigger event on listeners.
4928   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_RESULT,
4929                                             null, null, null, this, status);
4930   this.dispatchEvent(e);
4931 };
4932 
4933 /**
4934  * @private
4935  */
4936 net.user1.orbiter.Client.prototype.fireStopObservingResult = function (status) {
4937   // Trigger event on listeners.
4938   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT,
4939                                             null, null, null, this, status);
4940   this.dispatchEvent(e);
4941 };
4942 
4943 /**
4944  * @private
4945  */
4946 net.user1.orbiter.Client.prototype.fireLogin = function () {
4947   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
4948                                              net.user1.orbiter.Status.SUCCESS, this.getAccount().getUserID(), this.getClientID());
4949   this.dispatchEvent(e);
4950 };
4951 
4952 /**
4953  * @private
4954  */
4955 net.user1.orbiter.Client.prototype.fireLogoff = function (userID) {
4956   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
4957                                              net.user1.orbiter.Status.SUCCESS, userID, this.getClientID());
4958   this.dispatchEvent(e);
4959 };
4960 
4961 /**
4962  * @private
4963  */
4964 net.user1.orbiter.Client.prototype.fireSynchronize = function () {
4965   // Trigger event on listeners.
4966   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.SYNCHRONIZE, null, null, null, this);
4967   this.dispatchEvent(e);
4968 };
4969 
4970 // =============================================================================
4971 // DISPOSAL
4972 // =============================================================================
4973 
4974 net.user1.orbiter.Client.prototype.dispose = function () {
4975   this.occupiedRoomIDs = null;
4976   this.attributeManager.dispose();
4977   this.attributeManager = null;
4978   this.clientID = null;
4979   this.log = null;
4980   this.account = null;
4981   this.customClients = null;
4982   this.messageManager = null;
4983   this.clientManager = null;
4984   this.roomManager = null;
4985   this.server = null;
4986   this.disposed = true;
4987 };
4988 //==============================================================================
4989 // CLASS DECLARATION
4990 //==============================================================================
4991 /** 
4992  * @class
4993  * @extends net.user1.orbiter.snapshot.Snapshot
4994  */
4995 net.user1.orbiter.snapshot.ClientCountSnapshot = function () {
4996   // Call superconstructor
4997   net.user1.orbiter.snapshot.Snapshot.call(this);
4998   this.count = 0;
4999   this.method = net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT;
5000   this.hasStatus = true;
5001 };
5002 
5003 //==============================================================================
5004 // INHERITANCE
5005 //==============================================================================
5006 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientCountSnapshot, net.user1.orbiter.snapshot.Snapshot);
5007 
5008 //==============================================================================
5009 // INSTANCE METHODS
5010 //==============================================================================
5011 /**
5012  * @private
5013  */    
5014 net.user1.orbiter.snapshot.ClientCountSnapshot.prototype.setCount = function (value) {
5015   this.count = value;
5016 };
5017 
5018 net.user1.orbiter.snapshot.ClientCountSnapshot.prototype.getCount = function () {
5019   return this.count;
5020 };
5021 //==============================================================================
5022 // CLASS DECLARATION
5023 //==============================================================================
5024 /** @class
5025     @extends net.user1.events.Event
5026 */
5027 net.user1.orbiter.ClientEvent = function (type,
5028                                           changedAttr,
5029                                           room,
5030                                           roomID,
5031                                           client,
5032                                           status,
5033                                           clientID) {
5034   net.user1.events.Event.call(this, type);
5035   
5036   this.changedAttr = changedAttr;
5037   this.room = room;
5038   this.roomID = roomID;
5039   this.client = client;
5040   this.status = status;
5041   this.clientID = clientID;
5042 };
5043 
5044 //==============================================================================
5045 // INHERITANCE
5046 //==============================================================================
5047 net.user1.utils.extend(net.user1.orbiter.ClientEvent, net.user1.events.Event);
5048 
5049 //==============================================================================
5050 // STATIC VARIABLES
5051 //==============================================================================
5052 /** @constant */
5053 net.user1.orbiter.ClientEvent.JOIN_ROOM = "JOIN_ROOM";
5054 /** @constant */
5055 net.user1.orbiter.ClientEvent.LEAVE_ROOM = "LEAVE_ROOM";
5056 /** @constant */
5057 net.user1.orbiter.ClientEvent.OBSERVE_ROOM = "OBSERVE_ROOM";
5058 /** @constant */
5059 net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM = "STOP_OBSERVING_ROOM";
5060 /** @constant */
5061 net.user1.orbiter.ClientEvent.OBSERVE = "OBSERVE";
5062 /** @constant */
5063 net.user1.orbiter.ClientEvent.STOP_OBSERVING = "STOP_OBSERVING";
5064 /** @constant */
5065 net.user1.orbiter.ClientEvent.OBSERVE_RESULT = "OBSERVE_RESULT";
5066 /** @constant */
5067 net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";
5068 /** @constant */
5069 net.user1.orbiter.ClientEvent.SYNCHRONIZE = "SYNCHRONIZE";
5070 
5071 net.user1.orbiter.ClientEvent.prototype.getClient = function () {
5072   return this.client;
5073 };
5074 
5075 net.user1.orbiter.ClientEvent.prototype.getClientID = function () {
5076   if (this.client != null) {
5077     return this.client.getClientID();
5078   } else {
5079     return this.clientID;
5080   }
5081 };
5082 
5083 net.user1.orbiter.ClientEvent.prototype.getRoom = function () {
5084   return this.room;
5085 };
5086 
5087 net.user1.orbiter.ClientEvent.prototype.getRoomID = function () {
5088   return this.roomID;
5089 }
5090 
5091 net.user1.orbiter.ClientEvent.prototype.getStatus = function () {
5092   return this.status;
5093 };
5094 
5095 net.user1.orbiter.ClientEvent.prototype.toString = function () {
5096   return "[object ClientEvent]";
5097 };
5098 //==============================================================================
5099 // CLASS DECLARATION
5100 //==============================================================================
5101 /** 
5102  * @class
5103  * @extends net.user1.orbiter.snapshot.Snapshot
5104  */
5105 net.user1.orbiter.snapshot.ClientListSnapshot = function () {
5106   // Call superconstructor
5107   net.user1.orbiter.snapshot.Snapshot.call(this);
5108   this.clientList;
5109   this.method = net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT;
5110 };
5111 
5112 //==============================================================================
5113 // INHERITANCE
5114 //==============================================================================
5115 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientListSnapshot, net.user1.orbiter.snapshot.Snapshot);
5116 
5117 //==============================================================================
5118 // INSTANCE METHODS
5119 //==============================================================================
5120 /**
5121  * @private
5122  */          
5123 net.user1.orbiter.snapshot.ClientListSnapshot.prototype.setClientList = function (value) {
5124   this.clientList = value;
5125 };
5126 
5127 net.user1.orbiter.snapshot.ClientListSnapshot.prototype.getClientList = function () {
5128   if (!this.clientList) {
5129     return null;
5130   }
5131   return this.clientList.slice();
5132 }
5133 //==============================================================================
5134 // CLASS DECLARATION
5135 //==============================================================================
5136 /** @class
5137 
5138 The ClientManager class dispatches the following events:
5139 
5140 <ul class="summary">
5141 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CREATE_ACCOUNT_RESULT}</li>
5142   
5143 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE}</li>
5144 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING}</li>
5145 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED}</li>
5146 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED}</li>
5147 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT}</li>
5148 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT}</li>
5149 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_RESULT}</li>
5150 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT}</li>
5151 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.KICK_RESULT}</li>
5152 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.BAN_RESULT}</li>
5153 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT}</li>
5154 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT}</li>
5155 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT}</li>
5156 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED}</li>
5157 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED}</li>
5158 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST}</li>
5159 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE}</li>
5160 </ul>
5161 
5162 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
5163 
5164     @extends net.user1.events.EventDispatcher
5165 */  
5166 net.user1.orbiter.ClientManager = function (roomManager, 
5167                                             accountManager,
5168                                             connectionManager,
5169                                             messageManager,
5170                                             server,
5171                                             log) {
5172   // Call superconstructor
5173   net.user1.events.EventDispatcher.call(this);
5174   
5175   this.selfReference = null;
5176   this.defaultClientClass = null;
5177   this.lifetimeClientsRequested = 0;
5178   
5179   this._isWatchingForClients;
5180   this._isWatchingForUsers;
5181   this._isWatchingForBannedAddresses;
5182   
5183   this.watchedClients  = new net.user1.orbiter.ClientSet();
5184   this.observedClients = new net.user1.orbiter.ClientSet();
5185   this.bannedAddresses = [];
5186   this.clientCache     = new net.user1.utils.LRUCache(5000);
5187   
5188   this.roomManager       = roomManager;
5189   this.accountManager    = accountManager;
5190   this.connectionManager = connectionManager;
5191   this.messageManager    = messageManager;
5192   this.server            = server;
5193   this.log               = log;
5194 };
5195 
5196 //==============================================================================    
5197 // INHERITANCE
5198 //============================================================================== 
5199 net.user1.utils.extend(net.user1.orbiter.ClientManager, net.user1.events.EventDispatcher);
5200 
5201 //==============================================================================
5202 // CLIENT OBJECT CREATION AND ACCESS
5203 //==============================================================================
5204 
5205 /**
5206  * @private
5207  */
5208 net.user1.orbiter.ClientManager.prototype.requestClient = function (clientID) {
5209   var client;
5210   
5211   if (clientID == null || clientID === "") {
5212     throw new Error("[CLIENT_MANAGER] requestClient() called with empty clientID.");
5213   }
5214   
5215   client = this.getInternalClient(clientID);
5216   
5217   // If the client isn't already known
5218   if (client === null) {
5219     client = new net.user1.orbiter.Client(clientID, this, this.messageManager, this.roomManager, this.connectionManager, this.server, this.log);
5220     this.lifetimeClientsRequested++;
5221     this.clientCache.put(clientID, client);
5222   }
5223 
5224   return client;
5225 }
5226 
5227 net.user1.orbiter.ClientManager.prototype.getClient = function (clientID, scope) {
5228   var theClient;
5229   var theCustomClient;
5230   
5231   if (clientID === "" || clientID == null) {
5232     throw new Error("ClientManager.getClient() failed. Client ID must not be null or the" + 
5233                     " empty string.");
5234   }
5235 
5236   theClient = this.getInternalClient(clientID);
5237   if (theClient === null) {
5238     this.log.debug("[CLIENT_MANAGER] getClient() called for unknown client ID ["
5239               + clientID + "]."); 
5240     return null;
5241   } else {
5242     theCustomClient = theClient.getCustomClient(scope);
5243     return theCustomClient === null ? theClient : theCustomClient;
5244   }
5245 };
5246 
5247 net.user1.orbiter.ClientManager.prototype.getClients = function () {
5248   // Get all internal clients
5249   var clients = this.getInternalClients();
5250   var clientsList  = new Array();
5251   var customClient;
5252   
5253   // Replace internal clients with custom clients where available
5254   var client;
5255   for (var clientID in clients) {
5256     client = clients[clientID];
5257     customClient = client.getCustomClient(null);
5258     if (customClient != null) {
5259       clientsList.push(customClient);
5260     } else {
5261       clientsList.push(client);
5262     }
5263   }
5264   return clientsList;
5265 }
5266 
5267 net.user1.orbiter.ClientManager.prototype.getInternalClients = function () {
5268   var clients = net.user1.utils.ObjectUtil.combine(this.roomManager.getAllClients(),
5269                                                    this.accountManager.getClientsForObservedAccounts(),
5270                                                    this.observedClients.getAll(),
5271                                                    this.watchedClients.getAll());
5272   if (this.selfReference != null) {
5273     clients[this.selfReference.getClientID()] = this.selfReference;
5274   }
5275   return clients;
5276 };
5277 
5278 net.user1.orbiter.ClientManager.prototype.getInternalClient = function (clientID) {
5279   var theClient;
5280   
5281   // Error checking
5282   if (clientID === "" || clientID == null) {
5283     throw new Error("[CLIENT_MANAGER] this.getInternalClient() failed. Client ID must not be null or the" + 
5284                     " empty string.");
5285   }
5286   
5287   theClient = this.clientCache.get(clientID);
5288   
5289   if (theClient != null) {
5290     return theClient;
5291   } else {
5292     // Find the client...
5293     
5294     // Look in rooms
5295     var clients = this.roomManager.getAllClients(); 
5296     theClient = clients[clientID];
5297     if (theClient != null) {
5298       this.clientCache.put(clientID, theClient);
5299       return theClient;
5300     }
5301     
5302     // Look in observed accounts
5303     clients = this.accountManager.getClientsForObservedAccounts();
5304     theClient = clients[clientID];
5305     if (theClient != null) {
5306       this.clientCache.put(clientID, theClient);
5307       return theClient;
5308     }
5309 
5310     // Look in observed clients
5311     theClient = this.observedClients.getByClientID(clientID);
5312     if (theClient != null) {
5313       this.clientCache.put(clientID, theClient);
5314       return theClient;
5315     }
5316 
5317     // Look in watched clients
5318     theClient = this.watchedClients.getByClientID(clientID);
5319     if (theClient != null) {
5320       this.clientCache.put(clientID, theClient);
5321       return theClient;
5322     }
5323   }
5324   
5325   // Client not found
5326   return null;
5327 }
5328 
5329 net.user1.orbiter.ClientManager.prototype.getClientByUserID = function (userID, scope) {
5330   var theClient;
5331   var theCustomClient;
5332   var account;
5333   
5334   if (userID === "" || userID == null) {
5335     throw new Error("ClientManager.getClientByUserID() failed. User ID must not be null or the" + 
5336                     " empty string.");
5337   }
5338 
5339   // Search for the client in all known clients
5340   var client;
5341   var clients = this.getInternalClients();
5342   for (var clientID in clients) {
5343     client = clients[clientID];
5344     account = client.getAccount();
5345     if (account != null && account.getUserID() === userID) {
5346       theClient = client;
5347       break;
5348     }
5349   }
5350   
5351   if (theClient === null) {
5352     this.log.debug("[CLIENT_MANAGER] getClientByUserID() called for unknown user ID ["
5353               + userID + "]."); 
5354     return null;
5355   } else {
5356     theCustomClient = theClient.getCustomClient(scope);
5357     return theCustomClient === null ? theClient : theCustomClient;
5358   }
5359 };
5360 
5361 net.user1.orbiter.ClientManager.prototype.getClientByAttribute  = function (attributeName,
5362                                                                             attributeValue,
5363                                                                             attributeScope,
5364                                                                             roomScope) {
5365   var theCustomClient;
5366   
5367   // Validate
5368   if (attributeName == null || attributeName === "") {
5369     return null;
5370   }
5371   
5372   // Search for the client in all known clients
5373   var client;
5374   var clients = this.getInternalClients();
5375   for (var clientID in clients) {
5376     client = clients[clientID];
5377     if (client.getAttribute(attributeName, attributeScope)
5378         === attributeValue) {
5379       theCustomClient = client.getCustomClient(roomScope);
5380       return theCustomClient === null ? client : theCustomClient;
5381     }
5382   }
5383   return null;
5384 };
5385 
5386 net.user1.orbiter.ClientManager.prototype.clientIsKnown = function (clientID) {
5387   return this.getInternalClients()[clientID] !== null;
5388 };
5389 
5390 // =============================================================================
5391 // WATCHED CLIENTS
5392 // =============================================================================
5393 
5394 net.user1.orbiter.ClientManager.prototype.watchForClients = function () {
5395   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_CLIENTS);
5396 };   
5397 
5398 net.user1.orbiter.ClientManager.prototype.stopWatchingForClients = function () {
5399   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS);
5400 };     
5401 
5402 net.user1.orbiter.ClientManager.prototype.isWatchingForClients = function () {
5403   return this._isWatchingForClients;
5404 };
5405 
5406 net.user1.orbiter.ClientManager.prototype.hasWatchedClient = function (clientID) {
5407   return this.watchedClients.containsClientID(clientID);
5408 };
5409     
5410 /**
5411  * @private
5412  */        
5413 net.user1.orbiter.ClientManager.prototype.setIsWatchingForClients = function (value) {
5414   this._isWatchingForClients = value;
5415 };
5416     
5417 /**
5418  * @private
5419  */        
5420 net.user1.orbiter.ClientManager.prototype.addWatchedClient = function (client) {
5421   var customClient = client.getCustomClient(null);
5422   this.watchedClients.add(client);
5423   this.fireClientConnected(customClient === null ? client : customClient);      
5424 };
5425 
5426 /**
5427  * @private
5428  */        
5429 net.user1.orbiter.ClientManager.prototype.removeWatchedClient = function (clientID) {
5430   this.watchedClients.removeByClientID(clientID);
5431 };
5432 
5433 /**
5434  * @private
5435  */        
5436 net.user1.orbiter.ClientManager.prototype.removeAllWatchedClients = function () {
5437   this.watchedClients.removeAll();
5438 };
5439     
5440 /**
5441  * @private
5442  */        
5443 net.user1.orbiter.ClientManager.prototype.deserializeWatchedClients = function (ids) {
5444   var idList = ids.split(net.user1.orbiter.Tokens.RS);
5445   var idHash = new Object();
5446   var localClients = this.watchedClients.getAll();
5447   var len = idList.length;
5448   var theClient;
5449   var accountID;
5450   
5451   // Client list received, so set isWatchingForClients now, otherwise, code 
5452   // with side-effects may take action against the clients being added
5453    this.setIsWatchingForClients(true);
5454   
5455   // Generate a hash of clientID keys to accountID values
5456   for (var i = len-2; i >= 0; i-=2) {
5457     idHash[idList[i]] = idList[i+1]; 
5458   }
5459   
5460   // Remove all local clients that are not in the new list from the server
5461   var clientStillExists;
5462   for (var clientID in localClients) {
5463     if (!idHash.hasOwnProperty(clientID)) {
5464       // For best performance, use direct access rather than removeByClientID()
5465       delete localClients[clientID];
5466     }
5467   }      
5468   
5469   // Add all new clients that are not in the local set
5470   for (clientID in idHash) {
5471     if (clientID != "") {
5472       if (!this.watchedClients.containsClientID(clientID)) {
5473         theClient = this.requestClient(clientID);
5474         accountID = idHash[clientID]; 
5475         if (accountID != "") {
5476           theClient.setAccount(this.accountManager.requestAccount(accountID));
5477         }
5478         this.addWatchedClient(theClient);
5479       }
5480     } else {
5481       throw new Error("[CLIENT_MANAGER] Received empty client id in client list (u101).");
5482     }
5483   }
5484   
5485   this.fireSynchronize();
5486 };
5487 
5488 // =============================================================================
5489 // OBSERVED CLIENTS
5490 // =============================================================================
5491 
5492 net.user1.orbiter.ClientManager.prototype.observeClient = function (clientID) {
5493   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_CLIENT, clientID);
5494 };      
5495 
5496 net.user1.orbiter.ClientManager.prototype.isObservingClient = function (clientID) {
5497   return this.observedClients.containsClientID(clientID);
5498 }
5499 
5500 /**
5501  * @private
5502  */        
5503 net.user1.orbiter.ClientManager.prototype.addObservedClient = function (client) {
5504   var customClient = client.getCustomClient(null);
5505   this.observedClients.add(client);
5506   this.fireObserveClient(customClient === null ? client : customClient);
5507 };
5508 
5509 /**
5510  * @private
5511  */        
5512 net.user1.orbiter.ClientManager.prototype.removeObservedClient = function (clientID) {
5513   var client = this.observedClients.removeByClientID(clientID);
5514   var customClient;
5515   if (client != null) {
5516     customClient = client.getCustomClient(null);
5517     this.fireStopObservingClient(customClient === null ? client : customClient);
5518   }
5519 };
5520 
5521 /**
5522  * @private
5523  */        
5524 net.user1.orbiter.ClientManager.prototype.removeAllObservedClients = function () {
5525   this.observedClients.removeAll();
5526 };
5527 
5528 //==============================================================================
5529 // CLIENT ATTRIBUTE ACCESS
5530 //==============================================================================
5531 
5532 net.user1.orbiter.ClientManager.prototype.getAttributeForClients = function (clientIDs,
5533                                                                              attrName, 
5534                                                                              attrScope) {
5535   var clientAttributes = new Array();
5536   var thisClient;
5537   
5538   for (var i = 0; i < clientIDs.length; i++) {
5539     thisClient = this.getInternalClient(clientIDs[i]);
5540     if (thisClient != null) {
5541       clientAttributes.push({clientID: clientIDs[i],
5542           value: thisClient.getAttribute(attrName, attrScope)});
5543     } else {
5544       this.log.debug("[CLIENT_MANAGER] Attribute retrieval failed during "
5545                 + " getAttributeForClients(). Unknown client ID [" + clientIDs[i] + "]");
5546     }
5547   }
5548   return clientAttributes;
5549 };
5550 
5551 //==============================================================================
5552 // CUSTOM CLIENT MANAGEMENT
5553 //==============================================================================
5554 
5555 net.user1.orbiter.ClientManager.prototype.setDefaultClientClass = function (defaultClass) {
5556   this.defaultClientClass = defaultClass;
5557 };
5558 
5559 net.user1.orbiter.ClientManager.prototype.getDefaultClientClass = function () {
5560   return this.defaultClientClass;
5561 };
5562 
5563 //==============================================================================
5564 // CURRENT CLIENT ASSIGNMENT AND ACCESS
5565 //==============================================================================
5566 
5567 /**
5568  * @private
5569  */
5570 net.user1.orbiter.ClientManager.prototype.self = function () {
5571   return this.selfReference;
5572 }
5573 
5574 /**
5575  * @private
5576  */
5577 net.user1.orbiter.ClientManager.prototype.setSelf = function (client) {
5578   this.selfReference = client;
5579   client.setIsSelf();
5580 }
5581 
5582 //==============================================================================
5583 // CLIENT MESSAGING
5584 //==============================================================================
5585 
5586 net.user1.orbiter.ClientManager.prototype.sendMessage = function (messageName, 
5587                                                                   clientIDs,
5588                                                                   filters) {
5589   var rest = Array.prototype.slice.call(arguments).slice(3);
5590   
5591   // An array of arguments to send to the server.
5592   var args;
5593 
5594   // Can't continue without a valid methodName.
5595   if (messageName == null || messageName == "") {
5596     this.log.warn("[CLIENT_MANAGER] sendMessage() failed. No messageName supplied.");
5597     return;
5598   }
5599   
5600   // Send the UPC.
5601   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS, 
5602           messageName, 
5603           clientIDs.join(net.user1.orbiter.Tokens.RS),
5604           filters != null ? filters.toXMLString() : ""];
5605   this.messageManager.sendUPC.apply(this.messageManager, args.concat(rest));
5606 };
5607 
5608 // =============================================================================
5609 // BAN / UNBAN / KICK
5610 // =============================================================================
5611 
5612 net.user1.orbiter.ClientManager.prototype.ban = function (address, duration, reason) {
5613   this.messageManager.sendUPC(net.user1.orbiter.UPC.BAN, address, null, duration.toString(), reason);
5614 };
5615 
5616 net.user1.orbiter.ClientManager.prototype.unban = function (address) {
5617   this.messageManager.sendUPC(net.user1.orbiter.UPC.UNBAN, address);
5618 };
5619 
5620 net.user1.orbiter.ClientManager.prototype.kickClient = function (clientID) {
5621   if (clientID == null || clientID == "") {
5622     this.log.warn("[CLIENT_MANAGER] Kick attempt failed. No clientID supplied.");
5623   }
5624   this.messageManager.sendUPC(net.user1.orbiter.UPC.KICK_CLIENT, clientID);
5625 }
5626 
5627 // =============================================================================
5628 // WATCH BANNED ADDRESSES
5629 // =============================================================================
5630 
5631 net.user1.orbiter.ClientManager.prototype.watchForBannedAddresses = function () {
5632   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES);
5633 };
5634 
5635 net.user1.orbiter.ClientManager.prototype.stopWatchingForBannedAddresses = function () {
5636   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES);
5637 };
5638 
5639 /**
5640  * @private
5641  */  
5642 net.user1.orbiter.ClientManager.prototype.setWatchedBannedAddresses = function (bannedList) {
5643   this.bannedAddresses = bannedList;
5644   this.fireSynchronizeBanlist();
5645 };
5646 
5647 /**
5648  * @private
5649  */  
5650 net.user1.orbiter.ClientManager.prototype.addWatchedBannedAddress = function (address) {
5651   this.bannedAddresses.push(address);
5652   this.fireAddressBanned(address);
5653 };
5654 
5655 /**
5656  * @private
5657  */  
5658 net.user1.orbiter.ClientManager.prototype.removeWatchedBannedAddress = function (address) {
5659   var idx = net.user1.util.ArrayUtil.indexOf(bannedAddresses, address);
5660   if (idx === -1) {
5661     this.log.warn("[CLIENT_MANAGER] Request to remove watched banned address failed."
5662              + " Address not found.");
5663   }
5664   this.bannedAddresses.splice(idx, 1);
5665   this.fireAddressUnbanned(address);
5666 }
5667 
5668 /**
5669  * @private
5670  */        
5671 net.user1.orbiter.ClientManager.prototype.setIsWatchingForBannedAddresses = function (value) {
5672   this._isWatchingForBannedAddresses = value;
5673 };
5674 
5675 net.user1.orbiter.ClientManager.prototype.isWatchingForBannedAddresses = function () {
5676   return this._isWatchingForBannedAddresses;
5677 };
5678 
5679 net.user1.orbiter.ClientManager.prototype.getBannedAddresses = function () {
5680   return this.bannedAddresses.slice(0);
5681 };
5682 
5683 //==============================================================================
5684 // STATISTICS
5685 //==============================================================================
5686 
5687 net.user1.orbiter.ClientManager.prototype.getLifetimeNumClientsKnown = function () {
5688   // -1 for each "ready" state the connection has achieved because we don't
5689   // count the current client ("self")
5690   return this.lifetimeClientsRequested-this.connectionManager.getReadyCount();
5691 };
5692 
5693 net.user1.orbiter.ClientManager.prototype.getNumClients = function () {
5694   return net.user1.utils.ObjectUtil.length(this.getInternalClients());
5695 };
5696 
5697 net.user1.orbiter.ClientManager.prototype.getNumClientsOnServer = function () {
5698   return this.watchedClients.length();
5699 }
5700 
5701 //==============================================================================
5702 // EVENT DISPATCHING
5703 //==============================================================================
5704 
5705 /**
5706  * @private
5707  */
5708 net.user1.orbiter.ClientManager.prototype.fireObserveClient = function (client) {
5709   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE, null, null, null, client);
5710   this.dispatchEvent(e);
5711 };
5712 
5713 /**
5714  * @private
5715  */
5716 net.user1.orbiter.ClientManager.prototype.fireStopObservingClient = function (client) {
5717   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING, null, null, null, client);
5718   this.dispatchEvent(e);
5719 }; 
5720 
5721 /**
5722  * @private
5723  */
5724 net.user1.orbiter.ClientManager.prototype.fireClientConnected = function (client) {
5725   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED, 
5726                                                               client.getClientID(), client));
5727 };    
5728 
5729 /**
5730  * @private
5731  */
5732 net.user1.orbiter.ClientManager.prototype.fireClientDisconnected = function (client) {
5733   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED, 
5734                                                               client.getClientID(), client));
5735 };   
5736 
5737 /**
5738  * @private
5739  */
5740 net.user1.orbiter.ClientManager.prototype.fireStopWatchingForClientsResult = function (status) {
5741   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT, 
5742                                                               null, null, null, status));
5743 };   
5744 
5745 /**
5746  * @private
5747  */
5748 net.user1.orbiter.ClientManager.prototype.fireWatchForClientsResult = function (status) {
5749   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT, 
5750                                                               null, null, null, status));
5751 };   
5752 
5753 /**
5754  * @private
5755  */
5756 net.user1.orbiter.ClientManager.prototype.fireObserveClientResult = function (clientID, status) {
5757   this.dispatchEvent(new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_RESULT, 
5758                                        null, null, null, this.getClient(clientID), status, clientID));
5759 };    
5760 
5761 /**
5762  * @private
5763  */
5764 net.user1.orbiter.ClientManager.prototype.fireStopObservingClientResult = function (clientID, status) {
5765   this.dispatchEvent(new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT, 
5766                                                        null, null, null, this.getClient(clientID), status, clientID));
5767 };    
5768 
5769 /**
5770  * @private
5771  */
5772 net.user1.orbiter.ClientManager.prototype.fireKickClientResult = function (clientID, status) {
5773   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.KICK_RESULT, 
5774                                                               clientID, null, null, status));
5775 };    
5776 
5777 /**
5778  * @private
5779  */
5780 net.user1.orbiter.ClientManager.prototype.fireBanClientResult = function (address, clientID, status) {
5781   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.BAN_RESULT, 
5782                                                               clientID, null, address, status));
5783 };    
5784 
5785 /**
5786  * @private
5787  */
5788 net.user1.orbiter.ClientManager.prototype.fireUnbanClientResult = function (address, status) {
5789   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT, 
5790                                                               null, null, address, status));
5791 };    
5792 
5793 /**
5794  * @private
5795  */
5796 net.user1.orbiter.ClientManager.prototype.fireWatchForBannedAddressesResult = function (status) {
5797   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT, 
5798                                                               null, null, null, status));
5799 };    
5800 
5801 /**
5802  * @private
5803  */
5804 net.user1.orbiter.ClientManager.prototype.fireStopWatchingForBannedAddressesResult = function (status) {
5805   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT, 
5806                                                               null, null, null, status));
5807 };    
5808 
5809 /**
5810  * @private
5811  */
5812 net.user1.orbiter.ClientManager.prototype.fireAddressBanned = function (address) {
5813   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED, 
5814                                                               null, null, address));
5815 }; 
5816 
5817 /**
5818  * @private
5819  */
5820 net.user1.orbiter.ClientManager.prototype.fireAddressUnbanned = function (address) {
5821   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED, 
5822                                                               null, null, address));
5823 }; 
5824 
5825 /**
5826  * @private
5827  */
5828 net.user1.orbiter.ClientManager.prototype.fireSynchronizeBanlist = function () {
5829   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST));
5830 };        
5831 
5832 /**
5833  * @private
5834  */
5835 net.user1.orbiter.ClientManager.prototype.fireSynchronize = function () {
5836   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE));
5837 };        
5838 
5839 //==============================================================================
5840 // CLEANUP AND DISPOSAL
5841 //==============================================================================
5842 
5843 /**
5844  * @private
5845  */    
5846 net.user1.orbiter.ClientManager.prototype.cleanup = function () {
5847   this.log.info("[CLIENT_MANAGER] Cleaning resources.");
5848   this.selfReference = null;
5849   this.removeAllObservedClients();
5850   this.removeAllWatchedClients();
5851   this.setIsWatchingForClients(false);
5852 };   
5853 
5854 net.user1.orbiter.ClientManager.prototype.dispose = function () {
5855   this.log.info("[CLIENT_MANAGER] Disposing resources.");
5856   this.watchedClients = null;
5857   this.observedClients = null;
5858   this.defaultClientClass = null;
5859 };
5860 //==============================================================================
5861 // CLASS DECLARATION
5862 //==============================================================================
5863 /** @class
5864     @extends net.user1.events.Event
5865 */
5866 net.user1.orbiter.ClientManagerEvent = function (type,
5867                                                  clientID,
5868                                                  client,
5869                                                  address,
5870                                                  status) {
5871   net.user1.events.Event.call(this, type);
5872   
5873   this.clientID = clientID;
5874   this.client   = client;
5875   this.address  = address;
5876   this.status   = status;
5877 };
5878 
5879 //==============================================================================
5880 // INHERITANCE
5881 //==============================================================================
5882 net.user1.utils.extend(net.user1.orbiter.ClientManagerEvent, net.user1.events.Event);
5883 
5884 //==============================================================================
5885 // STATIC VARIABLES
5886 //==============================================================================
5887 /** @constant */
5888 net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT = "WATCH_FOR_CLIENTS_RESULT";
5889 /** @constant */
5890 net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT = "STOP_WATCHING_FOR_CLIENTS_RESULT";
5891 /** @constant */
5892 net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED = "CLIENT_DISCONNECTED";
5893 /** @constant */
5894 net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED = "CLIENT_CONNECTED";
5895 /** @constant */
5896 net.user1.orbiter.ClientManagerEvent.KICK_RESULT = "KICK_RESULT";
5897 /** @constant */
5898 net.user1.orbiter.ClientManagerEvent.BAN_RESULT = "BAN_RESULT";
5899 /** @constant */
5900 net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT = "UNBAN_RESULT";
5901 /** @constant */
5902 net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT = "WATCH_FOR_BANNED_ADDRESSES_RESULT";
5903 /** @constant */
5904 net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT";
5905 /** @constant */
5906 net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED = "ADDRESS_BANNED";
5907 /** @constant */
5908 net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED = "ADDRESS_UNBANNED";
5909 /** @constant */
5910 net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST = "SYNCHRONIZE_BANLIST"; 
5911 /** @constant */
5912 net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE = "SYNCHRONIZE"; 
5913     
5914 net.user1.orbiter.ClientManagerEvent.prototype.getClientID = function () {
5915   return this.clientID;
5916 };
5917 
5918 net.user1.orbiter.ClientManagerEvent.prototype.getClient = function () {
5919   return this.client;
5920 };
5921 
5922 net.user1.orbiter.ClientManagerEvent.prototype.getAddress = function () {
5923   return this.address;
5924 };
5925 
5926 net.user1.orbiter.ClientManagerEvent.prototype.getStatus = function () {
5927   return this.status;
5928 };
5929 
5930 net.user1.orbiter.ClientManagerEvent.prototype.toString = function () {
5931   return "[object ClientManagerEvent]";
5932 };
5933 //==============================================================================
5934 // CLASS DECLARATION
5935 //==============================================================================
5936 net.user1.orbiter.ClientManifest = function () {
5937   this.clientID = null;
5938   this.userID = null;
5939   this.persistentAttributes = new net.user1.orbiter.AttributeCollection();
5940   this.transientAttributes = new net.user1.orbiter.AttributeCollection();
5941   this.occupiedRoomIDs = null;
5942   this.observedRoomIDs = null;
5943 };
5944 
5945 /**
5946  * @private
5947  */        
5948 net.user1.orbiter.ClientManifest.prototype.deserialize = function (clientID,
5949                                                                    userID,
5950                                                                    serializedOccupiedRoomIDs,
5951                                                                    serializedObservedRoomIDs,
5952                                                                    globalAttrs,
5953                                                                    roomAttrs) {
5954   this.clientID = clientID == "" ? null : clientID;
5955   this.userID   = userID == "" ? null : userID;
5956   
5957   // Room ids
5958   this.deserializeOccupiedRoomIDs(serializedOccupiedRoomIDs);
5959   this.deserializeObservedRoomIDs(serializedObservedRoomIDs);
5960   
5961   // Global attrs
5962   this.deserializeAttributesByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR, globalAttrs);
5963   
5964   // Room attrs
5965   for (var i = 0; i < roomAttrs.length; i += 2) {
5966     this.deserializeAttributesByScope(roomAttrs[i], roomAttrs[i+1]);
5967   }
5968 };
5969     
5970 /**
5971  * @private
5972  */        
5973 net.user1.orbiter.ClientManifest.prototype.deserializeOccupiedRoomIDs = function (roomIDs) {
5974   // No rooms included in the manifest
5975   if (roomIDs == null) {
5976     return;
5977   }
5978   // Client is in no rooms
5979   if (roomIDs == "") {
5980     this.occupiedRoomIDs = [];
5981     return;
5982   }
5983   // Client is in one or more room
5984   this.occupiedRoomIDs = roomIDs.split(net.user1.orbiter.Tokens.RS);
5985 };
5986     
5987 /**
5988  * @private
5989  */        
5990 net.user1.orbiter.ClientManifest.prototype.deserializeObservedRoomIDs = function (roomIDs) {
5991   if (roomIDs == null) {
5992     return;
5993   }
5994   if (roomIDs == "") {
5995     this.observedRoomIDs = [];
5996     return;
5997   }
5998   this.observedRoomIDs = roomIDs.split(net.user1.orbiter.Tokens.RS);
5999 };
6000      
6001 /**
6002  * @private
6003  */         
6004 net.user1.orbiter.ClientManifest.prototype.deserializeAttributesByScope = function (scope,
6005                                                                                     serializedAttributes) {
6006   var attrList;
6007   if (serializedAttributes == null || serializedAttributes == "") {
6008     return;
6009   }
6010   attrList = serializedAttributes.split(net.user1.orbiter.Tokens.RS);
6011   for (var i = attrList.length-3; i >= 0; i -=3) {
6012     if (parseInt(attrList[i+2]) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
6013       // Persistent
6014       this.persistentAttributes.setAttribute(attrList[i], attrList[i+1], scope);
6015     } else {
6016       // Non-persistent
6017       this.transientAttributes.setAttribute(attrList[i], attrList[i+1], scope);
6018     }
6019   }
6020 };
6021 //==============================================================================
6022 // CLASS DECLARATION
6023 //==============================================================================
6024 /**
6025  * @private
6026  */  
6027 net.user1.orbiter.ClientSet = function () {
6028   this.clients = new net.user1.utils.UDictionary();
6029 };
6030 
6031 net.user1.orbiter.ClientSet.prototype.add = function (client) {
6032   this.clients[client.getClientID()] = client;
6033 };
6034 
6035 net.user1.orbiter.ClientSet.prototype.remove = function (client) {
6036   var client = clients[client.getClientID()];
6037   delete this.clients[client.getClientID()];
6038   return client;
6039 };
6040 
6041 net.user1.orbiter.ClientSet.prototype.removeAll = function () {
6042   this.clients = new net.user1.utils.UDictionary();
6043 }
6044 
6045 net.user1.orbiter.ClientSet.prototype.removeByClientID = function (clientID) {
6046   var client = this.clients[clientID];
6047   delete this.clients[clientID];
6048   return client;
6049 };
6050 
6051 net.user1.orbiter.ClientSet.prototype.contains = function (client) {
6052   return this.clients[client.getClientID()] != null;
6053 };
6054 
6055 net.user1.orbiter.ClientSet.prototype.containsClientID = function (clientID) {
6056   if (clientID == "" || clientID == null) {
6057     return false;
6058   }
6059   return this.getByClientID(clientID) != null;
6060 };
6061 
6062 net.user1.orbiter.ClientSet.prototype.getByClientID = function (clientID) {
6063   return this.clients[clientID];
6064 };
6065 
6066 net.user1.orbiter.ClientSet.prototype.getByUserID = function (userID) {
6067   var account;
6068   
6069   var client;
6070   for (var clientID in this.clients) {
6071     client = this.clients[clientID];
6072     account = client.getAccount();
6073     if (account != null && account.getUserID() == userID) {
6074       return client;
6075     }
6076   }
6077   return null;
6078 };
6079 
6080 net.user1.orbiter.ClientSet.prototype.getAll = function () {
6081   return this.clients;
6082 }
6083 
6084 net.user1.orbiter.ClientSet.prototype.getAllIDs = function () {
6085   var ids = [];
6086   for (var clientID in this.clients) {
6087     ids.push(clientID);
6088   }
6089   return ids;
6090 };
6091 
6092 net.user1.orbiter.ClientSet.prototype.length = function () {
6093   return net.user1.utils.ObjectUtil.length(this.clients);
6094 };
6095 //==============================================================================
6096 // CLASS DECLARATION
6097 //==============================================================================
6098 /** 
6099  * @class
6100  * @extends net.user1.orbiter.snapshot.Snapshot
6101  */
6102 net.user1.orbiter.snapshot.ClientSnapshot = function (clientID) {
6103   // Call superconstructor
6104   net.user1.orbiter.snapshot.Snapshot.call(this);
6105   this.manifest = null;
6106   this.method = net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT;
6107   this.args   = [clientID];
6108   this.hasStatus = true;
6109 };
6110 
6111 //==============================================================================
6112 // INHERITANCE
6113 //==============================================================================
6114 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientSnapshot, net.user1.orbiter.snapshot.Snapshot);
6115 
6116 //==============================================================================
6117 // INSTANCE METHODS
6118 //==============================================================================        
6119 /**
6120  * @private
6121  */    
6122 net.user1.orbiter.snapshot.ClientSnapshot.prototype.setManifest = function (value) {
6123   this.manifest = value;
6124 };
6125 
6126 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getAttribute = function (name, scope) {
6127   if (!this.manifest) {
6128     return null;
6129   }
6130   return this.manifest.transientAttributes.getAttribute(name, scope);
6131 };
6132 
6133 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getAttributes = function () {
6134   if (!this.manifest) {
6135     return null;
6136   }
6137   return this.manifest.transientAttributes.getAll();
6138 };
6139 
6140 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getClientID = function () {
6141   if (!this.manifest) {
6142     return null;
6143   }
6144   return this.manifest.clientID;
6145 };
6146 
6147 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getUserID = function () {
6148   if (!this.manifest) {
6149     return null;
6150   }
6151   return this.manifest.userID;
6152 };
6153 
6154 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getOccupiedRoomIDs = function () {
6155   if (!this.manifest) {
6156     return null;
6157   }
6158   return this.manifest.occupiedRoomIDs.slice();
6159 };
6160 
6161 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getObservedRoomIDs = function () {
6162   if (!this.manifest) {
6163     return null;
6164   }
6165   return this.manifest.observedRoomIDs.slice();
6166 }
6167 //==============================================================================
6168 // CLASS DECLARATION
6169 //==============================================================================
6170 /** 
6171  * @private
6172  */
6173 net.user1.orbiter.CollectionEvent = function (type, item) {
6174   net.user1.events.Event.call(this, type);
6175   
6176   this.item = item;
6177 };
6178 
6179 //==============================================================================
6180 // INHERITANCE
6181 //==============================================================================
6182 net.user1.utils.extend(net.user1.orbiter.CollectionEvent, net.user1.events.Event);
6183 
6184 //==============================================================================
6185 // STATIC VARIABLES
6186 //==============================================================================
6187 
6188 /** @constant */
6189 net.user1.orbiter.CollectionEvent.REMOVE_ITEM = "REMOVE_ITEM";
6190 /** @constant */
6191 net.user1.orbiter.CollectionEvent.ADD_ITEM = "ADD_ITEM";
6192     
6193 net.user1.orbiter.CollectionEvent.prototype.getItem = function () {
6194   return this.item;
6195 };
6196 
6197 net.user1.orbiter.CollectionEvent.prototype.toString = function () {
6198   return "[object CollectionEvent]";
6199 };
6200 //==============================================================================
6201 // COMPARE TYPE CONSTANTS
6202 //==============================================================================
6203 /** @class */
6204 net.user1.orbiter.filters.CompareType = new Object();
6205 /** @constant */
6206 net.user1.orbiter.filters.CompareType.EQUAL = "eq";
6207 /** @constant */
6208 net.user1.orbiter.filters.CompareType.NOT_EQUAL = "ne";
6209 /** @constant */
6210 net.user1.orbiter.filters.CompareType.GREATER_THAN = "gt";
6211 /** @constant */
6212 net.user1.orbiter.filters.CompareType.GREATER_THAN_OR_EQUAL = "ge";
6213 /** @constant */
6214 net.user1.orbiter.filters.CompareType.LESS_THAN = "lt";
6215 /** @constant */
6216 net.user1.orbiter.filters.CompareType.LESS_THAN_OR_EQUAL = "le";
6217 //==============================================================================
6218 // CLASS DECLARATION
6219 //==============================================================================
6220 /**
6221 * @private
6222 */
6223 net.user1.orbiter.CoreEventLogger = function (log,
6224                                               connectionMan,
6225                                               roomMan,
6226                                               accountMan,
6227                                               server,
6228                                               clientMan,
6229                                               orbiter) {
6230   this.log = log;
6231 
6232   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
6233                            this.stopWatchingForRoomsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6234   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
6235                            this.watchForRoomsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6236   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT,
6237                            this.createRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6238   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT,
6239                            this.removeRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6240   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_ADDED,
6241                            this.roomAddedListener, this, net.user1.utils.integer.MAX_VALUE);
6242   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED,
6243                            this.roomRemovedListener, this, net.user1.utils.integer.MAX_VALUE);
6244   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_COUNT,
6245                            this.roomCountListener, this, net.user1.utils.integer.MAX_VALUE);
6246   roomMan.addEventListener(net.user1.orbiter.RoomEvent.JOIN_RESULT,
6247                            this.joinRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6248   roomMan.addEventListener(net.user1.orbiter.RoomEvent.LEAVE_RESULT,
6249                            this.leaveRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6250   roomMan.addEventListener(net.user1.orbiter.RoomEvent.OBSERVE_RESULT,
6251                            this.observeRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6252   roomMan.addEventListener(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT,
6253                            this.stopObservingRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6254   
6255   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT, 
6256                               this.createAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6257   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT,
6258                               this.removeAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6259   accountMan.addEventListener(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT,
6260                               this.changePasswordResultListener, this, net.user1.utils.integer.MAX_VALUE);
6261   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED,
6262                               this.accountAddedListener, this, net.user1.utils.integer.MAX_VALUE);
6263   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED,
6264                               this.accountRemovedListener, this, net.user1.utils.integer.MAX_VALUE);
6265   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGOFF_RESULT,
6266                               this.logoffResultListener, this, net.user1.utils.integer.MAX_VALUE);
6267   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGOFF,
6268                               this.logoffListener, this, net.user1.utils.integer.MAX_VALUE);
6269   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGIN_RESULT,
6270                               this.loginResultListener, this, net.user1.utils.integer.MAX_VALUE);
6271   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGIN,
6272                               this.loginListener, this, net.user1.utils.integer.MAX_VALUE);
6273   accountMan.addEventListener(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
6274                               this.changePasswordListener, this, net.user1.utils.integer.MAX_VALUE);
6275   accountMan.addEventListener(net.user1.orbiter.AccountEvent.OBSERVE,
6276                               this.observeAccountListener, this, net.user1.utils.integer.MAX_VALUE);
6277   accountMan.addEventListener(net.user1.orbiter.AccountEvent.STOP_OBSERVING,
6278                               this.stopObservingAccountListener, this, net.user1.utils.integer.MAX_VALUE);
6279   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT,
6280                               this.stopWatchingForAccountsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6281   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT,
6282                               this.watchForAccountsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6283   accountMan.addEventListener(net.user1.orbiter.AccountEvent.OBSERVE_RESULT,
6284                               this.observeAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6285   accountMan.addEventListener(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT,
6286                               this.stopObservingAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6287   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE,
6288                               this.synchronizeAccountsListener, this, net.user1.utils.integer.MAX_VALUE);
6289 
6290   server.addEventListener(net.user1.orbiter.ServerEvent.TIME_SYNC, this.timeSyncListener, this, net.user1.utils.integer.MAX_VALUE);
6291 
6292   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE,
6293                                  this.connectFailureListener, this, net.user1.utils.integer.MAX_VALUE);
6294   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT,
6295                                  this.clientKillConnectListener, this, net.user1.utils.integer.MAX_VALUE);
6296   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT,
6297                                  this.serverKillConnectListener, this, net.user1.utils.integer.MAX_VALUE);
6298   
6299   clientMan.addEventListener(net.user1.orbiter.ClientEvent.OBSERVE,
6300                              this.observeClientListener, this, net.user1.utils.integer.MAX_VALUE);
6301   clientMan.addEventListener(net.user1.orbiter.ClientEvent.STOP_OBSERVING,
6302                              this.stopObservingClientListener, this, net.user1.utils.integer.MAX_VALUE);
6303   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED,
6304                              this.clientConnectedListener, this, net.user1.utils.integer.MAX_VALUE);
6305   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED,
6306                              this.clientDisconnectedListener, this, net.user1.utils.integer.MAX_VALUE);
6307   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT,
6308                              this.stopWatchingForClientsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6309   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT,
6310                              this.watchForClientsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6311   clientMan.addEventListener(net.user1.orbiter.ClientEvent.OBSERVE_RESULT,
6312                              this.observeClientResultListener, this, net.user1.utils.integer.MAX_VALUE);
6313   clientMan.addEventListener(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT,
6314                              this.stopObservingClientResultListener, this, net.user1.utils.integer.MAX_VALUE);
6315   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE,
6316                              this.synchronizeClientsListener, this, net.user1.utils.integer.MAX_VALUE);
6317   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED,
6318                              this.addressBannedListener, this, net.user1.utils.integer.MAX_VALUE);
6319   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED,
6320                              this.addressUnbannedListener, this, net.user1.utils.integer.MAX_VALUE);
6321   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT,
6322                              this.stopWatchingForBannedAddressesResultListener, this, net.user1.utils.integer.MAX_VALUE);
6323   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT,
6324                              this.watchForBannedAddressesResultListener, this, net.user1.utils.integer.MAX_VALUE);
6325   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST,
6326                              this.synchronizeBanlistListener, this, net.user1.utils.integer.MAX_VALUE);
6327   
6328                            
6329   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.readyListener, this, net.user1.utils.integer.MAX_VALUE);
6330   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE, this.protocolIncompatibleListener, this, net.user1.utils.integer.MAX_VALUE);
6331   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED, this.connectRefusedListener, this, net.user1.utils.integer.MAX_VALUE);
6332         
6333   this.log.addEventListener(net.user1.logger.LogEvent.LEVEL_CHANGE, this.logLevelChangeListener, this, net.user1.utils.integer.MAX_VALUE);
6334 };    
6335 
6336         
6337 // =============================================================================
6338 // Logger EVENT LISTENERS
6339 // =============================================================================
6340   
6341 /**
6342  * @private
6343  */        
6344 net.user1.orbiter.CoreEventLogger.prototype.logLevelChangeListener = function (e) {
6345   this.log.info("[LOGGER] Log level set to: [" + e.getLevel() + "].");
6346 };
6347 
6348 // =============================================================================
6349 // Orbiter EVENT LISTENERS
6350 // =============================================================================
6351   
6352 /**
6353  * @private
6354  */        
6355 net.user1.orbiter.CoreEventLogger.prototype.readyListener = function (e) {
6356   this.log.info("[ORBITER] Orbiter now connected and ready.");
6357 };
6358   
6359 /**
6360  * @private
6361  */        
6362 net.user1.orbiter.CoreEventLogger.prototype.protocolIncompatibleListener = function (e) {
6363   this.log.warn("[ORBITER] Orbiter UPC protocol incompatibility detected. Client "
6364            + "UPC version: " + e.target.getSystem().getUPCVersion().toString()
6365            + ". Server version: " + e.getServerUPCVersion().toString() + ".");
6366 };
6367 
6368 /**
6369  * @private
6370  */        
6371 net.user1.orbiter.CoreEventLogger.prototype.connectRefusedListener = function (e) {
6372   if (e.getConnectionRefusal().reason == net.user1.orbiter.ConnectionRefusalReason.BANNED) {
6373     this.log.warn("[ORBITER] Union Server refused the connection because the"
6374              + " client address is banned for the following reason: [" 
6375              + e.getConnectionRefusal().banReason + "]. The ban started at: ["
6376              + new Date(e.getConnectionRefusal().bannedAt) + "]. The ban duration is: ["
6377              + net.user1.utils.NumericFormatter.msToElapsedDayHrMinSec(e.getConnectionRefusal().banDuration*1000) + "].");
6378   } else {
6379     this.log.warn("[ORBITER] Union Server refused the connection. Reason: [" 
6380              + e.getConnectionRefusal().reason + "]. Description: ["
6381              + e.getConnectionRefusal().description + "].");
6382   }
6383 }
6384 
6385 // =============================================================================
6386 // Server EVENT LISTENERS
6387 // =============================================================================
6388 
6389 net.user1.orbiter.CoreEventLogger.prototype.timeSyncListener = function (e) {
6390   this.log.info("[SERVER] Server time synchronized with client. Approximate time on " + 
6391       "server is now: " + new Date(e.target.getServerTime()));
6392 };
6393 
6394 // =============================================================================
6395 // AccountManager EVENT LISTENERS 
6396 // =============================================================================
6397 net.user1.orbiter.CoreEventLogger.prototype.createAccountResultListener = function (e) {
6398   this.log.info("[ACCOUNT_MANAGER] Result for createAccount(). Account: " 
6399            + e.getUserID() + ", Status: " + e.getStatus());
6400 };
6401 
6402 net.user1.orbiter.CoreEventLogger.prototype.removeAccountResultListener = function (e) {
6403   this.log.info("[ACCOUNT_MANAGER] Result for removeAccount(). Account: "
6404            + e.getUserID() + ", Status: " + e.getStatus());
6405 };
6406 
6407 net.user1.orbiter.CoreEventLogger.prototype.changePasswordResultListener = function (e) {
6408   this.log.info("[ACCOUNT_MANAGER] Result for changePassword(). Account: "
6409            + e.getUserID() + ", Status: " + e.getStatus());
6410 };
6411 
6412 net.user1.orbiter.CoreEventLogger.prototype.accountAddedListener = function (e) {
6413   this.log.info("[ACCOUNT_MANAGER] Account added: " + e.getAccount());
6414 };
6415 
6416 net.user1.orbiter.CoreEventLogger.prototype.accountRemovedListener = function (e) {
6417   this.log.info("[ACCOUNT_MANAGER] Account removed: " + e.getAccount());
6418 };
6419 
6420 net.user1.orbiter.CoreEventLogger.prototype.logoffResultListener = function (e) {
6421   this.log.info("[ACCOUNT_MANAGER] Result for logoff(). Account: "
6422            + e.getAccount() + ", Status: " + e.getStatus());
6423 };
6424 
6425 net.user1.orbiter.CoreEventLogger.prototype.logoffListener = function (e) {
6426   this.log.info("[ACCOUNT_MANAGER] Account logged off: " + e.getAccount());
6427 };
6428 
6429 net.user1.orbiter.CoreEventLogger.prototype.loginResultListener = function (e) {
6430   this.log.info("[ACCOUNT_MANAGER] Result for login(). Account: "
6431            + e.getAccount() + ", Status: " + e.getStatus());
6432 };
6433 
6434 net.user1.orbiter.CoreEventLogger.prototype.loginListener = function (e) {
6435   this.log.info("[ACCOUNT_MANAGER] Account logged in: " + e.getAccount());
6436 };
6437 
6438 net.user1.orbiter.CoreEventLogger.prototype.changePasswordListener = function (e) {
6439   this.log.info("[ACCOUNT_MANAGER] Password changed for account: " + e.getUserID());
6440 };
6441 
6442 net.user1.orbiter.CoreEventLogger.prototype.observeAccountListener = function (e) {
6443   this.log.info("[ACCOUNT_MANAGER] Account observed: " + e.getAccount());
6444 };
6445 
6446 net.user1.orbiter.CoreEventLogger.prototype.stopObservingAccountListener = function (e) {
6447   this.log.info("[ACCOUNT_MANAGER] Stopped observing account: " + e.getUserID());
6448 };
6449 
6450 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForAccountsResultListener = function (e) {
6451   this.log.info("[SERVER] 'Stop watching for accounts' result: " + e.getStatus());
6452 };
6453 
6454 net.user1.orbiter.CoreEventLogger.prototype.watchForAccountsResultListener = function (e) {
6455   this.log.info("[ACCOUNT_MANAGER] 'Watch for accounts' result: " + e.getStatus());
6456 };
6457 
6458 net.user1.orbiter.CoreEventLogger.prototype.observeAccountResultListener = function (e) {
6459   this.log.info("[ACCOUNT_MANAGER] 'Observe account result' for account: "
6460            + e.getAccount() + ", Status: " + e.getStatus());
6461 };
6462 
6463 net.user1.orbiter.CoreEventLogger.prototype.stopObservingAccountResultListener = function (e) {
6464   this.log.info("[ACCOUNT_MANAGER] 'Stop observing account result' for account: "
6465            + e.getUserID() + ", Status: " + e.getStatus());
6466 };
6467 
6468 net.user1.orbiter.CoreEventLogger.prototype.synchronizeAccountsListener = function (e) {
6469   this.log.info("[ACCOUNT_MANAGER] User account list synchronized with server.");
6470 };
6471 
6472 // =============================================================================
6473 // CONNECTION EVENT LISTENERS
6474 // =============================================================================
6475 
6476 net.user1.orbiter.CoreEventLogger.prototype.connectFailureListener = function (e) {
6477   this.log.info("[CONNECTION_MANAGER] " + e.getStatus());
6478 };
6479 
6480 net.user1.orbiter.CoreEventLogger.prototype.serverKillConnectListener = function (e) {
6481   this.log.info("[CONNECTION_MANAGER] Server closed the connection.");
6482 };
6483 
6484 net.user1.orbiter.CoreEventLogger.prototype.clientKillConnectListener = function (e) {
6485   this.log.info("[CONNECTION_MANAGER] Connection to server closed by client.");
6486 };
6487 
6488 // =============================================================================
6489 // RoomManager EVENT LISTENERS
6490 // =============================================================================
6491 
6492 net.user1.orbiter.CoreEventLogger.prototype.watchForRoomsResultListener = function (e) {
6493   this.log.info("[ROOM_MANAGER] 'Watch for rooms' result for qualifier [" 
6494             + e.getRoomIdQualifier() + "]: " + e.getStatus());
6495 };
6496 
6497 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForRoomsResultListener = function (e) {
6498   this.log.info("[ROOM_MANAGER] 'Stop watching for rooms' result for"
6499             + " qualifier [" + e.getRoomIdQualifier() 
6500             + "]: " + e.getStatus());
6501 };
6502 
6503 net.user1.orbiter.CoreEventLogger.prototype.createRoomResultListener = function (e) {
6504   this.log.info("[ROOM_MANAGER] Room creation result for room [" 
6505             + e.getRoomID() + "]: " + e.getStatus());
6506 };
6507 
6508 net.user1.orbiter.CoreEventLogger.prototype.removeRoomResultListener = function (e) {
6509   this.log.info("[ROOM_MANAGER] Room removal result for room [" 
6510             + e.getRoomID() + "]: " + e.getStatus());
6511 };
6512 
6513 net.user1.orbiter.CoreEventLogger.prototype.roomAddedListener = function (e) {
6514   this.log.info("[ROOM_MANAGER] Room added: " + e.getRoom() + ".");
6515 };
6516 
6517 net.user1.orbiter.CoreEventLogger.prototype.roomRemovedListener = function (e) {
6518   this.log.info("[ROOM_MANAGER] Room removed: " + e.getRoom() + ".");
6519 };
6520 
6521 net.user1.orbiter.CoreEventLogger.prototype.roomCountListener = function (e) {
6522   this.log.info("[ROOM_MANAGER] New room count: " + e.getNumRooms() + ".");
6523 };
6524 
6525 net.user1.orbiter.CoreEventLogger.prototype.joinRoomResultListener = function (e) {
6526   this.log.info("[ROOM_MANAGER] Join result for room [" 
6527             + e.getRoomID() + "]: " + e.getStatus());
6528 };
6529 
6530 net.user1.orbiter.CoreEventLogger.prototype.leaveRoomResultListener = function (e) {
6531   this.log.info("[ROOM_MANAGER] Leave result for room [" 
6532             + e.getRoomID() + "]: " + e.getStatus());
6533 };
6534 
6535 net.user1.orbiter.CoreEventLogger.prototype.observeRoomResultListener = function (e) {
6536   this.log.info("[ROOM_MANAGER] Observe result for room [" 
6537             + e.getRoomID() + "]: " + e.getStatus());
6538 };
6539 
6540 net.user1.orbiter.CoreEventLogger.prototype.stopObservingRoomResultListener = function (e) {
6541   this.log.info("[ROOM_MANAGER] Stop observing result for room [" 
6542             + e.getRoomID() + "]: " + e.getStatus());
6543 };
6544 
6545 // =============================================================================
6546 // ClientManager EVENT LISTENERS
6547 // =============================================================================
6548 
6549 net.user1.orbiter.CoreEventLogger.prototype.observeClientListener = function (e) {
6550   this.log.info("[CLIENT_MANAGER] Client observed: " + e.getClient());
6551 };
6552 
6553 net.user1.orbiter.CoreEventLogger.prototype.stopObservingClientListener = function (e) {
6554   this.log.info("[CLIENT_MANAGER] Stopped observing client: " + e.getClient());
6555 };
6556 
6557 net.user1.orbiter.CoreEventLogger.prototype.clientConnectedListener = function (e) {
6558   this.log.info("[CLIENT_MANAGER] Foreign client connected. ClientID: [" + e.getClientID() 
6559            + "].");
6560 };
6561 
6562 net.user1.orbiter.CoreEventLogger.prototype.clientDisconnectedListener = function (e) {
6563   this.log.info("[CLIENT_MANAGER] Foreign client disconnected. ClientID: [" + e.getClientID() 
6564            + "].");
6565 };
6566 
6567 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForClientsResultListener = function (e) {
6568   this.log.info("[CLIENT_MANAGER] 'Stop watching for clients' result: " + e.getStatus());
6569 };
6570 
6571 net.user1.orbiter.CoreEventLogger.prototype.watchForClientsResultListener = function (e) {
6572   this.log.info("[CLIENT_MANAGER] 'Watch for clients' result: " + e.getStatus());
6573 };
6574 
6575 net.user1.orbiter.CoreEventLogger.prototype.observeClientResultListener = function (e) {
6576   this.log.info("[CLIENT_MANAGER] 'Observe client' result for client: " 
6577            + e.getClient() + ", status: " + e.getStatus());
6578 };
6579 
6580 net.user1.orbiter.CoreEventLogger.prototype.stopObservingClientResultListener = function (e) {
6581   this.log.info("[CLIENT_MANAGER] 'Stop observing client' result for client: " 
6582            + e.getClient() + ", status: " + e.getStatus());
6583 };
6584 
6585 net.user1.orbiter.CoreEventLogger.prototype.synchronizeClientsListener = function (e) {
6586   this.log.info("[CLIENT_MANAGER] Client list synchronized with server.");
6587 };
6588 
6589 net.user1.orbiter.CoreEventLogger.prototype.addressBannedListener = function (e) {
6590   this.log.info("[CLIENT_MANAGER] Client address banned: [" + e.getAddress() 
6591            + "].");
6592 };
6593 
6594 net.user1.orbiter.CoreEventLogger.prototype.addressUnbannedListener = function (e) {
6595   this.log.info("[CLIENT_MANAGER] Client address unbanned. ClientID: [" + e.getAddress() 
6596            + "].");
6597 };
6598 
6599 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForBannedAddressesResultListener = function (e) {
6600   this.log.info("[CLIENT_MANAGER] 'Stop watching for banned addresses' result: " + e.getStatus());
6601 };
6602 
6603 net.user1.orbiter.CoreEventLogger.prototype.watchForBannedAddressesResultListener = function (e) {
6604   this.log.info("[CLIENT_MANAGER] 'Watch for banned addresses' result: " + e.getStatus());
6605 };
6606 
6607 net.user1.orbiter.CoreEventLogger.prototype.synchronizeBanlistListener = function (e) {
6608   this.log.info("[CLIENT_MANAGER] Banned list synchronized with server.");
6609 };
6610 
6611 
6612 
6613 
6614 
6615 //==============================================================================
6616 // CLASS DECLARATION
6617 //==============================================================================
6618 /** 
6619  * @class
6620  * The CoreMessageListener class is an internal class that responds to the 
6621  * built-in UPC messages sent by the Union Server to the Orbiter. The 
6622  * CoreMessageListener class does not define any public methods or variables.
6623  * 
6624  * @private
6625  */
6626 net.user1.orbiter.CoreMessageListener = function (orbiter) {
6627   /**
6628    * @type net.user1.orbiter.Orbiter
6629    */
6630   this.orbiter = orbiter;
6631   this.log = orbiter.getLog();      
6632   this.registerCoreListeners();
6633   this.orbiter.getConnectionManager().addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
6634                                                        this.selectConnectionListener, this);
6635 
6636   this.roomMan = this.orbiter.getRoomManager();
6637   this.accountMan = this.orbiter.getAccountManager();
6638   this.clientMan = this.orbiter.getClientManager();
6639   this.snapshotMan = this.orbiter.getSnapshotManager();
6640 };
6641 
6642 //==============================================================================
6643 // INSTANCE METHODS
6644 //==============================================================================
6645 net.user1.orbiter.CoreMessageListener.prototype.registerCoreListeners = function () {
6646   var msgMan = this.orbiter.getMessageManager();
6647   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM, this.u6, this);
6648   msgMan.addMessageListener(net.user1.orbiter.UPC.RECEIVE_MESSAGE, this.u7, this);
6649   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE, this.u8, this);
6650   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ATTR_UPDATE, this.u9, this);
6651   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_METADATA, this.u29, this);
6652   msgMan.addMessageListener(net.user1.orbiter.UPC.CREATE_ROOM_RESULT, this.u32, this);
6653   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROOM_RESULT, this.u33, this);
6654   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT, this.u34, this);
6655   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM, this.u36, this);
6656   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM, this.u37, this);
6657   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT, this.u38, this);
6658   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ADDED, this.u39, this);
6659   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_REMOVED, this.u40, this);
6660   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT, this.u42, this);
6661   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT, this.u43, this);
6662   msgMan.addMessageListener(net.user1.orbiter.UPC.LEFT_ROOM, this.u44, this);
6663   msgMan.addMessageListener(net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT, this.u46, this);
6664   msgMan.addMessageListener(net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT, this.u47, this);
6665   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT, this.u48, this);
6666   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGIN_RESULT, this.u49, this);
6667   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_SNAPSHOT, this.u54, this);
6668   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM, this.u59, this);
6669   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT, this.u60, this);
6670   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM, this.u62, this);
6671   msgMan.addMessageListener(net.user1.orbiter.UPC.SERVER_HELLO, this.u66, this);
6672   msgMan.addMessageListener(net.user1.orbiter.UPC.JOIN_ROOM_RESULT, this.u72, this);
6673   msgMan.addMessageListener(net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT, this.u73, this);
6674   msgMan.addMessageListener(net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT, this.u74, this);
6675   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT, this.u75, this);
6676   msgMan.addMessageListener(net.user1.orbiter.UPC.LEAVE_ROOM_RESULT, this.u76, this);
6677   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT, this.u77, this);
6678   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT, this.u78, this);
6679   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ATTR_REMOVED, this.u79, this);
6680   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT, this.u80, this);
6681   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED, this.u81, this);
6682   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT, this.u82, this);
6683   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGOFF_RESULT, this.u87, this);
6684   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGGED_IN, this.u88, this);
6685   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGGED_OFF, this.u89, this);
6686   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED, this.u90, this);
6687   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENTLIST_SNAPSHOT, this.u101, this);
6688   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER, this.u102, this);
6689   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER, this.u103, this);
6690   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_SNAPSHOT, this.u104, this);
6691   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT, this.u105, this);
6692   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT, this.u106, this);
6693   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT, this.u107, this);
6694   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT, this.u108, this);
6695   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT, this.u109, this);
6696   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT, this.u110, this);
6697   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_ADDED, this.u111, this);
6698   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_REMOVED, this.u112, this);
6699   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT, this.u113, this);
6700   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT, this.u114, this);
6701   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT, this.u115, this);
6702   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT, this.u116, this);
6703   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT, this.u117, this);
6704   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT, this.u118, this);
6705   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_OBSERVED, this.u119, this);
6706   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT, this.u120, this);
6707   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT, this.u123, this);
6708   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_OBSERVED, this.u124, this);
6709   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT, this.u125, this);
6710   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT, this.u126, this);
6711   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE, this.u127, this);
6712   msgMan.addMessageListener(net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE, this.u128, this);
6713   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM, this.u129, this);
6714   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM, this.u130, this);
6715   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE, this.u131, this);
6716   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE, this.u132, this);
6717   msgMan.addMessageListener(net.user1.orbiter.UPC.ADD_ROLE_RESULT, this.u134, this);
6718   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROLE_RESULT, this.u136, this);
6719   msgMan.addMessageListener(net.user1.orbiter.UPC.BAN_RESULT, this.u138, this);
6720   msgMan.addMessageListener(net.user1.orbiter.UPC.UNBAN_RESULT, this.u140, this);
6721   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT, this.u142, this);
6722   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT, this.u144, this);
6723   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT, this.u146, this);
6724   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED, this.u147, this);
6725   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED, this.u148, this);
6726   msgMan.addMessageListener(net.user1.orbiter.UPC.KICK_CLIENT_RESULT, this.u150, this);
6727   msgMan.addMessageListener(net.user1.orbiter.UPC.SERVERMODULELIST_SNAPSHOT, this.u152, this);
6728   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT_RESULT, this.u155, this);
6729   msgMan.addMessageListener(net.user1.orbiter.UPC.UPC_STATS_SNAPSHOT, this.u156, this);
6730   msgMan.addMessageListener(net.user1.orbiter.UPC.RESET_UPC_STATS_RESULT, this.u158, this);
6731   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS_RESULT, this.u160, this);
6732   msgMan.addMessageListener(net.user1.orbiter.UPC.PROCESSED_UPC_ADDED, this.u161, this);
6733   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT, this.u163, this);
6734   msgMan.addMessageListener(net.user1.orbiter.UPC.NODELIST_SNAPSHOT, this.u166, this);
6735   msgMan.addMessageListener(net.user1.orbiter.UPC.GATEWAYS_SNAPSHOT, this.u168, this);
6736 }
6737 
6738 net.user1.orbiter.CoreMessageListener.prototype.createHashFromArg = function (arg) {
6739   var list = arg.split(net.user1.orbiter.Tokens.RS);
6740   var hash = new Object();
6741 
6742   for (var i = 0; i < list.length; i += 2) {
6743     hash[list[i]] = list[i+1];
6744   }
6745   return hash;
6746 };
6747 
6748 net.user1.orbiter.CoreMessageListener.prototype.selectConnectionListener = function (e) {
6749   var msgMan = this.orbiter.getMessageManager();
6750   if (msgMan.removeListenersOnDisconnect) {
6751     this.registerCoreListeners();
6752   }
6753 };
6754   
6755 /**
6756  * Room joined.
6757  */
6758 net.user1.orbiter.CoreMessageListener.prototype.u6 = function (roomID) {
6759   // Add the room to the occupied room list
6760   var room = this.roomMan.addOccupiedRoom(roomID);
6761   // Tell the room to do its join duties
6762   room.doJoin();
6763   // Fire JOIN through the client
6764   var selfClient = this.clientMan.self();
6765   if (selfClient) {
6766     selfClient.fireJoinRoom(room, roomID);
6767   }
6768 };
6769 
6770 /**
6771  * Handles sendMessage() calls sent by other clients.
6772  */
6773 net.user1.orbiter.CoreMessageListener.prototype.u7 = function (message,
6774                                                                broadcastType,
6775                                                                fromClientID,
6776                                                                toRoomID) {
6777   var msgMan = this.orbiter.getMessageManager();
6778   var listenerError;
6779   var fromClient;
6780   var toRoom;
6781   var args;  // Args passed to the messsage listener
6782   var userDefinedArgs = Array.prototype.slice.call(arguments).slice(4);
6783 
6784   // Retrieve the Room object for the recipient room. 
6785   toRoom = this.roomMan.getRoom(toRoomID);
6786   
6787   // Retrieve the Client object for the sender
6788   if (fromClientID == "") {
6789     // No client ID was supplied, so the message was generated by the
6790     // server, not by a client, so set fromClient to null.
6791     fromClient = null;
6792   } else {
6793     // A valid ID was supplied, so find or create the matching IClient object
6794     fromClient = this.clientMan.getClient(fromClientID);
6795     fromClient = fromClient == null ? this.clientMan.requestClient(fromClientID) : fromClient;
6796   }
6797 
6798   // ===== To Clients, or To Server =====
6799   // If the message was sent to a specific client, a list of specific clients,
6800   // or to the whole server, then args passed to registered message listeners 
6801   // are: the Client object plus all user-defined arguments originally passed
6802   // to sendMessage().       
6803   if (broadcastType != net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS) {
6804     args = [fromClient].concat(userDefinedArgs);
6805     try {
6806       msgMan.notifyMessageListeners(message, args);
6807     } catch (e) {
6808       listenerError = e;
6809     }
6810   } else {
6811   
6812     // ===== To Rooms =====
6813     // Check if the room is valid
6814     if (toRoom == null) { 
6815       this.log.warn("Message (u7) received for unknown room: [" + toRoomID + "]"
6816         + "Message: [" + message + "]");
6817       return;
6818     }
6819   
6820     // RECEIVE_MESSAGE was a response to SEND_MESSAGE_TO_ROOMS, so 
6821     // we notify listeners only if they asked to be told about messages 
6822     // sent to the recipient room.
6823 
6824     // First, get the list of messsage listeners for this message
6825     var listeners = msgMan.getMessageListeners(message);
6826 
6827     // Split the recipient room ID into two parts
6828     var toRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(toRoomID);
6829     var toRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(toRoomID);
6830 
6831     // If the message can be dispatched, set to true.
6832     var listenerFound; 
6833     // If the listener isn't interested in messages sent to the 
6834     // recipient room, set to true.
6835     var listenerIgnoredMessage;
6836                                          
6837     // ===== Run once for each message listener =====
6838     var messageListener;
6839     for (var i = 0; i < listeners.length; i++) {
6840       messageListener = listeners[i];
6841       
6842       // Assume this listener ignored the message until we prove it didn't
6843       listenerIgnoredMessage = true;
6844       
6845       // --- Has no "forRoomIDs" filter ---
6846       // If the listener doesn't specify any forRoomIDs, then 
6847       // just send it the message notification. (No-rooms-specified
6848       // means the listener wants all of these messages, no matter
6849       // which room they were sent to.) This listener is told which 
6850       // room the message was sent to via args[1] (toRoomID).
6851       if (messageListener.getForRoomIDs() == null) {
6852         args = [fromClient, toRoom].concat(userDefinedArgs);
6853         try {
6854           messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
6855         } catch (e) {
6856           listenerError = e;
6857         }
6858         listenerFound = true;
6859         listenerIgnoredMessage = false;
6860         continue;  // Done with this listener. On to the next.
6861       }
6862       
6863       // --- Has a "forRoomIDs" filter ---
6864       // If the message was sent to any of the rooms the listener is 
6865       // interested in, then notify that listener. Note that a listener 
6866       // for messages sent to room foo.* means the listener wants 
6867       // notifications for all rooms whose ids start with foo.
6868       var listenerRoomIDs  = messageListener.getForRoomIDs();
6869       var listenerRoomQualifier;
6870       var listenerRoomSimpleID;
6871       // ===== Run once for each room id =====
6872       var listenerRoomIDString;
6873       for (var j = 0; j < listenerRoomIDs.length; j++) {
6874         listenerRoomIDString = listenerRoomIDs[j];
6875         // Split the room id
6876         listenerRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(listenerRoomIDString);
6877         listenerRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(listenerRoomIDString);
6878 
6879         // Check if the listener is interested in the recipient room...
6880         if (listenerRoomQualifier == toRoomQualifier
6881             && 
6882             (listenerRoomSimpleID == toRoomSimpleID
6883              || listenerRoomSimpleID == "*")) {
6884           // Found a match. Notify the listener...
6885             
6886           // Prepare args.
6887           if (listenerRoomIDs.length == 1) {
6888             // The listener is interested in messages sent to a 
6889             // specific room only, so omit the "toRoom" arg.
6890             // (The listener already knows the target room because 
6891             // it's only notified if the message was sent to that room.)
6892             args = [fromClient].concat(userDefinedArgs);
6893           } else {
6894             // The listener is interested in messages sent to 
6895             // multiple rooms. In this case, we have to 
6896             // include the "toRoom" arg so the listener knows 
6897             // which room received the message.
6898             args = [fromClient, toRoom].concat(userDefinedArgs);
6899           }
6900           
6901           try {
6902             messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
6903           } catch (e) {
6904             listenerError = e;
6905           }
6906           listenerFound = true;
6907           listenerIgnoredMessage = false;
6908           break; // Stop looking at this listener's room ids
6909         }
6910       } // Done looking at this listener's room ids
6911       if (listenerIgnoredMessage) {
6912         this.log.debug("Message listener ignored message: " + message + ". "
6913                        + "Listener registered to receive " 
6914                        + "messages sent to: " + messageListener.getForRoomIDs() 
6915                        + ", but message was sent to: " + toRoomID);
6916       }
6917     } 
6918     if (!listenerFound) {
6919       this.log.warn("No message listener handled incoming message: " 
6920                     + message + ", sent to: " + toRoomID);
6921     }
6922   } // Done looking at listeners for the incoming message
6923   
6924   if (listenerError != null) {
6925     throw new Error("A message listener for incoming message [" + message + "]" +
6926       (fromClient == null ? "" : ", received from client [" + fromClient.getClientID() + "],") + 
6927       " encountered an error:\n\n" + listenerError.toString() +
6928       "\n\nEnsure that all [" + message + "] listeners supply a first" +
6929       " parameter whose datatype is Client (or a compatible type). Listeners" +
6930       " that registered for the message via MessageManager's addMessageListener()" +
6931       " with anything other than a single roomID for the toRoomIDs parameter must" +
6932       " also define a second paramter whose" +
6933       " datatype is Room (or a compatible type). Finally, ensure that" +
6934       " the listener's declared message parameters match the following actual message" +
6935       " arguments:\n    " + userDefinedArgs
6936       + (typeof listenerError.stack === "undefined" ? "" : "\n\nStack trace follows:\n" + listenerError.stack)
6937       );
6938   }
6939 }
6940 
6941 /**
6942  * Client attribute update
6943  */
6944 net.user1.orbiter.CoreMessageListener.prototype.u8 = function (attrScope,
6945                                                                clientID,
6946                                                                userID,
6947                                                                attrName,
6948                                                                attrVal,
6949                                                                attrOptions) { 
6950   var client;
6951   var account;
6952   var options = parseInt(attrOptions);
6953   
6954   if (options &net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
6955     account = this.accountMan.getAccount(userID);
6956     if (account != null) {
6957       account.getAttributeManager().setAttributeLocal(attrName, attrVal, attrScope);
6958     } else {
6959       throw new Error("[CORE_MESSAGE_LISTENER] Received an attribute update for "
6960         + " an unknown user account [" + userID + "]. Please report this error with"
6961         + " the following log to union@user1.net.\n" 
6962         + this.log.getHistory().join("\n"));
6963     }
6964   } else {
6965     client = this.clientMan.getInternalClient(clientID);
6966     if (client != null) {
6967       client.getAttributeManager().setAttributeLocal(attrName, attrVal, attrScope);
6968     } else {
6969       throw new Error("[CORE_MESSAGE_LISTENER] Received an attribute update for "
6970         + " an unknown client [" + clientID + "]. Please report this error with"
6971         + " the following log to union@user1.net.\n" 
6972         + this.log.getHistory().join("\n"));
6973     }
6974   }
6975 };
6976 
6977 /**
6978  * Room attribute update
6979  */
6980 net.user1.orbiter.CoreMessageListener.prototype.u9 = function (roomID, 
6981                                                                byClientID,
6982                                                                attrName,
6983                                                                attrVal) {
6984   var theRoom = this.roomMan.getRoom(roomID);
6985   var byClient;
6986   
6987   // Quit if the room isn't found
6988   if (theRoom == null) {
6989     this.log.warn("Room attribute update received for server-side room with no" + 
6990              " matching client-side Room object. Room ID [" +
6991              roomID + "]. Attribute: [" + attrName + "].");
6992     return;
6993   }
6994 
6995   // Retrieve the Client object for the sender
6996   if (byClientID == "") {
6997     // No client ID was supplied, so the message was generated by the
6998     // server, not by a client, so set fromClient to null.
6999     byClient = null;
7000   } else {
7001     // A valid ID was supplied, so find or create the matching IClient object
7002     byClient = this.clientMan.getClient(byClientID);
7003     byClient = byClient == null ? this.clientMan.requestClient(byClientID) : byClient;
7004   }
7005 
7006   theRoom.getAttributeManager().setAttributeLocal(attrName, attrVal, net.user1.orbiter.Tokens.GLOBAL_ATTR, byClient);
7007 };
7008 
7009 /**
7010  * CLIENT_METADATA
7011  */
7012 net.user1.orbiter.CoreMessageListener.prototype.u29 = function (id) {
7013   var theClient = this.clientMan.requestClient(id);
7014   this.clientMan.setSelf(theClient);
7015 };
7016 
7017 /**
7018  * CREATE_ROOM_RESULT
7019  */
7020 net.user1.orbiter.CoreMessageListener.prototype.u32 = function (roomID, status) {
7021   var theRoom = this.roomMan.getRoom(roomID);
7022   switch (status) {
7023     case net.user1.orbiter.Status.ERROR:
7024     case net.user1.orbiter.Status.SUCCESS:
7025     case net.user1.orbiter.Status.ROOM_EXISTS:
7026     case net.user1.orbiter.Status.PERMISSION_DENIED:
7027       this.roomMan.fireCreateRoomResult(net.user1.orbiter.RoomIDParser.getQualifier(roomID),
7028                                         net.user1.orbiter.RoomIDParser.getSimpleRoomID(roomID),
7029                                         status);
7030       break;
7031     
7032     default: 
7033       this.log.warn("Unrecognized status code for u32."
7034                + " Room ID: [" + roomID + "], status: [" + status + "].");
7035   }
7036 };
7037 
7038 /**
7039  * REMOVE_ROOM_RESULT
7040  */
7041 net.user1.orbiter.CoreMessageListener.prototype.u33 = function (roomID, status) {
7042   this.roomMan.fireRemoveRoomResult(net.user1.orbiter.RoomIDParser.getQualifier(roomID),
7043                                     net.user1.orbiter.RoomIDParser.getSimpleRoomID(roomID),
7044                                     status);
7045   switch (status) {
7046     case net.user1.orbiter.Status.ERROR: 
7047       this.log.warn("Server error for room removal attempt: " + roomID);
7048       break;
7049     case net.user1.orbiter.Status.PERMISSION_DENIED: 
7050       this.log.info("Attempt to remove room [" + roomID 
7051                + "] failed. Permission denied. See server log for details.");
7052       break;
7053       
7054     case net.user1.orbiter.Status.SUCCESS:
7055     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7056       if (this.roomMan.getRoom(roomID) != null) {
7057         this.roomMan.disposeRoom(roomID);
7058       }
7059       break;
7060     
7061     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7062     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7063       break;
7064     
7065     default: 
7066       this.log.warn("Unrecognized status code for u33."
7067                + " Room ID: [" + roomID + "], status: [" + status + "].");
7068   }
7069 };
7070 
7071 /**
7072  * CLIENTCOUNT_SNAPSHOT
7073  */
7074 net.user1.orbiter.CoreMessageListener.prototype.u34 = function (requestID,
7075                                                                 numClients) {
7076   this.snapshotMan.receiveClientCountSnapshot(requestID, parseInt(numClients));
7077 };
7078 
7079 /**
7080  * CLIENT_ADDED_TO_ROOM
7081  */
7082 net.user1.orbiter.CoreMessageListener.prototype.u36 = function (roomID,
7083                                                      clientID,
7084                                                      userID,
7085                                                      globalAttributes,
7086                                                      roomAttributes) {
7087   var theClient = this.clientMan.requestClient(clientID);
7088   var account = this.accountMan.requestAccount(userID);
7089   var clientManifest;
7090   if (account != null
7091       && theClient.getAccount() != account) {
7092     theClient.setAccount(account);
7093   }
7094 
7095   // If it's not the current client, set the client's attributes. 
7096   // (The current client obtains its own attributes through separate u8s.)
7097   var theRoom = this.roomMan.getRoom(roomID);
7098   if (!theClient.isSelf()) {
7099     clientManifest = new net.user1.orbiter.ClientManifest();
7100     clientManifest.deserialize(clientID, userID, null, 
7101                                null, globalAttributes, [roomID, roomAttributes]);
7102     theClient.synchronize(clientManifest);
7103     
7104     // If the client is observed, don't fire JOIN; observed clients always
7105     // fire JOIN based on observation updates. Likewise, don't fire JOIN
7106     // on self; self fires JOIN when it receives a u6.
7107     if (!this.clientMan.isObservingClient(clientID)) {
7108       theClient.fireJoinRoom(theRoom, roomID);
7109     }
7110   }
7111 
7112   // Add the client to the given room.
7113   theRoom.addOccupant(theClient);
7114 };
7115 
7116 /**
7117  * CLIENT_REMOVED_FROM_ROOM
7118  */
7119 net.user1.orbiter.CoreMessageListener.prototype.u37 = function (roomID, 
7120                                                                 clientID) {
7121   // Remove the room from the client's list of occupied rooms
7122   var theClient = this.clientMan.requestClient(clientID);
7123   var theRoom = this.roomMan.getRoom(roomID);
7124 
7125   // Remove the client from the given room
7126   theRoom.removeOccupant(clientID);
7127   
7128   // Don't fire LEAVE on self; self fires LEAVE when it receives a u44.
7129   if (!theClient.isSelf()) {
7130     // If the client is observed, don't fire LEAVE; observed clients always
7131     // fire LEAVE based on observation updates.
7132     if (!this.clientMan.isObservingClient(clientID)) {
7133       theClient.fireLeaveRoom(theRoom, roomID);
7134     }
7135   }
7136 };
7137 
7138 /**
7139  * ROOMLIST_SNAPSHOT
7140  */
7141 net.user1.orbiter.CoreMessageListener.prototype.u38 = function (requestID,
7142                                                                 requestedRoomIDQualifier, 
7143                                                                 recursive) {
7144   var args = Array.prototype.slice.call(arguments).slice(3);
7145   var roomQualifier;
7146   var roomIDs;
7147   var roomList = [];
7148   
7149   if (requestID == "") {
7150     // Synchronize
7151     for (var i = 0; i < args.length; i+=2) {
7152       roomQualifier = args[i];
7153       roomIDs       = args[i+1].split(net.user1.orbiter.Tokens.RS);
7154       
7155       this.roomMan.setWatchedRooms(roomQualifier, roomIDs);
7156     }
7157   } else {
7158     // Snapshot
7159     for (i = 0; i < args.length; i+=2) {
7160       roomQualifier = args[i];
7161       roomIDs = args[i+1].split(net.user1.orbiter.Tokens.RS);
7162       for (var j = 0; j < roomIDs.length; j++) {
7163         roomList.push(roomQualifier + (roomQualifier == "" ? "" : ".") + roomIDs[j]);
7164       }
7165     }
7166     this.snapshotMan.receiveRoomListSnapshot(requestID, roomList, requestedRoomIDQualifier, recursive == "true");
7167   }
7168 };
7169 
7170 /**
7171  * ROOM_ADDED
7172  */
7173 net.user1.orbiter.CoreMessageListener.prototype.u39 = function (roomID) { 
7174   // Add the room 
7175   this.roomMan.addWatchedRoom(roomID);
7176 };
7177 
7178 /**
7179  * ROOM_REMOVED
7180  */
7181 net.user1.orbiter.CoreMessageListener.prototype.u40 = function (roomID) {
7182   this.roomMan.removeWatchedRoom(roomID);
7183   if (this.roomMan.getRoom(roomID) != null) {
7184     this.roomMan.disposeRoom(roomID);
7185   }
7186 };
7187 
7188 /**
7189  * WATCH_FOR_ROOMS_RESULT
7190  */
7191 net.user1.orbiter.CoreMessageListener.prototype.u42 = function (roomIdQualifier, recursive, status) { 
7192   // Broadcast the result of the observation attempt.
7193   this.roomMan.fireWatchForRoomsResult(roomIdQualifier, status);
7194   switch (status) {
7195     case net.user1.orbiter.Status.SUCCESS:
7196     case net.user1.orbiter.Status.ERROR:
7197     case net.user1.orbiter.Status.INVALID_QUALIFIER:
7198     case net.user1.orbiter.Status.ALREADY_WATCHING:
7199     case net.user1.orbiter.Status.PERMISSION_DENIED:
7200       break;
7201       
7202     default: 
7203       this.log.warn("Unrecognized status code for u42."
7204         + " Room ID Qualifier: [" + roomIdQualifier + "], recursive: [" 
7205         + recursive + "], status: [" + status + "].");
7206   }
7207 };
7208 
7209 /**
7210  * STOP_WATCHING_FOR_ROOMS_RESULT
7211  */
7212 net.user1.orbiter.CoreMessageListener.prototype.u43 = function (roomIdQualifier, recursive, status) {
7213   switch (status) {
7214     case net.user1.orbiter.Status.SUCCESS:
7215       if (roomIdQualifier == "" && recursive == "true") {
7216         this.roomMan.removeAllWatchedRooms();
7217       } else {
7218         // Remove all watched rooms for the qualifier
7219         this.roomMan.setWatchedRooms(roomIdQualifier, []);
7220       }
7221     case net.user1.orbiter.Status.ERROR:
7222     case net.user1.orbiter.Status.NOT_WATCHING:
7223     case net.user1.orbiter.Status.INVALID_QUALIFIER:
7224       this.roomMan.fireStopWatchingForRoomsResult(roomIdQualifier, status);
7225       break;
7226       
7227     default: 
7228       this.log.warn("Unrecognized status code for u43."
7229         + " Room ID Qualifier: [" + roomIdQualifier + "], recursive: [" 
7230         + recursive + "], status: [" + status + "].");
7231   }
7232 };
7233 
7234 /**
7235  * LEFT_ROOM
7236  */
7237 net.user1.orbiter.CoreMessageListener.prototype.u44 = function (roomID) {
7238   var leftRoom = this.roomMan.getRoom(roomID);
7239   this.roomMan.removeOccupiedRoom(roomID);
7240   if (leftRoom != null) {
7241     leftRoom.doLeave();
7242     this.clientMan.self().fireLeaveRoom(leftRoom, roomID);
7243   }
7244 };
7245 
7246 /**
7247  * CHANGE_ACCOUNT_PASSWORD_RESULT
7248  */
7249 net.user1.orbiter.CoreMessageListener.prototype.u46 = function (userID, status) {
7250   var account = this.accountMan.getAccount(userID); 
7251   if (account != null) {
7252     account.fireChangePasswordResult(status);
7253   }
7254   this.accountMan.fireChangePasswordResult(userID, status);
7255 };
7256 
7257 /**
7258  * CREATE_ACCOUNT_RESULT
7259  */
7260 net.user1.orbiter.CoreMessageListener.prototype.u47 = function (userID, status) {
7261   switch (status) {
7262     case net.user1.orbiter.Status.SUCCESS:
7263     case net.user1.orbiter.Status.ERROR:
7264     case net.user1.orbiter.Status.ACCOUNT_EXISTS:
7265     case net.user1.orbiter.Status.PERMISSION_DENIED:
7266      this.orbiter.getAccountManager().fireCreateAccountResult(userID, status);
7267       break;
7268     default: 
7269       this.log.warn("Unrecognized status code for u47."
7270         + " Account: [" + userID + "], status: [" + status + "].");
7271   }
7272 };
7273 
7274 /**
7275  * REMOVE_ACCOUNT_RESULT
7276  */
7277 net.user1.orbiter.CoreMessageListener.prototype.u48 = function (userID, status) {
7278   switch (status) {
7279     case net.user1.orbiter.Status.SUCCESS:
7280     case net.user1.orbiter.Status.ERROR:
7281     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7282     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7283     case net.user1.orbiter.Status.PERMISSION_DENIED:
7284      this.orbiter.getAccountManager().fireRemoveAccountResult(userID, status);
7285       break;
7286     default: 
7287       this.log.warn("Unrecognized status code for u48."
7288         + " Account: [" + userID + "], status: [" + status + "].");
7289   }
7290 };
7291 
7292 /**
7293  * LOGIN_RESULT
7294  */
7295 net.user1.orbiter.CoreMessageListener.prototype.u49 = function (userID, status) {
7296   switch (status) {
7297     case net.user1.orbiter.Status.SUCCESS:
7298     case net.user1.orbiter.Status.ERROR:
7299     case net.user1.orbiter.Status.ALREADY_LOGGED_IN:
7300     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7301     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7302     case net.user1.orbiter.Status.PERMISSION_DENIED:
7303       this.orbiter.getAccountManager().fireLoginResult(userID, status);
7304       break;
7305     default:
7306       this.log.warn("Unrecognized status code for u49."
7307         + " Account: [" + userID + "], status: [" + status + "].");
7308   }          
7309 };
7310 
7311 /**
7312  * ROOM_SNAPSHOT
7313  */
7314 net.user1.orbiter.CoreMessageListener.prototype.u54 = function (requestID,
7315                                                                 roomID,
7316                                                                 occupantCount,
7317                                                                 observerCount,
7318                                                                 roomAttributes) {
7319   var clientList = Array.prototype.slice.call(arguments).slice(5);
7320   var clientManifest;
7321   var roomManifest = new net.user1.orbiter.RoomManifest();
7322   var theRoom;
7323   roomManifest.deserialize(roomID, 
7324                            roomAttributes, 
7325                            clientList, 
7326                            parseInt(occupantCount), 
7327                            parseInt(observerCount));
7328   
7329   if (requestID == "") {
7330     // Synchronize
7331     theRoom = this.roomMan.getRoom(roomID);
7332 
7333     if (theRoom == null) { 
7334       // If the server makes the current client join or observe a room, it
7335       // will first send a u54 before sending the u6 or u59 notice. In that
7336       // case, the room might be unknown briefly, so create a cached room
7337       // then wait for the u6 or u59 to arrive.
7338       theRoom = this.roomMan.addCachedRoom(roomID); 
7339     }
7340     
7341     theRoom.synchronize(roomManifest);
7342   } else {
7343     // Snapshot
7344     this.snapshotMan.receiveRoomSnapshot(requestID, roomManifest);
7345   }
7346 };
7347 
7348 
7349 /**
7350  * OBSERVED_ROOM
7351  */
7352 net.user1.orbiter.CoreMessageListener.prototype.u59 = function (roomID) {
7353   // Add the room to the observed room list
7354   var room = this.roomMan.addObservedRoom(roomID);
7355   // Tell the room to do its join duties
7356   room.doObserve();
7357   // Fire OBSERVE through the client
7358   this.clientMan.self().fireObserveRoom(room, roomID);
7359 };
7360 
7361 /**
7362  * GET_ROOM_SNAPSHOT_RESULT
7363  */
7364 net.user1.orbiter.CoreMessageListener.prototype.u60 = function (requestID,
7365                                                                 roomID,
7366                                                                 status) {
7367   switch (status) {
7368     case net.user1.orbiter.Status.SUCCESS:
7369     case net.user1.orbiter.Status.ERROR:
7370     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7371     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7372     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7373     case net.user1.orbiter.Status.PERMISSION_DENIED:
7374       this.snapshotMan.receiveSnapshotResult(requestID, status);
7375       break;
7376     default:
7377       this.log.warn("Unrecognized status code for u60."
7378         + " Request ID: [" + requestID + "], Room ID: [" 
7379         + roomID + "], status: [" + status + "].");
7380   } 
7381 };
7382 
7383 /**
7384  * STOPPED_OBSERVING_ROOM
7385  */
7386 net.user1.orbiter.CoreMessageListener.prototype.u62 = function (roomID) {
7387   var theRoom = this.roomMan.getRoom(roomID);
7388   this.roomMan.removeObservedRoom(roomID);
7389   if (theRoom != null) {
7390     theRoom.doStopObserving();
7391     // self() might return null if a STOP_OBSERVING listener has closed the connection
7392     if (this.clientMan.self() != null) {   
7393       this.clientMan.self().fireStopObservingRoom(theRoom, roomID);
7394     }
7395   }
7396 };
7397 
7398 /**
7399  * SERVER_HELLO
7400  */
7401 net.user1.orbiter.CoreMessageListener.prototype.u66 = function (serverVersion, 
7402                                                                 sessionID,
7403                                                                 serverUPCVersionString,
7404                                                                 protocolCompatible,
7405                                                                 affinityAddress,
7406                                                                 affinityDuration) {
7407   this.log.info("[ORBITER] Server version: " + serverVersion);
7408   this.log.info("[ORBITER] Server UPC version: " + serverUPCVersionString);
7409   
7410   var serverUPCVersion = new net.user1.orbiter.VersionNumber();
7411   serverUPCVersion.fromVersionString(serverUPCVersionString);
7412   this.orbiter.getServer().setVersion(serverVersion);
7413   this.orbiter.getServer().setUPCVersion(serverUPCVersion);
7414 
7415 
7416   var inProgressConnection = this.orbiter.getConnectionManager().getInProgressConnection();
7417   var inProgressConnectionHost = inProgressConnection.getHost();
7418   if (affinityAddress != ""  
7419       && typeof affinityAddress !== "undefined"
7420       && affinityAddress != inProgressConnectionHost) {
7421     this.orbiter.getConnectionManager().setAffinity(inProgressConnectionHost, 
7422                                                     affinityAddress, 
7423                                                     parseFloat(affinityDuration));
7424     inProgressConnection.applyAffinity();
7425   }
7426 };
7427 
7428 
7429 /**
7430  * JOIN_ROOM_RESULT
7431  */
7432 net.user1.orbiter.CoreMessageListener.prototype.u72 = function (roomID,
7433                                                                status) {
7434   var theRoom = this.roomMan.getRoom(roomID);
7435   switch (status) {
7436     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7437       if (this.roomMan.getRoom(roomID) != null) {
7438         this.roomMan.disposeRoom(roomID);
7439       }
7440     
7441     case net.user1.orbiter.Status.ERROR:
7442     case net.user1.orbiter.Status.ROOM_FULL:
7443     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7444     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7445     case net.user1.orbiter.Status.SUCCESS:
7446     case net.user1.orbiter.Status.ALREADY_IN_ROOM:
7447     case net.user1.orbiter.Status.PERMISSION_DENIED:
7448       this.roomMan.fireJoinRoomResult(roomID, status);
7449       if (theRoom != null) {
7450         theRoom.doJoinResult(status);
7451       }
7452       break;
7453     
7454     default: 
7455       this.log.warn("Unrecognized status code for u72."
7456         + " Room ID: [" + roomID + "], status: [" + status + "].");
7457   }
7458 };
7459 
7460 /**
7461  * SET_CLIENT_ATTR_RESULT
7462  */
7463 net.user1.orbiter.CoreMessageListener.prototype.u73 = function (attrScope,
7464                                                                 clientID,
7465                                                                 userID,
7466                                                                 attrName,
7467                                                                 attrOptions,
7468                                                                 status) { 
7469   var theClient;
7470   var theAccount;
7471   
7472   switch (status) {
7473     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7474     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7475       break;
7476       
7477     case net.user1.orbiter.Status.SUCCESS:
7478     case net.user1.orbiter.Status.ERROR:
7479     case net.user1.orbiter.Status.DUPLICATE_VALUE:
7480     case net.user1.orbiter.Status.IMMUTABLE:
7481     case net.user1.orbiter.Status.SERVER_ONLY:
7482     case net.user1.orbiter.Status.EVALUATION_FAILED:
7483     case net.user1.orbiter.Status.PERMISSION_DENIED:
7484       if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7485         // Persistent attr
7486         theAccount = this.accountMan.requestAccount(userID);
7487         theAccount.getAttributeManager().fireSetAttributeResult(attrName, attrScope, status);
7488       } else {
7489         // Non-persistent attr
7490         theClient = this.clientMan.requestClient(clientID);
7491         theClient.getAttributeManager().fireSetAttributeResult(attrName, attrScope, status);
7492       }
7493       break;
7494       
7495     default:
7496       this.log.warn("Unrecognized status received for u73: " + status);
7497   }
7498 };
7499 
7500 /**
7501  * SET_ROOM_ATTR_RESULT
7502  */
7503 net.user1.orbiter.CoreMessageListener.prototype.u74 = function (roomID, 
7504                                                                 attrName,
7505                                                                 status) {
7506   var theRoom = this.roomMan.getRoom(roomID);
7507   
7508   // Quit if the room isn't found
7509   if (theRoom == null) {
7510     this.log.warn("Room attribute update received for room with no" + 
7511       " client-side Room object. Room ID [" +
7512       roomID + "]. Attribute: [" + attrName + "]. Status: ["
7513       + status + "].");
7514     return;
7515   }
7516   
7517   switch (status) {
7518     case net.user1.orbiter.Status.SUCCESS:
7519     case net.user1.orbiter.Status.ERROR:
7520     case net.user1.orbiter.Status.IMMUTABLE:
7521     case net.user1.orbiter.Status.SERVER_ONLY:
7522     case net.user1.orbiter.Status.EVALUATION_FAILED:
7523     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7524     case net.user1.orbiter.Status.PERMISSION_DENIED:
7525       theRoom.getAttributeManager().fireSetAttributeResult(attrName, 
7526                                                            net.user1.orbiter.Tokens.GLOBAL_ATTR,
7527                                                            status);
7528       break;
7529     
7530     default:
7531       this.log.warn("Unrecognized status received for u74: " + status);
7532   }
7533 };
7534 
7535 /**
7536  * GET_CLIENTCOUNT_SNAPSHOT_RESULT
7537  */
7538 net.user1.orbiter.CoreMessageListener.prototype.u75 = function (requestID,
7539                                                                 status) {
7540   this.snapshotMan.receiveSnapshotResult(requestID, status);
7541 };
7542 
7543 /**
7544  * LEAVE_ROOM_RESULT
7545  */
7546 net.user1.orbiter.CoreMessageListener.prototype.u76 = function (roomID, 
7547                                                                 status) {
7548   var leftRoom = this.roomMan.getRoom(roomID);
7549   
7550   switch (status) {
7551     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7552       if (leftRoom != null) {
7553         this.roomMan.disposeRoom(roomID);
7554       }
7555       
7556     case net.user1.orbiter.Status.SUCCESS:
7557     case net.user1.orbiter.Status.ERROR:
7558     case net.user1.orbiter.Status.NOT_IN_ROOM:
7559       this.roomMan.fireLeaveRoomResult(roomID, status);
7560       if (leftRoom != null) {
7561         leftRoom.doLeaveResult(status);
7562       }
7563       break;
7564     
7565     default: 
7566       this.log.warn("Unrecognized status code for u76."
7567         + " Room ID: [" + roomID + "]. Status: [" + status + "].");        
7568   }
7569 };
7570 
7571 /**
7572  * OBSERVE_ROOM_RESULT
7573  */
7574 net.user1.orbiter.CoreMessageListener.prototype.u77 = function (roomID,
7575                       status) {
7576   var theRoom = this.roomMan.getRoom(roomID);
7577   switch (status) { 
7578     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7579       if (theRoom != null) {
7580         this.roomMan.disposeRoom(roomID);
7581       }
7582     
7583     case net.user1.orbiter.Status.ERROR:
7584     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7585     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7586     case net.user1.orbiter.Status.SUCCESS:
7587     case net.user1.orbiter.Status.ALREADY_OBSERVING:
7588     case net.user1.orbiter.Status.PERMISSION_DENIED:
7589       this.roomMan.fireObserveRoomResult(roomID, status);
7590       
7591       if (theRoom) {
7592         theRoom.doObserveResult(status);
7593       }
7594       break;
7595     
7596     default:
7597       this.log.warn("Unrecognized status code for u77."
7598         + " Room ID: [" + roomID + "], status: " + status + ".");
7599   }
7600 }    
7601 
7602 /**
7603  * STOP_OBSERVING_ROOM_RESULT
7604  */
7605 net.user1.orbiter.CoreMessageListener.prototype.u78 = function (roomID, 
7606                                                                 status) {
7607   var theRoom = this.roomMan.getRoom(roomID);
7608   
7609   switch (status) {
7610     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7611       if (theRoom != null) {
7612         this.roomMan.disposeRoom(roomID);
7613       }
7614     
7615     case net.user1.orbiter.Status.SUCCESS:
7616     case net.user1.orbiter.Status.ERROR:
7617     case net.user1.orbiter.Status.NOT_OBSERVING:
7618       this.roomMan.fireStopObservingRoomResult(roomID, status);
7619       
7620       if (theRoom != null) {
7621         theRoom.doStopObservingResult(status);
7622       }
7623       break;
7624     
7625     default: 
7626       this.log.warn("Unrecognized status code for u78."
7627         + " Room ID: [" + roomID + "], status: " + status + ".");        
7628   }
7629 };
7630 
7631 /**
7632  * ROOM_ATTR_REMOVED
7633  */
7634 net.user1.orbiter.CoreMessageListener.prototype.u79 = function (roomID,
7635                                                                 byClientID,
7636                                                                 attrName) {
7637   var theRoom = this.roomMan.getRoom(roomID);
7638   var theClient;
7639   
7640   // Quit if the room isn't found
7641   if (theRoom == null) {
7642     this.log.warn("Room attribute removal notification received for room with no" + 
7643       " client-side Room object. Room ID [" +
7644       roomID + "]. Attribute: [" + attrName + "].");
7645     return;
7646   }
7647   
7648   // If the clientID is "", the server removed the room, so there's no
7649   // corresponding client.
7650   theClient = byClientID == "" ? null : this.clientMan.requestClient(byClientID);
7651   theRoom.getAttributeManager().removeAttributeLocal(attrName, net.user1.orbiter.Tokens.GLOBAL_ATTR, theClient)
7652 }
7653 
7654 /**
7655  * REMOVE_ROOM_ATTR_RESULT
7656  */
7657 net.user1.orbiter.CoreMessageListener.prototype.u80 = function (roomID,
7658                                                                 attrName,
7659                                                                 status) {
7660   var theRoom = this.roomMan.getRoom(roomID);      
7661   switch (status) {
7662     case net.user1.orbiter.Status.SUCCESS:
7663     case net.user1.orbiter.Status.ERROR:
7664     case net.user1.orbiter.Status.IMMUTABLE:
7665     case net.user1.orbiter.Status.SERVER_ONLY:
7666     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7667     case net.user1.orbiter.Status.ATTR_NOT_FOUND:
7668     case net.user1.orbiter.Status.PERMISSION_DENIED:
7669       if (theRoom != null) {
7670         theRoom.getAttributeManager().fireDeleteAttributeResult(attrName,
7671                                                                 net.user1.orbiter.Tokens.GLOBAL_ATTR,
7672                                                                 status);
7673       }
7674       break;
7675     
7676     default:
7677       this.log.warn("Unrecognized status received for u80: " + status);
7678   }
7679 };  
7680 
7681 /**
7682  * CLIENT_ATTR_REMOVED
7683  */
7684 net.user1.orbiter.CoreMessageListener.prototype.u81 = function (attrScope,
7685                                                                 clientID,
7686                                                                 userID, 
7687                                                                 attrName,
7688                                                                 attrOptions) {
7689   var client;
7690   var account;
7691   
7692   if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7693     // Persistent attr
7694     account = this.accountMan.requestAccount(userID);
7695     account.getAttributeManager().removeAttributeLocal(attrName, attrScope);
7696   } else {
7697     // Non-persistent attr
7698     client = this.clientMan.requestClient(clientID);
7699     client.getAttributeManager().removeAttributeLocal(attrName, attrScope);
7700   }
7701 };
7702 
7703 /**
7704  * REMOVE_CLIENT_ATTR_RESULT
7705  */
7706 net.user1.orbiter.CoreMessageListener.prototype.u82 = function (attrScope,
7707                                                                 clientID,
7708                                                                 userID,
7709                                                                 attrName,
7710                                                                 attrOptions,
7711                                                                 status) { 
7712   var client;
7713   var account;
7714   
7715   
7716   switch (status) {
7717     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7718     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7719       break;
7720     case net.user1.orbiter.Status.SUCCESS:
7721     case net.user1.orbiter.Status.ERROR:
7722     case net.user1.orbiter.Status.IMMUTABLE:
7723     case net.user1.orbiter.Status.SERVER_ONLY:
7724     case net.user1.orbiter.Status.ATTR_NOT_FOUND: 
7725     case net.user1.orbiter.Status.EVALUATION_FAILED:
7726     case net.user1.orbiter.Status.PERMISSION_DENIED:
7727       if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7728         // Persistent attr
7729         account = this.accountMan.requestAccount(userID);
7730         account.getAttributeManager().fireDeleteAttributeResult(attrName, attrScope, status);
7731       } else {
7732         // Non-persistent attr
7733         client = this.clientMan.requestClient(clientID);
7734         client.getAttributeManager().fireDeleteAttributeResult(attrName, attrScope, status);
7735       }
7736       break;
7737       
7738     default:
7739       this.log.warn("Unrecognized status received for u82: " + status);
7740   }
7741 };
7742 
7743 /**
7744  * LOGOFF_RESULT
7745  */
7746 net.user1.orbiter.CoreMessageListener.prototype.u87 = function (userID, status) {
7747   var account = this.accountMan.getAccount(userID);
7748   
7749   switch (status) {
7750     case net.user1.orbiter.Status.SUCCESS:
7751     case net.user1.orbiter.Status.ERROR:
7752     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7753     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7754     case net.user1.orbiter.Status.NOT_LOGGED_IN:
7755     case net.user1.orbiter.Status.PERMISSION_DENIED:
7756       if (account != null) {
7757         account.fireLogoffResult(status);
7758       }
7759       // Tell the account manager
7760       this.accountMan.fireLogoffResult(userID, status);
7761       break;
7762     default:
7763       this.log.warn("Unrecognized status received for u87: " + status);
7764   }
7765 };    
7766 
7767 /**
7768  * LOGGED_IN
7769  */
7770 net.user1.orbiter.CoreMessageListener.prototype.u88 = function (clientID, 
7771                                                                 userID,
7772                                                                 globalAttrs) {
7773   var roomAttrs = Array.prototype.slice.call(arguments).slice(3);
7774   var account = this.accountMan.requestAccount(userID);
7775   var client = this.clientMan.requestClient(clientID);
7776   var clientManifest = new net.user1.orbiter.ClientManifest();
7777   clientManifest.deserialize(clientID, userID, null, null, globalAttrs, roomAttrs);      
7778   // Update the account
7779   var scopes = clientManifest.persistentAttributes.getScopes();
7780   var accountAttrs = account.getAttributeManager().getAttributeCollection();
7781   for (var i = scopes.length; --i >= 0;) {
7782     accountAttrs.synchronizeScope(scopes[i], clientManifest.persistentAttributes);
7783   }
7784   
7785   if (client.getAccount() == null) {
7786     // Client doesn't know about this account yet
7787     client.setAccount(account);
7788     client.fireLogin();
7789     account.doLoginTasks();
7790     this.accountMan.fireLogin(account, clientID);
7791   } else {
7792     // Do nothing if the account is known. Logins are reported for 
7793     // observe-account, observe-client, and watch-for-clients, so a 
7794     // client might receive multiple login notifications.
7795   }
7796 };
7797 
7798 /**
7799  * LOGGED_OFF
7800  */
7801 net.user1.orbiter.CoreMessageListener.prototype.u89 = function (clientID, userID) {
7802   var client = this.clientMan.getInternalClient(clientID);
7803   var account = this.accountMan.getAccount(userID);
7804   
7805   if (account != null) {
7806     if (account.getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN) {
7807       if (client != null) {
7808         client.fireLogoff(userID);
7809       }
7810       account.doLogoffTasks();
7811       this.accountMan.fireLogoff(account, clientID);
7812     } else {
7813       // Do nothing if the account is unknown. Logoffs are reported for 
7814       // observe-account, observe-client, and watch-for-clients, so a 
7815       // client might receive multiple logoff notifications.
7816     }
7817   } else {
7818     throw new Error("LOGGED_OFF (u89) received for an unknown user: [" + userID + "].");
7819   }
7820 }
7821 
7822 /**
7823  * PASSWORD_CHANGED
7824  */
7825 net.user1.orbiter.CoreMessageListener.prototype.u90 = function () {
7826   var self = this.orbiter.self();
7827   var selfAccount = self.getAccount();
7828   if (selfAccount != null) {
7829     selfAccount.fireChangePassword();
7830   }
7831   this.accountMan.fireChangePassword(selfAccount ? selfAccount.getUserID() : null);
7832 };
7833 
7834 /**
7835  * CLIENTLIST_SNAPSHOT
7836  */
7837 net.user1.orbiter.CoreMessageListener.prototype.u101 = function (requestID, serializedIDs) {
7838   var ids = serializedIDs.split(net.user1.orbiter.Tokens.RS);
7839   var clientList;
7840   var thisUserID;
7841   
7842   if (requestID == "") {
7843     // Synchronize
7844     this.clientMan.deserializeWatchedClients(serializedIDs);
7845   } else {
7846     // Snapshot
7847     clientList = [];
7848     for (var i = ids.length-1; i >= 0; i-=2) {
7849       thisUserID = ids[i];
7850       thisUserID = thisUserID == "" ? null : thisUserID;
7851       clientList.push({clientID:ids[i-1], userID:thisUserID});
7852     }
7853     this.snapshotMan.receiveClientListSnapshot(requestID, clientList);
7854   }      
7855 };    
7856 
7857 /**
7858  * CLIENT_ADDED_TO_SERVER
7859  */
7860 net.user1.orbiter.CoreMessageListener.prototype.u102 = function (clientID) {
7861   this.clientMan.addWatchedClient(this.clientMan.requestClient(clientID));
7862 };
7863 
7864 /**
7865  * CLIENT_REMOVED_FROM_SERVER
7866  */
7867 net.user1.orbiter.CoreMessageListener.prototype.u103 = function (clientID) {
7868   var client = this.clientMan.getInternalClient(clientID);
7869   
7870   if (this.clientMan.hasWatchedClient(clientID)) {
7871     this.clientMan.removeWatchedClient(clientID);
7872   }
7873   if (this.clientMan.isObservingClient(clientID)) {
7874     this.clientMan.removeObservedClient(clientID);
7875   }
7876   
7877   // If the current client is both observing a client and watching for clients,
7878   // it will receive two u103 notifications. When the second one arrives, the
7879   // client will be unknown, so no disconnection event should be dispatched.
7880   if (client != null) {
7881     client.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
7882     // Retrieve the client reference using getClient() here so that the
7883     // ClientManagerEvent.CLIENT_DISCONNECTED event provides the application
7884     // with access to the custom client, if available.
7885     this.clientMan.fireClientDisconnected(this.clientMan.getClient(clientID));
7886   }
7887 };
7888 
7889 /**
7890  * CLIENT_SNAPSHOT
7891  */
7892 net.user1.orbiter.CoreMessageListener.prototype.u104 = function (requestID,
7893                                                                  clientID,
7894                                                                  userID,
7895                                                                  serializedOccupiedRoomIDs,
7896                                                                  serializedObservedRoomIDs,
7897                                                                  globalAttrs) {
7898   var roomAttrs = Array.prototype.slice.call(arguments).slice(7);
7899   var theClient;
7900   var account = this.accountMan.requestAccount(userID);
7901   var clientManifest = new net.user1.orbiter.ClientManifest();
7902   clientManifest.deserialize(clientID, userID, serializedOccupiedRoomIDs, 
7903                              serializedObservedRoomIDs, globalAttrs, roomAttrs);
7904   var scopes; // Used with UserAccount only
7905   
7906   if (clientID != "") {  
7907     // --- Client update ---
7908     
7909     if (requestID == "") {
7910       // Synchronize
7911       theClient = this.clientMan.requestClient(clientID);
7912       theClient.setAccount(account);
7913       theClient.synchronize(clientManifest);
7914       theClient.fireSynchronize();
7915     } else {
7916       // Snapshot
7917       this.snapshotMan.receiveClientSnapshot(requestID, clientManifest);
7918     }
7919   } else {  
7920     // --- User account update ---
7921 
7922     if (requestID == "") {
7923       // Synchronize
7924       scopes = clientManifest.persistentAttributes.getScopes();
7925       for (var i = scopes.length; --i >= 0;) {
7926         account.getAttributeManager().getAttributeCollection().synchronizeScope(scopes[i], clientManifest.persistentAttributes);
7927       }
7928       account.fireSynchronize();
7929     } else {
7930       // Snapshot
7931       this.snapshotMan.receiveAccountSnapshot(requestID, clientManifest);
7932     }
7933   }
7934 };
7935 
7936 /**
7937  * OBSERVE_CLIENT_RESULT
7938  */
7939 net.user1.orbiter.CoreMessageListener.prototype.u105 = function (clientID, status) {
7940   var theClient = this.clientMan.getInternalClient(clientID);
7941   switch (status) { 
7942     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7943     case net.user1.orbiter.Status.SUCCESS:
7944     case net.user1.orbiter.Status.ERROR:
7945     case net.user1.orbiter.Status.ALREADY_OBSERVING:
7946     case net.user1.orbiter.Status.PERMISSION_DENIED:
7947       this.clientMan.fireObserveClientResult(clientID, status);
7948       if (theClient != null) {
7949         theClient.fireObserveResult(status);
7950       }
7951       break;
7952     
7953     default:
7954       this.log.warn("Unrecognized status code for u105."
7955                + " Client ID: [" + clientID + "], status: [" + status + "].");
7956   }
7957 };    
7958 
7959 /**
7960  * STOP_OBSERVING_CLIENT_RESULT
7961  */
7962 net.user1.orbiter.CoreMessageListener.prototype.u106 = function (clientID, status) {
7963   var theClient = this.clientMan.getInternalClient(clientID);
7964   switch (status) { 
7965     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7966     case net.user1.orbiter.Status.SUCCESS:
7967     case net.user1.orbiter.Status.ERROR:
7968     case net.user1.orbiter.Status.NOT_OBSERVING:
7969       this.clientMan.fireStopObservingClientResult(clientID, status);
7970       if (theClient != null) {
7971         theClient.fireStopObservingResult(status);
7972       }
7973       break;
7974     
7975     default:
7976       this.log.warn("Unrecognized status code for u106."
7977                + " Client ID: [" + clientID + "], status: [" + status + "].");
7978   }
7979 };
7980 
7981 /**
7982  * WATCH_FOR_CLIENTS_RESULT
7983  */
7984 net.user1.orbiter.CoreMessageListener.prototype.u107 = function (status) {
7985   switch (status) { 
7986     case net.user1.orbiter.Status.SUCCESS:
7987     case net.user1.orbiter.Status.ERROR:
7988     case net.user1.orbiter.Status.ALREADY_WATCHING:
7989       this.clientMan.fireWatchForClientsResult(status);
7990       break;
7991     
7992     default:
7993       this.log.warn("Unrecognized status code for u107."
7994                     + "Status: [" + status + "].");
7995   }
7996 };
7997 
7998 /**
7999  * STOP_WATCHING_FOR_CLIENTS_RESULT
8000  */
8001 net.user1.orbiter.CoreMessageListener.prototype.u108 = function (status) {
8002   switch (status) { 
8003     case net.user1.orbiter.Status.SUCCESS:
8004       this.clientMan.setIsWatchingForClients(false);
8005       this.clientMan.removeAllWatchedClients();
8006     case net.user1.orbiter.Status.ERROR:
8007     case net.user1.orbiter.Status.NOT_WATCHING:
8008       this.clientMan.fireStopWatchingForClientsResult(status);
8009       break;
8010     
8011     default:
8012       this.log.warn("Unrecognized status code for u108."
8013                + "Status: [" + status + "].");
8014   }
8015 };    
8016 
8017 /**
8018  * WATCH_FOR_USERS_RESULT
8019  */
8020 net.user1.orbiter.CoreMessageListener.prototype.u109 = function (status) {
8021   switch (status) { 
8022     case net.user1.orbiter.Status.SUCCESS:
8023       this.accountMan.setIsWatchingForAccounts(true);
8024     case net.user1.orbiter.Status.ERROR:
8025     case net.user1.orbiter.Status.ALREADY_WATCHING:
8026       this.accountMan.fireWatchForAccountsResult(status);
8027       break;
8028     
8029     default:
8030       this.log.warn("Unrecognized status code for u109."
8031                + "Status: [" + status + "].");
8032   }
8033 };  
8034 
8035 /**
8036  * STOP_WATCHING_FOR_USERS_RESULT
8037  */
8038 net.user1.orbiter.CoreMessageListener.prototype.u110 = function (status) {
8039   switch (status) { 
8040     case net.user1.orbiter.Status.SUCCESS:
8041       this.accountMan.setIsWatchingForAccounts(false);
8042       this.accountMan.removeAllWatchedAccounts();
8043     case net.user1.orbiter.Status.ERROR:
8044     case net.user1.orbiter.Status.NOT_WATCHING:
8045       this.accountMan.fireStopWatchingForAccountsResult(status);
8046       break;
8047     
8048     default:
8049       this.log.warn("Unrecognized status code for u110."
8050                + "Status: [" + status + "].");
8051   }
8052 };    
8053 
8054 /**
8055  * USER_ADDED
8056  */
8057 net.user1.orbiter.CoreMessageListener.prototype.u111 = function (userID) {
8058   this.accountMan.addWatchedAccount(this.accountMan.requestAccount(userID));
8059 };    
8060 
8061 /**
8062  * USER_REMOVED
8063  */
8064 net.user1.orbiter.CoreMessageListener.prototype.u112 = function (userID) {
8065   var account;
8066   if (this.accountMan.hasWatchedAccount(userID)) {
8067     account = this.accountMan.removeWatchedAccount(userID);
8068   }
8069   if (this.accountMan.isObservingAccount(userID)) {
8070     account = this.accountMan.removeObservedAccount(userID);
8071   }
8072   this.accountMan.fireAccountRemoved(userID, account);
8073 };    
8074 
8075 /**
8076  * JOINED_ROOM_ADDED_TO_CLIENT
8077  */
8078 net.user1.orbiter.CoreMessageListener.prototype.u113 = function (clientID, roomID) {
8079   var client = this.clientMan.requestClient(clientID);
8080   client.addOccupiedRoomID(roomID);
8081   client.fireJoinRoom(this.roomMan.getRoom(roomID), roomID);
8082 }    
8083 
8084 /**
8085  * JOINED_ROOM_REMOVED_FROM_CLIENT
8086  */
8087 net.user1.orbiter.CoreMessageListener.prototype.u114 = function (clientID, roomID) {
8088   var client = this.clientMan.requestClient(clientID);
8089   client.removeOccupiedRoomID(roomID);
8090   client.fireLeaveRoom(this.roomMan.getRoom(roomID), roomID);
8091 };    
8092 
8093 /**
8094  * GET_CLIENT_SNAPSHOT_RESULT
8095  */
8096 net.user1.orbiter.CoreMessageListener.prototype.u115 = function (requestID,
8097                                                                  clientID, 
8098                                                                  status) {
8099   this.snapshotMan.receiveSnapshotResult(requestID, status);
8100 };
8101 
8102 /**
8103  * GET_ACCOUNT_SNAPSHOT_RESULT
8104  */
8105 net.user1.orbiter.CoreMessageListener.prototype.u116 = function (requestID,
8106                                                                  userID, 
8107                                                                  status) {
8108   this.snapshotMan.receiveSnapshotResult(requestID, status);
8109 };
8110 
8111 /**
8112  * OBSERVED_ROOM_ADDED_TO_CLIENT
8113  */
8114 net.user1.orbiter.CoreMessageListener.prototype.u117 = function (clientID, roomID) {
8115   var client = this.clientMan.requestClient(clientID);
8116   client.addObservedRoomID(roomID);
8117   client.fireObserveRoom(this.roomMan.getRoom(roomID), roomID);
8118 };    
8119 
8120 /**
8121  * OBSERVED_ROOM_REMOVED_FROM_CLIENT
8122  */
8123 net.user1.orbiter.CoreMessageListener.prototype.u118 = function (clientID, roomID) {
8124   var client = this.clientMan.requestClient(clientID);
8125   client.removeObservedRoomID(roomID);
8126   client.fireStopObservingRoom(this.roomMan.getRoom(roomID), roomID);
8127 }    
8128 
8129 /**
8130  * CLIENT_OBSERVED
8131  */
8132 net.user1.orbiter.CoreMessageListener.prototype.u119 = function (clientID) {
8133   var client = this.clientMan.requestClient(clientID);
8134   this.clientMan.addObservedClient(client);
8135   client.fireObserve();
8136 };
8137 
8138 /**
8139  * STOPPED_OBSERVING_CLIENT
8140  */
8141 net.user1.orbiter.CoreMessageListener.prototype.u120 = function (clientID) {
8142   var client = this.clientMan.getInternalClient(clientID)
8143   this.clientMan.removeObservedClient(clientID);
8144   if (client != null) {
8145     client.fireStopObserving();
8146   }
8147 };
8148 
8149 /**
8150  * OBSERVE_ACCOUNT_RESULT
8151  */
8152 net.user1.orbiter.CoreMessageListener.prototype.u123 = function (userID, status) {
8153   var theAccount = this.accountMan.getAccount(userID);
8154   switch (status) { 
8155     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8156     case net.user1.orbiter.Status.SUCCESS:
8157     case net.user1.orbiter.Status.ERROR:
8158     case net.user1.orbiter.Status.ALREADY_OBSERVING:
8159       this.accountMan.fireObserveAccountResult(userID, status);
8160       if (theAccount) {
8161         theAccount.fireObserveResult(status);
8162       }
8163       break;
8164     
8165     default:
8166       this.log.warn("Unrecognized status code for u123."
8167                + " User ID: [" + userID + "], status: [" + status + "].");
8168   }
8169 };
8170 
8171 /**
8172  * ACCOUNT_OBSERVED
8173  */
8174 net.user1.orbiter.CoreMessageListener.prototype.u124 = function (userID) {
8175   var theAccount = this.accountMan.requestAccount(userID);
8176   this.accountMan.addObservedAccount(theAccount);
8177   theAccount.fireObserve();
8178 };
8179 
8180 /**
8181  * STOP_OBSERVING_ACCOUNT_RESULT
8182  */
8183 net.user1.orbiter.CoreMessageListener.prototype.u125 = function (userID, status) {
8184   var theAccount = this.accountMan.getAccount(userID);
8185   switch (status) { 
8186     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8187     case net.user1.orbiter.Status.SUCCESS:
8188     case net.user1.orbiter.Status.ERROR:
8189     case net.user1.orbiter.Status.ALREADY_OBSERVING:
8190       this.accountMan.fireStopObservingAccountResult(userID, status);
8191       if (theAccount) {
8192         theAccount.fireStopObservingResult(status);
8193       }
8194       break;
8195     
8196     default:
8197       this.log.warn("Unrecognized status code for u125."
8198                + " User ID: [" + userID + "], status: [" + status + "].");
8199   }
8200 };
8201 
8202 /**
8203  * STOPPED_OBSERVING_ACCOUNT
8204  */
8205 net.user1.orbiter.CoreMessageListener.prototype.u126 = function (userID) {
8206   var account = this.accountMan.getAccount(userID);
8207   this.accountMan.removeObservedAccount(userID);
8208   if (account != null) {
8209     account.fireStopObserving();
8210   }
8211 };
8212 
8213 /**
8214  * ACCOUNT_LIST_UPDATE
8215  */
8216 net.user1.orbiter.CoreMessageListener.prototype.u127 = function (requestID, serializedIDs) {
8217   var ids = serializedIDs.split(net.user1.orbiter.Tokens.RS);
8218   var accountList;
8219   
8220   if (requestID == "") {
8221     // Synchronize
8222     this.accountMan.deserializeWatchedAccounts(serializedIDs);
8223   } else {
8224     // Snapshot
8225     accountList = [];
8226     for (var i = ids.length; --i >= 0;) {
8227       accountList.push(ids[i]);
8228     }
8229     this.snapshotMan.receiveAccountListSnapshot(requestID, accountList);
8230   }  
8231 };
8232 
8233 /**
8234  * UPDATE_LEVELS_UPDATE
8235  */
8236 net.user1.orbiter.CoreMessageListener.prototype.u128 = function (updateLevels, roomID) {
8237   var room = this.roomMan.getRoom(roomID);
8238   var levels = new net.user1.orbiter.UpdateLevels();
8239   levels.fromInt(parseInt(updateLevels));
8240   if (room != null) {
8241     if (!levels.occupantList) {
8242       var occupantID;
8243       var occupantIDs = room.getOccupantIDs();
8244       var numOccupantIDs = occupantIDs.length;
8245       for (var i = 0; i < numOccupantIDs; i++) {
8246         occupantID = occupantIDs[i];
8247         room.removeOccupant(occupantID);
8248       }
8249     }
8250     if (!levels.observerList) {
8251       var observerID;
8252       var observerIDs = room.getObserverIDs();
8253       var numObserverIDs = observerIDs.length;
8254       for (i = 0; i < numObserverIDs; i++) {
8255         observerID = observerIDs[i];
8256         room.removeObserver(observerID);
8257       }
8258     }
8259     if (!levels.sharedRoomAttributes
8260         && !levels.allRoomAttributes) {
8261       room.getAttributeManager().removeAll();
8262     }
8263   }
8264 };
8265 
8266 /**
8267  * CLIENT_OBSERVED_ROOM
8268  */
8269 net.user1.orbiter.CoreMessageListener.prototype.u129 = function (roomID,
8270                                                                  clientID,
8271                                                                  userID,
8272                                                                  globalAttributes,
8273                                                                  roomAttributes) {
8274   var theClient = this.clientMan.requestClient(clientID);
8275   var account = this.accountMan.requestAccount(userID);
8276   var clientManifest;
8277   if (account != null
8278       && theClient.getAccount() != account) {
8279     theClient.setAccount(account);
8280   }
8281 
8282   // If it's not the current client, set the client's attributes. 
8283   // (The current client obtains its own attributes through separate u8s.)
8284   var theRoom = this.roomMan.getRoom(roomID);
8285   if (!theClient.isSelf()) {
8286     clientManifest = new net.user1.orbiter.ClientManifest();
8287     clientManifest.deserialize(clientID, userID, null, 
8288                                null, globalAttributes, [roomID, roomAttributes]);
8289     theClient.synchronize(clientManifest);
8290     
8291     // If the client is observed, don't fire OBSERVE_ROOM; observed clients always
8292     // fire OBSERVE_ROOM based on observation updates. Likewise, don't fire OBSERVE_ROOM
8293     // on self; self fires OBSERVE_ROOM when it receives a u59.
8294     if (!this.clientMan.isObservingClient(clientID)) {
8295       theClient.fireObserveRoom(theRoom, roomID);
8296     }
8297   }
8298 
8299   // Add the client to the room's observer list
8300   theRoom.addObserver(theClient);
8301 };
8302 
8303 /**
8304  * CLIENT_STOPPED_OBSERVING_ROOM
8305  */
8306 net.user1.orbiter.CoreMessageListener.prototype.u130 = function (roomID, 
8307                                                                  clientID) {
8308   // Remove the room from the client's list of observed rooms
8309   var theClient = this.clientMan.requestClient(clientID);
8310   var theRoom = this.roomMan.getRoom(roomID);
8311 
8312   // Remove the client from the given room
8313   theRoom.removeObserver(clientID);
8314   
8315   // Don't fire STOP_OBSERVING_ROOM on self; self fires STOP_OBSERVING_ROOM
8316   // when it receives a u62.
8317   if (!theClient.isSelf()) {
8318     // If the client is observed, don't fire STOP_OBSERVING_ROOM; observed 
8319     // clients always fire STOP_OBSERVING_ROOM based on observation updates.
8320     if (!this.clientMan.isObservingClient(clientID)) {
8321       theClient.fireStopObservingRoom(theRoom, roomID);
8322     }
8323   }
8324 };
8325 
8326 /**
8327  * ROOM_OCCUPANTCOUNT_UPDATE
8328  */
8329 net.user1.orbiter.CoreMessageListener.prototype.u131 = function (roomID, 
8330                                                                  numClients) {
8331   var levels = this.clientMan.self().getUpdateLevels(roomID);
8332   
8333   if (levels != null) {
8334     if (!levels.occupantList) {
8335       this.roomMan.getRoom(roomID).setNumOccupants(parseInt(numClients));
8336     }
8337   } else {
8338     throw new Error("[CORE_MESSAGE_LISTENER] Received a room occupant count" +
8339       " update (u131), but update levels are unknown for the room. Synchronization" +
8340       " error. Please report this error to union@user1.net.");
8341   }
8342 };
8343 
8344 /**
8345  * ROOM_OBSERVERCOUNT_UPDATE
8346  */
8347 net.user1.orbiter.CoreMessageListener.prototype.u132 = function (roomID,  
8348                                                                  numClients) {
8349   var levels = this.clientMan.self().getUpdateLevels(roomID);
8350   
8351   if (levels != null) {
8352     if (!levels.observerList) {
8353       this.roomMan.getRoom(roomID).setNumObservers(parseInt(numClients));
8354     }
8355   } else {
8356     throw new Error("[CORE_MESSAGE_LISTENER] Received a room observer count" +
8357       " update (u132), but update levels are unknown for the room. Synchronization" +
8358       " error. Please report this error to union@user1.net.");
8359   }
8360 }
8361 
8362 /**
8363  * ADD_ROLE_RESULT
8364  */
8365 net.user1.orbiter.CoreMessageListener.prototype.u134 = function (userID, role, status) {
8366   var theAccount = this.accountMan.getAccount(userID);
8367   switch (status) { 
8368     case net.user1.orbiter.Status.SUCCESS:
8369     case net.user1.orbiter.Status.ERROR:
8370     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8371     case net.user1.orbiter.Status.ROLE_NOT_FOUND:
8372     case net.user1.orbiter.Status.ALREADY_ASSIGNED:
8373     case net.user1.orbiter.Status.PERMISSION_DENIED:
8374       this.accountMan.fireAddRoleResult(userID, role, status);
8375       if (theAccount) {
8376         theAccount.fireAddRoleResult(role, status);
8377       }
8378       break;
8379     
8380     default:
8381       this.log.warn("Unrecognized status code for u134."
8382                + " User ID: [" + userID + "], role: [" + role 
8383                + "], status: [" + status + "].");
8384   }
8385 };
8386 
8387 /**
8388  * REMOVE_ROLE_RESULT
8389  */
8390 net.user1.orbiter.CoreMessageListener.prototype.u136 = function (userID, role, status) {
8391   var theAccount = this.accountMan.getAccount(userID);
8392   switch (status) { 
8393     case net.user1.orbiter.Status.SUCCESS:
8394     case net.user1.orbiter.Status.ERROR:
8395     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8396     case net.user1.orbiter.Status.ROLE_NOT_FOUND:
8397     case net.user1.orbiter.Status.NOT_ASSIGNED:
8398     case net.user1.orbiter.Status.PERMISSION_DENIED:
8399       this.accountMan.fireRemoveRoleResult(userID, role, status);
8400       if (theAccount) {
8401         theAccount.fireRemoveRoleResult(role, status);
8402       }
8403       break;
8404     
8405     default:
8406       this.log.warn("Unrecognized status code for u136."
8407                + " User ID: [" + userID + "], role: [" + role 
8408                + "], status: [" + status + "].");
8409   }
8410 };
8411 
8412 /**
8413  * BAN_RESULT
8414  */
8415 net.user1.orbiter.CoreMessageListener.prototype.u138 = function (address, clientID, status) {
8416   switch (status) { 
8417     case net.user1.orbiter.Status.SUCCESS:
8418     case net.user1.orbiter.Status.ERROR:
8419     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
8420     case net.user1.orbiter.Status.ALREADY_BANNED:
8421     case net.user1.orbiter.Status.PERMISSION_DENIED:
8422       this.clientMan.fireBanClientResult(address, clientID, status);
8423       break;
8424     
8425     default:
8426       this.log.warn("Unrecognized status code for u138."
8427                + " Address: [" + address + "], clientID: [" + clientID 
8428                + "], status: [" + status + "].");
8429   }
8430 };
8431 
8432 /**
8433  * UNBAN_RESULT
8434  */
8435 net.user1.orbiter.CoreMessageListener.prototype.u140 = function (address, status) {
8436   switch (status) { 
8437     case net.user1.orbiter.Status.SUCCESS:
8438     case net.user1.orbiter.Status.ERROR:
8439     case net.user1.orbiter.Status.NOT_BANNED:
8440     case net.user1.orbiter.Status.PERMISSION_DENIED:
8441       this.clientMan.fireUnbanClientResult(address, status);
8442       break;
8443     
8444     default:
8445       this.log.warn("Unrecognized status code for u140."
8446                + " Address: [" + address + "],"
8447                + " status: [" + status + "].");
8448   }
8449 };
8450 
8451 /**
8452  * BANNED_LIST_SNAPSHOT
8453  */
8454 net.user1.orbiter.CoreMessageListener.prototype.u142 = function (requestID, bannedListSource) {
8455   var bannedList = bannedListSource == "" ? [] : bannedListSource.split(net.user1.orbiter.Tokens.RS);
8456   
8457   if (requestID == "") {
8458     this.clientMan.setWatchedBannedAddresses(bannedList);
8459   } else {
8460     // Snapshot
8461     this.snapshotMan.receiveBannedListSnapshot(requestID, bannedList);
8462   }
8463 };
8464 
8465 /**
8466  * WATCH_FOR_BANNED_ADDRESSES_RESULT
8467  */
8468 net.user1.orbiter.CoreMessageListener.prototype.u144 = function (status) {
8469   switch (status) { 
8470     case net.user1.orbiter.Status.SUCCESS:
8471     case net.user1.orbiter.Status.ERROR:
8472     case net.user1.orbiter.Status.ALREADY_WATCHING:
8473     case net.user1.orbiter.Status.PERMISSION_DENIED:
8474       this.clientMan.fireWatchForBannedAddressesResult(status);
8475       break;
8476     
8477     default:
8478       this.log.warn("Unrecognized status code for u144:"
8479                + " [" + status + "].");
8480   }
8481 };
8482 
8483 /**
8484  * STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT
8485  */
8486 net.user1.orbiter.CoreMessageListener.prototype.u146 = function (status) {
8487   switch (status) { 
8488     case net.user1.orbiter.Status.SUCCESS:
8489     case net.user1.orbiter.Status.ERROR:
8490     case net.user1.orbiter.Status.NOT_WATCHING:
8491       this.clientMan.fireStopWatchingForBannedAddressesResult(status);
8492       break;
8493     
8494     default:
8495       this.log.warn("Unrecognized status code for u146:"
8496                + " [" + status + "].");
8497   }
8498 };
8499 
8500 /**
8501  * BANNED_ADDRESS_ADDED
8502  */
8503 net.user1.orbiter.CoreMessageListener.prototype.u147 = function (address) {
8504   this.clientMan.addWatchedBannedAddress(address);
8505 };
8506 
8507 /**
8508  * BANNED_ADDRESS_REMOVED
8509  */
8510 net.user1.orbiter.CoreMessageListener.prototype.u148 = function (address) {
8511   this.clientMan.removeWatchedBannedAddress(address);
8512 };
8513 
8514 /**
8515  * KICK_RESULT
8516  */
8517 net.user1.orbiter.CoreMessageListener.prototype.u150 = function (clientID, status) {
8518   switch (status) { 
8519     case net.user1.orbiter.Status.SUCCESS:
8520     case net.user1.orbiter.Status.ERROR:
8521     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
8522     case net.user1.orbiter.Status.PERMISSION_DENIED:
8523       this.clientMan.fireKickClientResult(clientID, status);
8524       break;
8525     
8526     default:
8527       this.log.warn("Unrecognized status code for u150:"
8528                + " [" + status + "].");
8529   }
8530 };
8531 
8532 /**
8533  * SERVERMODULELIST_SNAPSHOT
8534  */
8535 net.user1.orbiter.CoreMessageListener.prototype.u152 = function (requestID, serverModuleListSource) {
8536   var moduleListArray = serverModuleListSource == "" ? [] : serverModuleListSource.split(net.user1.orbiter.Tokens.RS);
8537   var moduleList = [];
8538   for (var i = 0; i < moduleListArray.length; i+= 3) {
8539     moduleList.push(new ModuleDefinition(moduleListArray[i],
8540                                          moduleListArray[i+1],
8541                                          moduleListArray[i+2]));
8542   }
8543   
8544   if (requestID == "") {
8545     this.log.warn("Incoming SERVERMODULELIST_SNAPSHOT UPC missing required requestID. Ignoring message.");
8546   } else {
8547     // Snapshot
8548     this.snapshotMan.receiveServerModuleListSnapshot(requestID, moduleList);
8549   }
8550 };
8551 
8552 /**
8553  * GET_UPC_STATS_SNAPSHOT_RESULT
8554  */
8555 net.user1.orbiter.CoreMessageListener.prototype.u155 = function (requestID,
8556                                                                  status) {
8557   this.snapshotMan.receiveSnapshotResult(requestID, status);
8558 };
8559 
8560 /**
8561  * UPC_STATS_SNAPSHOT
8562  */
8563 net.user1.orbiter.CoreMessageListener.prototype.u156 = function (requestID,
8564                                                                  totalUPCsProcessed,
8565                                                                  numUPCsInQueue,
8566                                                                  lastQueueWaitTime) {
8567   var longestUPCProcesses = Array.prototype.slice.call(arguments).slice(5);
8568   var upcProcessingRecord;
8569   for (var i = 0; i < longestUPCProcesses.length; i++) {
8570     upcProcessingRecord = new net.user1.orbiter.UPCProcessingRecord();
8571     upcProcessingRecord.deserialize(longestUPCProcesses[i]);
8572     longestUPCProcesses[i] = upcProcessingRecord;
8573   }
8574   
8575   this.snapshotMan.receiveUPCStatsSnapshot(requestID, 
8576                                            parseFloat(totalUPCsProcessed),
8577                                            parseFloat(numUPCsInQueue),
8578                                            parseFloat(lastQueueWaitTime),
8579                                            longestUPCProcesses);
8580 };
8581 
8582 /**
8583  * RESET_UPC_STATS_RESULT
8584  */
8585 net.user1.orbiter.CoreMessageListener.prototype.u158 = function (status) {
8586   switch (status) { 
8587     case net.user1.orbiter.Status.SUCCESS:
8588     case net.user1.orbiter.Status.ERROR:
8589     case net.user1.orbiter.Status.PERMISSION_DENIED:
8590      this.orbiter.getServer().dispatchResetUPCStatsResult(status);
8591       break;
8592     
8593     default:
8594       this.log.warn("Unrecognized status code for u158."
8595                + "Status: [" + status + "].");
8596   }
8597 };
8598 
8599 /**
8600  * WATCH_FOR_PROCESSED_UPCS_RESULT
8601  */
8602 net.user1.orbiter.CoreMessageListener.prototype.u160 = function (status) {
8603   switch (status) { 
8604     case net.user1.orbiter.Status.SUCCESS:
8605       this.orbiter.getServer().setIsWatchingForProcessedUPCs(true);
8606     case net.user1.orbiter.Status.ERROR:
8607     case net.user1.orbiter.Status.ALREADY_WATCHING:
8608     case net.user1.orbiter.Status.PERMISSION_DENIED:
8609       this.orbiter.getServer().dispatchWatchForProcessedUPCsResult(status);
8610       break;
8611     
8612     default:
8613       this.log.warn("Unrecognized status code for u160."
8614                + "Status: [" + status + "].");
8615   }
8616 };
8617 
8618 /**
8619  * PROCESSED_UPC_ADDED
8620  */
8621 net.user1.orbiter.CoreMessageListener.prototype.u161 = function (fromClientID,
8622                                                                  fromUserID,
8623                                                                  fromClientAddress,
8624                                                                  queuedAt,
8625                                                                  processingStartedAt,
8626                                                                  processingFinishedAt,
8627                                                                  source) {
8628   var upcProcessingRecord = new net.user1.orbiter.UPCProcessingRecord();
8629   upcProcessingRecord.deserializeParts(fromClientID,
8630                                        fromUserID,
8631                                        fromClientAddress,
8632                                        queuedAt,
8633                                        processingStartedAt,
8634                                        processingFinishedAt,
8635                                        source);
8636   this.orbiter.getServer().dispatchUPCProcessed(upcProcessingRecord);
8637 };
8638 
8639 /**
8640  * STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT
8641  */
8642 net.user1.orbiter.CoreMessageListener.prototype.u163 = function (status) {
8643   switch (status) { 
8644     case net.user1.orbiter.Status.SUCCESS:
8645       this.orbiter.getServer().setIsWatchingForProcessedUPCs(false);
8646     case net.user1.orbiter.Status.ERROR:
8647     case net.user1.orbiter.Status.NOT_WATCHING:
8648     case net.user1.orbiter.Status.PERMISSION_DENIED:
8649       this.orbiter.getServer().dispatchStopWatchingForProcessedUPCsResult(status);
8650       break;
8651     
8652     default:
8653       this.log.warn("Unrecognized status code for u163."
8654                + "Status: [" + status + "].");
8655   }
8656 };
8657 
8658     
8659 /**
8660  * NODELIST_SNAPSHOT
8661  */
8662 net.user1.orbiter.CoreMessageListener.prototype.u166 = function (requestID, nodeListSource) {
8663   var nodeIDs = nodeListSource == "" ? [] : nodeListSource.split(net.user1.orbiter.Tokens.RS);
8664   
8665   if (requestID == "") {
8666     this.log.warn("Incoming NODELIST_SNAPSHOT UPC missing required requestID. Ignoring message.");
8667   } else {
8668     // Snapshot
8669     this.snapshotMan.receiveNodeListSnapshot(requestID, nodeIDs);
8670   }
8671 };
8672 
8673 /**
8674  * GATEWAYS_SNAPSHOT
8675  */
8676 net.user1.orbiter.CoreMessageListener.prototype.u168 = function (requestID) {
8677   var gatewayListSource = Array.prototype.slice.call(arguments).slice(1);
8678   var gateways = [];
8679 
8680   var gateway;
8681   var gatewayBandwidth;
8682   var gatewayBandwidthSource;
8683   var gatewayIntervalSource;
8684   for (var i = 0; i < gatewayListSource.length; i+=8) {
8685     gateway = new net.user1.orbiter.Gateway();
8686     gateway.id = gatewayListSource[i];
8687     gateway.type = gatewayListSource[i+1];
8688 
8689     gateway.lifetimeConnectionsByCategory = gatewayListSource[i+2] === "" ? {} : this.createHashFromArg(gatewayListSource[i+2]);
8690     for (var p in gateway.lifetimeConnectionsByCategory) {
8691       gateway.lifetimeConnectionsByCategory[p] = parseFloat(gateway.lifetimeConnectionsByCategory[p]);
8692     }
8693     gateway.lifetimeClientsByType = gatewayListSource[i+3] === "" ? {} : this.createHashFromArg(gatewayListSource[i+3]);
8694     for (p in gateway.lifetimeClientsByType) {
8695       gateway.lifetimeClientsByType[p] = parseFloat(gateway.lifetimeClientsByType[p]);
8696     }
8697     gateway.lifetimeClientsByUPCVersion = gatewayListSource[i+4] === "" ? {} : this.createHashFromArg(gatewayListSource[i+4]);
8698     for (p in gateway.lifetimeClientsByUPCVersion) {
8699       gateway.lifetimeClientsByUPCVersion[p] = parseFloat(gateway.lifetimeClientsByUPCVersion[p]);
8700     }
8701     gateway.attributes = gatewayListSource[i+5] === "" ? {} : this.createHashFromArg(gatewayListSource[i+5]);
8702 
8703     gatewayIntervalSource = gatewayListSource[i+6].split(net.user1.orbiter.Tokens.RS);
8704     gateway.connectionsPerSecond = parseFloat(gatewayIntervalSource[0]);
8705     gateway.maxConnectionsPerSecond = parseFloat(gatewayIntervalSource[1]);
8706     gateway.clientsPerSecond = parseFloat(gatewayIntervalSource[2]);
8707     gateway.maxClientsPerSecond = parseFloat(gatewayIntervalSource[3]);
8708 
8709     gatewayBandwidth = new net.user1.orbiter.GatewayBandwidth();
8710     gatewayBandwidthSource = gatewayListSource[i+7].split(net.user1.orbiter.Tokens.RS);
8711     gatewayBandwidth.lifetimeRead = gatewayBandwidthSource[0] === "" ? 0 : parseFloat(gatewayBandwidthSource[0]);
8712     gatewayBandwidth.lifetimeWritten = gatewayBandwidthSource[1] === "" ? 0 : parseFloat(gatewayBandwidthSource[1]);
8713     gatewayBandwidth.averageRead = gatewayBandwidthSource[2] === "" ? 0 : parseFloat(gatewayBandwidthSource[2]);
8714     gatewayBandwidth.averageWritten = gatewayBandwidthSource[3] === "" ? 0 : parseFloat(gatewayBandwidthSource[3]);
8715     gatewayBandwidth.intervalRead = gatewayBandwidthSource[4] === "" ? 0 : parseFloat(gatewayBandwidthSource[4]);
8716     gatewayBandwidth.intervalWritten = gatewayBandwidthSource[5] === "" ? 0 : parseFloat(gatewayBandwidthSource[5]);
8717     gatewayBandwidth.maxIntervalRead = gatewayBandwidthSource[6] === "" ? 0 : parseFloat(gatewayBandwidthSource[6]);
8718     gatewayBandwidth.maxIntervalWritten = gatewayBandwidthSource[7] === "" ? 0 : parseFloat(gatewayBandwidthSource[7]);
8719     gatewayBandwidth.scheduledWrite = gatewayBandwidthSource[8] === "" ? 0 : parseFloat(gatewayBandwidthSource[8]);
8720     gateway.bandwidth = gatewayBandwidth;
8721     gateways.push(gateway);
8722   }
8723 
8724   if (requestID == "") {
8725     this.log.warn("Incoming GATEWAYS_SNAPSHOT UPC missing required requestID. Ignoring message.");
8726   } else {
8727     // Snapshot
8728     this.snapshotMan.receiveGatewaysSnapshot(requestID, gateways);
8729   }
8730 };
8731 //==============================================================================
8732 // CLASS DECLARATION
8733 //==============================================================================
8734 /**
8735  * @class
8736  */
8737 net.user1.orbiter.CustomClient = function () {
8738   this.client = null;
8739 };
8740 
8741 /**
8742  * An initialization method invoked when this CustomClient object is ready
8743  * for use. Subclasses wishing to perform initialization tasks that require
8744  * this CustomClient's composed Client object should override this method. 
8745  * 
8746  * @since Orbiter 1.0.0
8747  */
8748 net.user1.orbiter.CustomClient.prototype.init = function () {
8749 };
8750 
8751 net.user1.orbiter.CustomClient.prototype.addEventListener = function (type, 
8752                                                            listener,
8753                                                            thisArg,
8754                                                            priority) {
8755   this.client.addEventListener(type, listener, thisArg, priority);
8756 };
8757 
8758 net.user1.orbiter.CustomClient.prototype.dispatchEvent = function (event) {
8759   return this.client.dispatchEvent(event);
8760 };
8761 
8762 net.user1.orbiter.CustomClient.prototype.hasEventListener = function (type) {
8763   return this.client.hasEventListener(type);
8764 };
8765 
8766 net.user1.orbiter.CustomClient.prototype.removeEventListener = function (type, 
8767                                                                          listener,
8768                                                                          thisObj) {
8769   this.client.removeEventListener(type, listener, thisObj);
8770 };
8771 
8772 net.user1.orbiter.CustomClient.prototype.willTrigger = function (type) {
8773   return this.client.willTrigger(type);
8774 };
8775 
8776 net.user1.orbiter.CustomClient.prototype.setClient = function (client) {
8777   this.client = client;
8778 };
8779 
8780 net.user1.orbiter.CustomClient.prototype.getClientID = function () {
8781   return this.client.getClientID();
8782 };
8783 
8784 net.user1.orbiter.CustomClient.prototype.getConnectionState = function () {
8785   return this.client.getConnectionState();
8786 };
8787 
8788 net.user1.orbiter.CustomClient.prototype.isSelf = function () {
8789   return this.client.isSelf();
8790 };
8791 
8792 net.user1.orbiter.CustomClient.prototype.setClientClass = function (scope, 
8793                                clientClass) {
8794   var fallbackClasses = Array.prototype.slice.call(arguments).slice(2);
8795   this.client.setClientClass.apply(this.client, [scope, clientClass].concat(fallbackClasses));
8796 };
8797 
8798 net.user1.orbiter.CustomClient.prototype.isInRoom = function (roomID) {
8799   return this.client.isInRoom(roomID);
8800 };
8801 
8802 net.user1.orbiter.CustomClient.prototype.isObservingRoom = function (roomID) {
8803   return this.client.isObservingRoom(roomID);
8804 };
8805 
8806 net.user1.orbiter.CustomClient.prototype.getOccupiedRoomIDs = function () {
8807   return this.client.getOccupiedRoomIDs();
8808 };
8809 
8810 net.user1.orbiter.CustomClient.prototype.getObservedRoomIDs = function () {
8811   return this.client.getObservedRoomIDs();
8812 };
8813 
8814 net.user1.orbiter.CustomClient.prototype.getIP = function () {
8815   return this.client.getIP();
8816 };
8817 
8818 net.user1.orbiter.CustomClient.prototype.getConnectTime = function () {
8819   return this.client.getConnectTime();
8820 };
8821 
8822 net.user1.orbiter.CustomClient.prototype.getPing = function () {
8823   return this.client.getPing();
8824 };
8825 
8826 net.user1.orbiter.CustomClient.prototype.getTimeOnline = function () {
8827   return this.client.getTimeOnline();
8828 };
8829 
8830 net.user1.orbiter.CustomClient.prototype.sendMessage = function (messageName) {
8831   var args = Array.prototype.slice.call(arguments).slice(0);
8832   this.client.sendMessage.apply(this.client, args);
8833 };
8834 
8835 net.user1.orbiter.CustomClient.prototype.setAttribute = function (attrName, 
8836                                                                   attrValue, 
8837                                                                   attrScope, 
8838                                                                   isShared, 
8839                                                                   evaluate) {
8840   this.client.setAttribute(attrName, attrValue, attrScope, isShared, evaluate);
8841 };
8842 
8843 net.user1.orbiter.CustomClient.prototype.deleteAttribute = function (attrName, attrScope) {
8844   this.client.deleteAttribute(attrName, attrScope);
8845 };
8846 
8847 net.user1.orbiter.CustomClient.prototype.getAttribute = function (attrName, attrScope) {
8848   return this.client.getAttribute(attrName, attrScope);
8849 };
8850 
8851 net.user1.orbiter.CustomClient.prototype.getAttributes = function () {
8852   return this.client.getAttributes();
8853 };
8854 
8855 net.user1.orbiter.CustomClient.prototype.getAttributesByScope = function (scope) {
8856   return this.client.getAttributesByScope();
8857 };
8858 
8859 net.user1.orbiter.CustomClient.prototype.getClientManager = function () {
8860   return this.client.getClientManager();
8861 };
8862 
8863 net.user1.orbiter.CustomClient.prototype.getAccount = function () {
8864   return this.client.getAccount();
8865 };
8866 
8867 net.user1.orbiter.CustomClient.prototype.kick = function () {
8868   this.client.kick();
8869 };
8870 
8871 net.user1.orbiter.CustomClient.prototype.ban = function (duration, reason) {
8872   this.client.ban(duration, reason);
8873 };
8874 
8875 net.user1.orbiter.CustomClient.prototype.observe = function () {
8876   this.client.observe();
8877 };
8878 
8879 net.user1.orbiter.CustomClient.prototype.stopObserving = function () {
8880   this.client.stopObserving();
8881 };
8882 
8883 net.user1.orbiter.CustomClient.prototype.isAdmin = function () {
8884   return this.client.isAdmin();
8885 };
8886 
8887 net.user1.orbiter.CustomClient.prototype.toString = function () {
8888   return "[object CustomClient, ID: " + this.getClientID() + "]";
8889 };
8890 //==============================================================================
8891 // EVENT UTILITIES
8892 //==============================================================================
8893 /** @class */
8894 net.user1.utils.EventUtil = new Object();
8895 
8896 net.user1.utils.EventUtil.migrateListeners = function (oldObject, 
8897                                                        newObject,
8898                                                        events,
8899                                                        thisObj) {
8900   var len = events.length
8901   for (var i = 0; i < len; i += 2) {
8902     if (oldObject != null) {
8903       oldObject.removeEventListener(events[i], events[i+1], thisObj);
8904     }
8905     if (newObject != null) {
8906       newObject.addEventListener(events[i], events[i+1], thisObj);
8907     }
8908   }
8909 };
8910 //==============================================================================
8911 // CLASS DECLARATION
8912 //==============================================================================
8913 /** 
8914  * @class
8915  */
8916 net.user1.orbiter.filters.FilterSet = function () {
8917   this.filters = new Array();
8918 };
8919     
8920 net.user1.orbiter.filters.FilterSet.prototype.addFilter = function (filter) {
8921   this.filters.push(filter);
8922 };
8923     
8924 net.user1.orbiter.filters.FilterSet.prototype.getFilters = function () {
8925   return this.filters.slice(0);
8926 };
8927     
8928 net.user1.orbiter.filters.FilterSet.prototype.toXMLString = function () {
8929   var s = "<filters>\n";
8930   
8931   var filter;
8932   for (var i = 0; i < this.filters.length; i++) {
8933     filter = this.filters[i];
8934     s += filter.toXMLString() + "\n";
8935   }
8936   s += "</filters>";
8937   return s;
8938 };
8939 //==============================================================================
8940 // CLASS DECLARATION
8941 //==============================================================================
8942 /** 
8943  * @class
8944  */
8945 net.user1.orbiter.GatewayBandwidth = function () {
8946   /**
8947    * @field
8948    * @type Number
8949    */
8950   this.lifetimeRead = 0;
8951   /**
8952    * @field
8953    * @type Number
8954    */
8955   this.lifetimeWritten = 0;
8956   /**
8957    * @field
8958    * @type Number
8959    */
8960   this.averageRead = 0;
8961   /**
8962    * @field
8963    * @type Number
8964    */
8965   this.averageWritten = 0;
8966   /**
8967    * @field
8968    * @type Number
8969    */
8970   this.intervalRead = 0;
8971   /**
8972    * @field
8973    * @type Number
8974    */
8975   this.intervalWritten = 0;
8976   /**
8977    * @field
8978    * @type Number
8979    */
8980   this.maxIntervalRead = 0;
8981   /**
8982    * @field
8983    * @type Number
8984    */
8985   this.maxIntervalWritten = 0;
8986   /**
8987    * @field
8988    * @type Number
8989    */
8990   this.scheduledWrite = 0;
8991 };
8992 //==============================================================================
8993 // CLASS DECLARATION
8994 //==============================================================================
8995 /**
8996  * @class
8997  */
8998 net.user1.orbiter.Gateway = function () {
8999   /**
9000    * @field
9001    * @type String
9002   this.id = null;
9003   /**
9004    * @field
9005    * @type String
9006    */
9007   this.type = null;
9008   /**
9009    * @field
9010    * @type Object
9011    */
9012   this.lifetimeConnectionsByCategory = null;
9013   /**
9014    * @field
9015    * @type Object
9016    */
9017   this.lifetimeClientsByType = null;
9018   /**
9019    * @field
9020    * @type Object
9021    */
9022   this.lifetimeClientsByUPCVersion = null;
9023   /**
9024    * @field
9025    * @type Object
9026    */
9027   this.attributes = null;
9028   /**
9029    * @field
9030    * @type Number
9031    */
9032   this.connectionsPerSecond = 0;
9033   /**
9034    * @field
9035    * @type Number
9036    */
9037   this.maxConnectionsPerSecond = 0;
9038   /**
9039    * @field
9040    * @type Number
9041    */
9042   this.clientsPerSecond = 0;
9043   /**
9044    * @field
9045    * @type Number
9046    */
9047   this.maxClientsPerSecond = 0;
9048   /**
9049    * @field
9050    * @type net.user1.orbiter.GatewayBandwidth
9051    */
9052   this.bandwidth = null;
9053 };
9054 //==============================================================================
9055 // CLASS DECLARATION
9056 //==============================================================================
9057 /** 
9058  * @class
9059  */
9060 net.user1.utils.LRUCache = function (maxLength) {
9061   this.maxLength = maxLength;
9062   this.length = 0;
9063   this.hash = new net.user1.utils.UDictionary();
9064   this.first = null;
9065   this.last = null;
9066 };
9067     
9068 net.user1.utils.LRUCache.prototype.get = function (key) {
9069   var node = this.hash[key];
9070   
9071   if (node != null) {
9072     this.moveToHead(node);
9073     return node.value;
9074   } else {
9075     return null;
9076   }
9077 };
9078 
9079 net.user1.utils.LRUCache.prototype.put = function (key, value) {
9080   var node = this.hash[key];
9081   if (node == null) {
9082     if (this.length >= this.maxLength) {
9083       this.removeLast();
9084     } else {
9085       this.length++;
9086     }
9087     node = new net.user1.utils.CacheNode();
9088   }
9089   
9090   node.value = value;
9091   node.key = key;
9092   this.moveToHead(node);
9093   this.hash[key] = node;
9094 };
9095 
9096 net.user1.utils.LRUCache.prototype.remove = function (key) {
9097   var node = this.hash[key];
9098   if (node != null) {
9099     if (node.prev != null) {
9100       node.prev.next = node.next;
9101     }
9102     if (node.next != null) {
9103       node.next.prev = node.prev;
9104     }
9105     if (this.last == node) {
9106       this.last = node.prev;
9107     }
9108     if (this.first == node) {
9109       this.first = node.next;
9110     }
9111   }
9112   return node;
9113 }
9114 
9115 net.user1.utils.LRUCache.prototype.clear = function () {
9116   this.first = null;
9117   this.last = null;
9118   this.length = 0;
9119   this.hash = new net.user1.utils.UDictionary();
9120 };
9121 
9122 /**
9123  * @private
9124  */
9125 net.user1.utils.LRUCache.prototype.removeLast = function () {
9126   if (this.last != null) {
9127     delete this.hash[this.last.key];
9128     if (this.last.prev != null) {
9129       this.last.prev.next = null;
9130     } else {
9131       this.first = null;
9132     }
9133     this.last = this.last.prev;
9134   }
9135 };
9136 
9137 /**
9138  * @private
9139  */
9140 net.user1.utils.LRUCache.prototype.moveToHead = function (node) {
9141   if (node == this.first) {
9142     return;
9143   }
9144   if (node.prev != null) {
9145     node.prev.next = node.next;
9146   }
9147   if (node.next != null) {
9148     node.next.prev = node.prev;
9149   }
9150   if (this.last == node) {
9151     this.last = node.prev;
9152   }
9153   if (this.first != null) {
9154     node.next = this.first;
9155     this.first.prev = node;
9156   }
9157   this.first = node;
9158   node.prev = null;
9159   if (this.last == null) {
9160     this.last = this.first;
9161   }
9162 };
9163 //==============================================================================
9164 // CLASS DECLARATION
9165 //==============================================================================
9166 /**
9167  * @class
9168  */
9169 net.user1.orbiter.ModuleDefinition = function (id, type, source) {
9170   this.id = id;
9171   this.type = type;
9172   this.source = source;
9173 };
9174 //==============================================================================
9175 // MODULE TYPE CONSTANTS
9176 //==============================================================================
9177 /** @class */
9178 net.user1.orbiter.ModuleType = new Object();
9179 /** @constant */
9180 net.user1.orbiter.ModuleType.CLASS = "class";
9181 /** @constant */
9182 net.user1.orbiter.ModuleType.SCRIPT = "script";
9183 //==============================================================================
9184 // CLASS DECLARATION
9185 //==============================================================================
9186 /** 
9187  * @class
9188  * @extends net.user1.orbiter.snapshot.Snapshot
9189  */
9190 net.user1.orbiter.snapshot.NodeListSnapshot = function () {
9191   // Call superconstructor
9192   net.user1.orbiter.snapshot.Snapshot.call(this);
9193   this.nodeList = null;
9194   this.method = net.user1.orbiter.UPC.GET_NODELIST_SNAPSHOT;
9195 };
9196 
9197 //==============================================================================
9198 // INHERITANCE
9199 //==============================================================================
9200 net.user1.utils.extend(net.user1.orbiter.snapshot.NodeListSnapshot, net.user1.orbiter.snapshot.Snapshot);
9201 
9202 //==============================================================================
9203 // INSTANCE METHODS
9204 //==============================================================================         
9205 /**
9206  * @private
9207  */    
9208 net.user1.orbiter.snapshot.NodeListSnapshot.prototype.setNodeList = function (value) {
9209   this.nodeList = value;
9210 }
9211 
9212 net.user1.orbiter.snapshot.NodeListSnapshot.prototype.getNodeList = function () {
9213   if (!this.nodeList) {
9214     return null;
9215   }
9216   return this.nodeList.slice();
9217 };
9218 //==============================================================================
9219 // A COLLECTION OF NUMERIC UTILITIES
9220 //==============================================================================
9221 /** @class */
9222 net.user1.utils.integer = new Object();
9223 /** @constant */
9224 net.user1.utils.integer.MAX_VALUE = Math.pow(2,32) - 1;
9225 //==============================================================================
9226 // CLASS DECLARATION
9227 //==============================================================================
9228 /** 
9229  * @class
9230  * @extends net.user1.orbiter.filters.BooleanGroup
9231  */
9232 net.user1.orbiter.filters.OrGroup = function () {
9233   net.user1.orbiter.filters.BooleanGroup.call(this, net.user1.orbiter.filters.BooleanGroupType.OR);
9234 };
9235 
9236 //==============================================================================
9237 // INHERITANCE
9238 //==============================================================================
9239 net.user1.utils.extend(net.user1.orbiter.filters.OrGroup, net.user1.orbiter.filters.BooleanGroup);
9240 /**
9241  * @private
9242  */  
9243 net.user1.orbiter.upc.RemoveClientAttr = function (clientID, userID, name, scope) { 
9244   // Abort if name is invalid.
9245   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
9246     throw new Error("Cannot delete attribute. Illegal name" + 
9247                     " (see Validator.isValidAttributeName()): " + name);
9248   }
9249   
9250   // Abort if scope is invalid.
9251   if (!net.user1.orbiter.Validator.isValidAttributeScope(scope)) {
9252     throw new Error("Cannot delete client attribute. Illegal scope" + 
9253              " (see Validator.isValidAttributeScope()): " + scope);
9254   }
9255   
9256   this.method = net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR;
9257   this.args   = [clientID, userID, name, scope];
9258 };
9259 /**
9260  * @private
9261  */  
9262 net.user1.orbiter.upc.RemoveRoomAttr = function (roomID, name) { 
9263   // Abort if name is invalid.
9264   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
9265     throw new Error("Cannot delete attribute. Illegal name" + 
9266                     " (see Validator.isValidAttributeName()): " + name);
9267   }
9268   
9269   this.method = net.user1.orbiter.UPC.REMOVE_ROOM_ATTR;
9270   this.args   = [roomID, name];
9271 };
9272 /** @function */
9273 net.user1.utils.resolveMemberExpression = function (value) {
9274   var parts = value.split(".");
9275   var reference = globalObject;
9276   for (var i = 0; i < parts.length; i++) {
9277     reference = reference[parts[i]];
9278   }
9279   return reference;
9280 };
9281 //==============================================================================
9282 // CLASS DECLARATION
9283 //==============================================================================
9284 /** @class
9285 
9286 The Room class dispatches the following events:
9287 
9288 <ul class="summary">
9289 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN}</li> 
9290 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN_RESULT}</li> 
9291 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE}</li> 
9292 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE_RESULT}</li> 
9293 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.ADD_OCCUPANT}</li> 
9294 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT}</li> 
9295 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.ADD_OBSERVER}</li> 
9296 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVE_OBSERVER}</li> 
9297 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE}</li> 
9298 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE}</li> 
9299 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OCCUPANT_COUNT}</li> 
9300 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OSERVER_COUNT}</li> 
9301 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.SYNCHRONIZE}</li> 
9302 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE}</li> 
9303 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE_RESULT}</li> 
9304 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING}</li> 
9305 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT}</li> 
9306 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVED}</li> 
9307 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li> 
9308 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li> 
9309 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.SET_RESULT}</li> 
9310 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE_RESULT}</li> 
9311 </ul>
9312 
9313 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
9314 
9315     @extends net.user1.events.EventDispatcher
9316 */
9317 net.user1.orbiter.Room = function (id,
9318                                    roomManager,
9319                                    messageManager,
9320                                    clientManager,
9321                                    accountManager,
9322                                    log) {
9323   // Call superconstructor
9324   net.user1.events.EventDispatcher.call(this);
9325 
9326   // Variables
9327   this.disposed = false;
9328   this.id = null;
9329   this.syncState = null;
9330   this._clientIsInRoom = false;
9331   this._clientIsObservingRoom = false;
9332   this.numOccupants = 0;
9333   this.numObservers = 0;
9334   this.defaultClientClass = null;
9335 
9336   // Initialization  
9337   this.setRoomID(id);
9338   this.roomManager    = roomManager;
9339   this.messageManager = messageManager;
9340   this.clientManager  = clientManager;
9341   this.accountManager = accountManager;
9342   this.log = log;
9343 
9344   this.occupantList     = new net.user1.orbiter.ClientSet();
9345   this.observerList     = new net.user1.orbiter.ClientSet();
9346   this.attributeManager = new net.user1.orbiter.AttributeManager(this, this.messageManager, this.log);
9347 
9348   this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9349 }
9350 
9351 //==============================================================================    
9352 // INHERITANCE
9353 //============================================================================== 
9354 net.user1.utils.extend(net.user1.orbiter.Room, net.user1.events.EventDispatcher);
9355 
9356 // =============================================================================
9357 // DEPENDENCIES
9358 // =============================================================================
9359 /** @private */
9360 net.user1.orbiter.Room.prototype.getAttributeManager = function () {
9361   return this.attributeManager;
9362 };   
9363 
9364 // =============================================================================
9365 // ROOM ID MANAGEMENT
9366 // =============================================================================
9367 
9368  /**
9369   * @private
9370   */
9371 net.user1.orbiter.Room.prototype.setRoomID = function (roomID) {
9372   var errorMsg;
9373   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
9374     errorMsg = "Invalid room ID specified during room creation. Offending ID: " + roomID;
9375     this.log.error(errorMsg);
9376     throw new Error(errorMsg);
9377   }
9378   this.id = roomID;
9379 };
9380   
9381 net.user1.orbiter.Room.prototype.getRoomID = function () {
9382   return this.id;
9383 };
9384 
9385 net.user1.orbiter.Room.prototype.getSimpleRoomID = function () {
9386   return net.user1.orbiter.RoomIDParser.getSimpleRoomID(this.id);
9387 };
9388 
9389 net.user1.orbiter.Room.prototype.getQualifier = function () {
9390   return net.user1.orbiter.RoomIDParser.getQualifier(this.id);
9391 };
9392   
9393 // =============================================================================
9394 // JOIN/LEAVE
9395 // =============================================================================
9396   
9397 net.user1.orbiter.Room.prototype.join = function (password,
9398                                                   updateLevels) {
9399   if (this.disposed) return;
9400   
9401   // Client can't join a room the its already in.
9402   if (this.clientIsInRoom()) {
9403     this.log.warn(this + "Room join attempt aborted. Already in room.");
9404     return;
9405   }
9406   // Validate the password
9407   if (password == null) {
9408     password = "";
9409   }
9410   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
9411     this.log.error(this + "Invalid room password supplied to join(). "
9412                    + " Join request not sent. See Validator.isValidPassword().");
9413     return;
9414   }
9415   
9416   // If any update levels are specified, send them before joining.
9417   if (updateLevels != null) {
9418     this.setUpdateLevels(updateLevels);
9419   }
9420 
9421   this.messageManager.sendUPC(net.user1.orbiter.UPC.JOIN_ROOM, 
9422                               this.getRoomID(), 
9423                               password);
9424 };
9425   
9426 net.user1.orbiter.Room.prototype.leave = function () {
9427   if (this.disposed) return;
9428   
9429   if (this.clientIsInRoom()) {
9430     this.messageManager.sendUPC(net.user1.orbiter.UPC.LEAVE_ROOM, this.getRoomID());
9431   } else {
9432     this.log.debug(this + " Leave-room request ignored. Not in room.");
9433   }
9434 };
9435   
9436 /**
9437  * @private
9438  */
9439 net.user1.orbiter.Room.prototype.doJoin = function () {
9440   this._clientIsInRoom = true;
9441   this.fireJoin();
9442 };
9443     
9444 /**
9445  * @private
9446  */
9447 net.user1.orbiter.Room.prototype.doJoinResult = function (status) {
9448   this.fireJoinResult(status);
9449 };
9450     
9451     /**
9452      * @private
9453      */
9454 net.user1.orbiter.Room.prototype.doLeave = function () {
9455   var rid = this.getRoomID();
9456 
9457   // If the client is not observing the room, then dispose
9458   // of all of the room's information.
9459   if (!this.clientIsObservingRoom()) {
9460     this.purgeRoomData();
9461   }
9462     
9463   // Note that the client is no longer in this room.
9464   this._clientIsInRoom = false;
9465   this.fireLeave();
9466 }
9467 
9468 /**
9469  * @private
9470  */
9471 net.user1.orbiter.Room.prototype.doLeaveResult = function (status) {
9472   this.fireLeaveResult(status);
9473 };
9474     
9475 //==============================================================================
9476 // MESSAGING
9477 //==============================================================================
9478 
9479 net.user1.orbiter.Room.prototype.sendMessage = function (messageName, 
9480                                                          includeSelf,
9481                                                          filters) {
9482   if (this.disposed) return;
9483   
9484   // Delegate to RoomManager.sendMessage()
9485   var rest = Array.prototype.slice.call(arguments).slice(3)
9486   var roomMan = this.roomManager;
9487   var args = [messageName, 
9488              [this.getRoomID()],
9489              includeSelf,
9490              filters != null ? filters : null];
9491   roomMan.sendMessage.apply(roomMan, args.concat(rest));
9492 };
9493 
9494 net.user1.orbiter.Room.prototype.addMessageListener = function (message, listener, thisArg) {
9495   if (this.messageManager != null) {
9496     this.messageManager.addMessageListener(message,
9497                                            listener, 
9498                                            thisArg,
9499                                            [this.getRoomID()]);
9500   }
9501 };
9502 
9503 net.user1.orbiter.Room.prototype.removeMessageListener = function (message, listener) {
9504   if (this.messageManager != null) {
9505     this.messageManager.removeMessageListener(message, 
9506                                               listener);
9507   }
9508 };
9509 
9510 net.user1.orbiter.Room.prototype.hasMessageListener = function (message,
9511                                                                 listener) {
9512   // First, get the list of messsage listeners for this message
9513   var listeners = this.messageManager.getMessageListeners(message);
9514   var messageListener;
9515   for (var i = 0; i < listeners.length; i++) {
9516     messageListener = listeners[i];
9517     var listenerRoomIDs = messageListener.getForRoomIDs();
9518     // ===== Run once for each room id =====
9519     var listenerRoomID;
9520     for (var j = 0; j < listenerRoomIDs.length; j++) {
9521       listenerRoomID = listenerRoomIDs[i];
9522       if (listenerRoomID == this.getRoomID()) {
9523         return true;
9524       }
9525     }
9526   }
9527   return false;
9528 };
9529 
9530 //==============================================================================
9531 // SYNCHRONIZATION
9532 //==============================================================================
9533     
9534 /**
9535  * @private
9536  */        
9537 net.user1.orbiter.Room.prototype.synchronize = function (manifest) {
9538   var oldSyncState = this.getSyncState();
9539   this.log.debug(this + " Begin synchronization.");
9540   this.setSyncState(net.user1.orbiter.SynchronizationState.SYNCHRONIZING);
9541 
9542   // SYNC ROOM ATTRIBUTES
9543   this.getAttributeManager().getAttributeCollection().synchronizeScope(net.user1.orbiter.Tokens.GLOBAL_ATTR, manifest.attributes);
9544   if (this.disposed) {
9545     return;
9546   }
9547 
9548   // SYNC OCCUPANT LIST
9549   var oldOccupantList = this.getOccupantIDs();
9550   var newOccupantList = [];
9551   var thisOccupantClientID;
9552   var thisOccupantUserID;
9553   var thisOccupant;
9554   var thisOccupantAccount;
9555   
9556   // Add all unknown occupants to the room's occupant list, and
9557   // synchronize all existing occupants.
9558   for (var i = manifest.occupants.length; --i >= 0;) {
9559     thisOccupantClientID = manifest.occupants[i].clientID;
9560     thisOccupantUserID = manifest.occupants[i].userID;
9561     
9562     newOccupantList.push(thisOccupantClientID);
9563     
9564     thisOccupant = this.clientManager.requestClient(thisOccupantClientID);
9565     // Init user account, if any
9566     thisOccupantAccount = this.accountManager.requestAccount(thisOccupantUserID);
9567     if (thisOccupantAccount != null) {
9568       thisOccupant.setAccount(thisOccupantAccount);
9569     }
9570     
9571     // If it's not the current client, update it.
9572     // The current client obtains its attributes through separate u8s.
9573     if (!thisOccupant.isSelf()) {
9574       thisOccupant.synchronize(manifest.occupants[i]);
9575     }
9576     
9577     this.addOccupant(thisOccupant);
9578     if (this.disposed) {
9579       return;
9580     }
9581   }
9582   
9583   // Remove occupants that are now gone...
9584   var oldClientID;
9585   for (i = oldOccupantList.length; --i >= 0;) {
9586     oldClientID = oldOccupantList[i];
9587     if (net.user1.utils.ArrayUtil.indexOf(newOccupantList, oldClientID) == -1) {
9588       this.removeOccupant(oldClientID);
9589       if (this.disposed) {
9590         return;
9591       }
9592     }
9593   }
9594 
9595   // SYNC OBSERVER LIST
9596   var oldObserverList = this.getObserverIDs();
9597   var newObserverList = [];
9598   var thisObserverClientID;
9599   var thisObserverUserID;
9600   var thisObserver;
9601   var thisObserverAccount;
9602   
9603   // Add all unknown observers to the room's observer list, and
9604   // synchronize all existing observers.
9605   for (i = manifest.observers.length; --i >= 0;) {
9606     thisObserverClientID = manifest.observers[i].clientID;
9607     thisObserverUserID = manifest.observers[i].userID;
9608     
9609     newObserverList.push(thisObserverClientID);
9610     
9611     thisObserver = this.clientManager.requestClient(thisObserverClientID);
9612     // Init user account, if any
9613     thisObserverAccount = this.accountManager.requestAccount(thisObserverUserID);
9614     if (thisObserverAccount != null) {
9615       thisObserver.setAccount(thisObserverAccount);
9616     }
9617     
9618     // If it's not the current client, update it.
9619     // The current client obtains its attributes through separate u8s.
9620     if (!thisObserver.isSelf()) {
9621       thisObserver.synchronize(manifest.observers[i]);
9622     }
9623     
9624     this.addObserver(thisObserver);
9625     if (this.disposed) {
9626       return;
9627     }
9628   }
9629   
9630   // Remove observers that are now gone...
9631   var oldClientID;
9632   for (i = oldObserverList.length; --i >= 0;) {
9633     oldClientID = oldObserverList[i]
9634     if (net.user1.utils.ArrayUtil.indexOf(newObserverList, oldClientID) == -1) {
9635       this.removeObserver(oldClientID);
9636       if (this.disposed) {
9637         return;
9638       }
9639     }
9640   }
9641   
9642   // UPDATE CLIENT COUNTS
9643   //   If a client list is available, use its length to calculate the
9644   //   client count. That way, the list length and the "get count" method
9645   //   return values will be the same (e.g., getOccupants().length and
9646   //   getNumOccupants()). Otherwise, rely on the server's reported count.
9647   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());
9648   if (levels.occupantList) {
9649     this.setNumOccupants(this.occupantList.length());
9650   } else if (levels.occupantCount) {
9651     this.setNumOccupants(manifest.occupantCount);
9652   }
9653   if (levels.observerList) {
9654     this.setNumObservers(this.observerList.length());
9655   } else if (levels.observerCount) {
9656     this.setNumObservers(manifest.observerCount);
9657   }
9658   
9659   // Update sync state 
9660   this.setSyncState(oldSyncState);
9661   
9662   // Tell listeners that synchronization is complete
9663   this.fireSynchronize(net.user1.orbiter.Status.SUCCESS);
9664 }
9665     
9666 /**
9667  * @private
9668  */
9669 net.user1.orbiter.Room.prototype.setSyncState = function (newSyncState) {
9670   this.syncState = newSyncState;
9671 };
9672 
9673 /**
9674  * @private
9675  */
9676 net.user1.orbiter.Room.prototype.updateSyncState = function () {
9677   if (this.disposed) {
9678     this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9679   } else {
9680     if (this.roomManager.hasObservedRoom(this.getRoomID()) 
9681         || this.roomManager.hasOccupiedRoom(this.getRoomID())
9682         || this.roomManager.hasWatchedRoom(this.getRoomID())) {
9683       this.setSyncState(net.user1.orbiter.SynchronizationState.SYNCHRONIZED);
9684     } else {
9685       this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9686     }
9687   }
9688 }
9689 
9690 net.user1.orbiter.Room.prototype.getSyncState = function () {
9691   return this.syncState;
9692 };
9693 
9694 //==============================================================================
9695 // UPDATE LEVELS
9696 //==============================================================================
9697 
9698 net.user1.orbiter.Room.prototype.setUpdateLevels = function (updateLevels) {
9699   if (this.messageManager) {
9700     this.messageManager.sendUPC(net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS,
9701                                 this.getRoomID(),
9702                                 updateLevels.toInt());
9703   }
9704 };
9705 
9706 //==============================================================================
9707 // OBSERVATION
9708 //==============================================================================
9709    
9710 net.user1.orbiter.Room.prototype.observe = function (password,
9711                                                      updateLevels) {
9712   if (this.disposed) return;
9713   
9714   this.roomManager.observeRoom(this.getRoomID(), 
9715                                password,
9716                                updateLevels);
9717 };
9718   
9719    
9720 net.user1.orbiter.Room.prototype.stopObserving = function () {
9721   if (this.disposed) return;
9722   
9723   if (this.clientIsObservingRoom()) {
9724     this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_ROOM, this.getRoomID());
9725   } else {
9726     this.log.debug(this + " Stop-observing-room request ignored. Not observing room.");
9727   }
9728 };
9729 
9730 /**
9731  * @private
9732  */
9733 net.user1.orbiter.Room.prototype.doObserve = function () {
9734   this._clientIsObservingRoom = true;
9735   this.fireObserve();
9736 };
9737 
9738 /**
9739  * @private
9740  */
9741 net.user1.orbiter.Room.prototype.doObserveResult = function (status) {
9742   this.fireObserveResult(status);
9743 };
9744 
9745 /**
9746  * @private
9747  */
9748 net.user1.orbiter.Room.prototype.doStopObserving = function () {
9749   var rid = this.getRoomID();
9750 
9751   // If the client is not in the room, then we dispose
9752   // of all of the room's information.
9753   if (!this.clientIsInRoom()) {
9754     this.purgeRoomData();
9755   }
9756     
9757   this._clientIsObservingRoom = false;
9758   
9759   this.fireStopObserving();
9760 };
9761     
9762 /**
9763  * @private
9764  */
9765 net.user1.orbiter.Room.prototype.doStopObservingResult = function (status) {
9766   this.fireStopObservingResult(status);
9767 }
9768     
9769 //==============================================================================
9770 // OCCUPANT MANAGEMENT
9771 //==============================================================================
9772 
9773 /**
9774  * @private
9775  */
9776 net.user1.orbiter.Room.prototype.addOccupant = function (client) {
9777   // Don't add the client if it's already in the list.
9778   if (this.occupantList.contains(client)) {
9779       this.log.info(this + " ignored addOccupant() request. Occupant list" +
9780                     " already contains client:" + client + ".");
9781       return;
9782   }
9783 
9784   // Add the client
9785   this.occupantList.add(client);
9786   
9787   // Update the number of clients in the room
9788   this.setNumOccupants(this.occupantList.length());  
9789   
9790   // Register for attribute change events
9791   if (!this.observerList.contains(client)) {
9792     this.addClientAttributeListeners(client);
9793   }
9794   
9795   // Tell listeners an occupant was added
9796   this.fireAddOccupant(client.getClientID());  
9797 };
9798 
9799 /**
9800  * @private
9801  */
9802 net.user1.orbiter.Room.prototype.removeOccupant = function (clientID) {
9803   var client = this.occupantList.removeByClientID(clientID);
9804   var clientFound = client != null;
9805   
9806   // Update the number of clients in the room
9807   this.setNumOccupants(this.occupantList.length());
9808   
9809   // Unregister for attribute change events
9810   if (!this.observerList.contains(client)) {
9811     this.removeClientAttributeListeners(client);
9812   }
9813   
9814   // Tell listeners an occupant was removed
9815   var customClient = client.getCustomClient(this.getRoomID());
9816   this.fireRemoveOccupant(customClient != null ? customClient : client);
9817 
9818   if (!clientFound) {
9819     this.log.debug(this + " could not remove occupant: " 
9820                    + clientID + ". No such client in the room's occupant list.");
9821   }
9822 };
9823 
9824 net.user1.orbiter.Room.prototype.getOccupantIDs = function () {
9825   if (this.disposed) return null;
9826   
9827   return this.occupantList.getAllIDs();
9828 }
9829 
9830 net.user1.orbiter.Room.prototype.getOccupants = function () {
9831   if (this.disposed) return null;
9832   
9833   var occupants = this.occupantList.getAll();
9834   var occupantsList = new Array();
9835   var customClient;
9836   var occupant;
9837   
9838   for (var clientID in occupants) {
9839     occupant = occupants[clientID];
9840     customClient = occupant.getCustomClient(this.getRoomID());
9841     if (customClient != null) {
9842       occupantsList.push(customClient);
9843     } else {
9844       occupantsList.push(occupant);
9845     }
9846   }
9847   return occupantsList;
9848 }
9849         
9850 /**
9851  * @private
9852  */        
9853 net.user1.orbiter.Room.prototype.getOccupantsInternal = function () {
9854   return this.occupantList.getAll();
9855 }
9856 
9857 net.user1.orbiter.Room.prototype.clientIsInRoom = function (clientID) {
9858   if (this.disposed) return false;
9859       
9860   if (clientID == null) {
9861     return this._clientIsInRoom;
9862   }
9863   return this.occupantList.containsClientID(clientID);
9864 };
9865     
9866 net.user1.orbiter.Room.prototype.getNumOccupants = function () {
9867   if (this.disposed) return 0;
9868   
9869   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());;
9870   if (levels != null) {
9871     if (levels.occupantCount || levels.occupantList) {
9872       return this.numOccupants;
9873     } else {
9874       this.log.warn(this + " getNumOccupants() called, but no occupant count is " +
9875                     "available. To enable occupant count, turn on occupant list" +
9876                     " updates or occupant count updates via the Room's setUpdateLevels()" +
9877                     " method.");
9878       return 0;
9879     }
9880   } else {
9881     this.log.debug(this + " getNumOccupants() called, but the current client's update"
9882                    + " levels for the room are unknown. To determine the room's"
9883                    + " occupant count, first join or observe the room.");
9884     return 0;
9885   }
9886 };
9887 
9888 /**
9889  * @private
9890  */
9891 net.user1.orbiter.Room.prototype.setNumOccupants = function (newNumOccupants) {
9892   var oldNumClients = this.numOccupants;
9893   this.numOccupants = newNumOccupants;
9894 
9895   // Tell listeners that the number of clients in the room has changed.
9896   if (oldNumClients != newNumOccupants) {
9897     this.fireOccupantCount(newNumOccupants);
9898   }
9899 };
9900     
9901 //==============================================================================
9902 // ROOM SETTINGS
9903 //==============================================================================
9904 
9905 net.user1.orbiter.Room.prototype.getRoomSettings = function () {
9906   if (this.disposed) return null;
9907   
9908   var settings = new net.user1.orbiter.RoomSettings();
9909   var maxClients = this.getAttribute(net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR);
9910   var removeOnEmpty = this.getAttribute(net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR);
9911   
9912   settings.maxClients = maxClients == null ? null : maxClients;
9913   switch (removeOnEmpty) {
9914     case null:
9915       settings.removeOnEmpty = null;
9916       break;
9917       
9918     case "true":
9919       settings.removeOnEmpty = true;
9920       break;
9921       
9922     case "false":
9923       settings.removeOnEmpty = false;
9924       break;
9925   }
9926   
9927   return settings;
9928 };
9929 
9930 net.user1.orbiter.Room.prototype.setRoomSettings = function (settings) {
9931   if (this.disposed) return;
9932   
9933   if (settings.maxClients != null) {
9934     this.setAttribute(net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR, settings.maxClients.toString());
9935   } 
9936   if (settings.password != null) {
9937     this.setAttribute(net.user1.orbiter.Tokens.PASSWORD_ATTR, settings.password);
9938   } 
9939   if (settings.removeOnEmpty != null) {
9940     this.setAttribute(net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR, settings.removeOnEmpty.toString());
9941   } 
9942 };
9943     
9944 //==============================================================================
9945 // OBSERVER MANAGEMENT
9946 //==============================================================================
9947 
9948 /**
9949  * @private
9950  */
9951 net.user1.orbiter.Room.prototype.addObserver = function (client) {
9952   // Don't add the client if it's already in the list.
9953   if (this.observerList.contains(client)) {
9954       this.log.info(this + " ignored addObserver() request. Observer list" +
9955                     " already contains client:" + client + ".");
9956       return;
9957   }
9958 
9959   // Add the client
9960   this.observerList.add(client);
9961   
9962   // Update the number of clients in the room
9963   this.setNumObservers(this.observerList.length());  
9964   
9965   // Register for attribute change events
9966   if (!this.occupantList.contains(client)) {
9967     this.addClientAttributeListeners(client);
9968   }
9969   
9970   // Tell listeners an observer was added
9971   this.fireAddObserver(client.getClientID());  
9972 };
9973 
9974 /**
9975  * @private
9976  */
9977 net.user1.orbiter.Room.prototype.removeObserver = function (clientID) {
9978   var client = this.observerList.removeByClientID(clientID);
9979   var clientFound = client != null;
9980   
9981   // Update the number of clients in the room
9982   this.setNumObservers(this.observerList.length());
9983   
9984   // Unregister for attribute change events
9985   if (!this.occupantList.contains(client)) {
9986     this.removeClientAttributeListeners(client);
9987   }
9988   
9989   // Tell listeners an observer was removed
9990   var customClient = client.getCustomClient(this.getRoomID());
9991   this.fireRemoveObserver(customClient != null ? customClient : client);
9992 
9993   if (!clientFound) {
9994     this.log.debug(this + " could not remove observer: " 
9995                    + clientID + ". No such client in the room's observer list.");
9996   }
9997 };
9998 
9999 net.user1.orbiter.Room.prototype.getObserverIDs = function () {
10000   if (this.disposed) return null;
10001   
10002   return this.observerList.getAllIDs();
10003 };
10004 
10005 net.user1.orbiter.Room.prototype.getObservers = function () {
10006   if (this.disposed) return null;
10007   
10008   var observers = this.observerList.getAll();
10009   var observersList = new Array();
10010   var customClient;
10011   var observer;
10012   
10013   for (var clientID in observers) {
10014     observer = observers[clientID];
10015     customClient = observer.getCustomClient(this.getRoomID());
10016     if (customClient != null) {
10017       observersList.push(customClient);
10018     } else {
10019       observersList.push(observer);
10020     }
10021   }
10022   return observersList;
10023 };
10024     
10025 /**
10026  * @private
10027  */        
10028 net.user1.orbiter.Room.prototype.getObserversInternal = function () {
10029   return this.observerList.getAll();
10030 }
10031 
10032 net.user1.orbiter.Room.prototype.clientIsObservingRoom = function (clientID) {
10033   if (this.disposed) return false;
10034   
10035   if (clientID == null) {
10036     return this._clientIsObservingRoom;
10037   }
10038   return this.observerList.containsClientID(clientID);
10039 }
10040     
10041 net.user1.orbiter.Room.prototype.getNumObservers = function () {
10042   if (this.disposed) return 0;
10043   
10044   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());
10045   if (levels != null) {
10046     if (levels.observerCount || levels.observerList) {
10047       return this.numObservers;
10048     } else {
10049       this.log.warn(this + " getNumObservers() called, but no observer count is " +
10050                "available. To enable observer count, turn on observer list" +
10051                " updates or observer count updates via the Room's setUpdateLevels()" +
10052                " method.");
10053       return 0;
10054     }
10055   } else {
10056     this.log.warn(this + " getNumObservers() called, but the current client's update "
10057       + " levels for the room are unknown. Please report this issue to union@user1.net.");
10058     return 0;
10059   }
10060 };
10061 
10062 /**
10063  * @private
10064  */
10065 net.user1.orbiter.Room.prototype.setNumObservers = function (newNumObservers) {
10066   var oldNumClients = this.numObservers;
10067   this.numObservers = newNumObservers;
10068 
10069   // Tell listeners that the number of clients in the room has changed.
10070   if (oldNumClients != newNumObservers) {
10071     this.fireObserverCount(newNumObservers);
10072   }
10073 };
10074     
10075 //==============================================================================
10076 // CLIENT ACCESS
10077 //==============================================================================
10078 
10079 net.user1.orbiter.Room.prototype.getClient = function (id) {
10080   if (this.disposed) return null;
10081   
10082   var customClient;
10083   var client = this.occupantList.getByClientID(id);
10084   client = (client == null) ? this.observerList.getByClientID(id) : client;
10085   
10086   if (client != null) {
10087     customClient = client.getCustomClient(this.getRoomID());
10088   }
10089   return customClient == null ? client : customClient;
10090 };
10091     
10092 //==============================================================================
10093 // CLIENT ATTRIBUTE LISTENERS
10094 //==============================================================================
10095 /** private */
10096 net.user1.orbiter.Room.prototype.addClientAttributeListeners = function (client) {
10097   client.addEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateClientAttributeListener, this);
10098   client.addEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteClientAttributeListener, this);
10099 };
10100     
10101 /** private */
10102 net.user1.orbiter.Room.prototype.removeClientAttributeListeners = function (client) {
10103   client.removeEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateClientAttributeListener, this);
10104   client.removeEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteClientAttributeListener, this);
10105 };
10106     
10107 /** private */
10108 net.user1.orbiter.Room.prototype.updateClientAttributeListener = function (e) {
10109   var attr = e.getChangedAttr();
10110   var client = e.target;
10111   var customClient = client.getCustomClient(this.getRoomID());
10112   
10113   this.fireUpdateClientAttribute(customClient == null ? client : customClient,
10114                                  attr.scope, attr.name, attr.value, attr.oldValue);
10115 };
10116     
10117 /** private */
10118 net.user1.orbiter.Room.prototype.deleteClientAttributeListener = function (e) {
10119   var attr = e.getChangedAttr();
10120   var client = e.target;
10121   var customClient = client.getCustomClient(this.getRoomID());
10122   
10123   this.fireDeleteClientAttribute(customClient == null ? client : customClient,
10124                                  attr.scope, attr.name, attr.value);
10125 }
10126     
10127 //==============================================================================
10128 // CLIENT CLASS
10129 //==============================================================================
10130 
10131 net.user1.orbiter.Room.prototype.setDefaultClientClass = function (defaultClass) {
10132   this.defaultClientClass = defaultClass;
10133 };
10134     
10135 net.user1.orbiter.Room.prototype.getDefaultClientClass = function () {
10136   return this.defaultClientClass;
10137 }
10138     
10139 //==============================================================================
10140 // ATTRIBUTES
10141 //==============================================================================
10142 
10143 net.user1.orbiter.Room.prototype.setAttribute = function (attrName,
10144                                   attrValue, 
10145                                   isShared,
10146                                   isPersistent,
10147                                   evaluate) {
10148   if (this.disposed) return;
10149   
10150   if (isShared !== false) {
10151     isShared = true;
10152   }
10153   
10154   // Create an integer to hold the attribute options.
10155   var attrOptions = (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
10156                     | (isPersistent ? net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT : 0)
10157                     | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
10158   this.attributeManager.setAttribute(new net.user1.orbiter.upc.SetRoomAttr(attrName, attrValue, attrOptions, this.getRoomID()));
10159 }
10160   
10161 net.user1.orbiter.Room.prototype.deleteAttribute = function (attrName) {
10162   if (this.disposed) return;
10163   
10164   var deleteRequest = new net.user1.orbiter.upc.RemoveRoomAttr(this.getRoomID(), attrName);
10165   this.attributeManager.deleteAttribute(deleteRequest);
10166 };
10167   
10168 net.user1.orbiter.Room.prototype.getAttribute = function (attrName) {
10169   if (this.disposed) return null;
10170   
10171   return this.attributeManager.getAttribute(attrName);
10172 };
10173   
10174 net.user1.orbiter.Room.prototype.getAttributes = function () {
10175   if (this.disposed) return null;
10176   
10177   // Room attributes are considered global
10178   return this.attributeManager.getAttributesByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR);
10179 }
10180 
10181 // =============================================================================
10182 // ROOM MODULES
10183 // =============================================================================
10184 
10185 net.user1.orbiter.Room.prototype.sendModuleMessage = function (messageName, 
10186                                                                messageArguments) {
10187   if (this.disposed) return;
10188   
10189   var sendupcArgs = [net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE, this.getRoomID(), messageName];
10190   
10191   for (var arg in messageArguments) {
10192     sendupcArgs.push(arg + "|" + messageArguments[arg]);
10193   }
10194         
10195   this.messageManager.sendUPC.apply(this.messageManager, sendupcArgs);
10196 };
10197 
10198 // =============================================================================
10199 // ROOM REMOVAL
10200 // =============================================================================
10201 
10202 net.user1.orbiter.Room.prototype.remove = function (password) {
10203   if (this.disposed) return;
10204   
10205   this.roomManager.removeRoom(this.getRoomID(), password);
10206 };
10207 
10208 // =============================================================================
10209 // TOSTRING
10210 // =============================================================================
10211 
10212 net.user1.orbiter.Room.prototype.toString = function () {
10213   return "[ROOM id: " + this.getRoomID() + "]";
10214 };
10215     
10216     
10217 // =============================================================================
10218 // EVENT DISPATCHING
10219 // =============================================================================
10220 
10221 /**
10222  * @private
10223  */
10224 net.user1.orbiter.Room.prototype.fireJoin = function () {
10225   if (this.log) this.log.info(this + " Room joined.");
10226 
10227   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN);
10228   this.dispatchEvent(e);
10229 };
10230   
10231 /**
10232  * @private
10233  */
10234 net.user1.orbiter.Room.prototype.fireJoinResult = function (status) {
10235   if (this.log) this.log.info(this + " Join result: " + status);
10236 
10237   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN_RESULT, 
10238                                   null,  null, status);
10239   this.dispatchEvent(e);
10240 };
10241   
10242 /**
10243  * @private
10244  */
10245 net.user1.orbiter.Room.prototype.fireLeave = function () {
10246   if (this.log) this.log.info(this + " Room left.");
10247   
10248   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE);
10249   this.dispatchEvent(e);
10250 };
10251   
10252 /**
10253  * @private
10254  */
10255 net.user1.orbiter.Room.prototype.fireLeaveResult = function (status) {
10256   if (this.log) this.log.info(this + " Leave result: " + status);
10257   
10258   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE_RESULT, 
10259                                   null, null, status);
10260   this.dispatchEvent(e);
10261 };
10262   
10263 /**
10264  * @private
10265  */
10266 net.user1.orbiter.Room.prototype.fireAddOccupant = function (id) {
10267   if (this.log) this.log.info(this + " Added occupant: [" + id + "].");
10268 
10269   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.ADD_OCCUPANT, 
10270                                   this.getClient(id),
10271                                   id);
10272   this.dispatchEvent(e);
10273 };
10274   
10275 /**
10276  * @private
10277  */
10278 net.user1.orbiter.Room.prototype.fireRemoveOccupant = function (client) {
10279   if (this.log) this.log.info(this + " Removed occupant: " + client + ".");
10280 
10281   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT,
10282                                   client,
10283                                   client.getClientID());
10284   this.dispatchEvent(e);
10285 };
10286   
10287 /**
10288  * @private
10289  */
10290 net.user1.orbiter.Room.prototype.fireAddObserver = function (id) {
10291   if (this.log) this.log.info(this + " Added observer: [" + id + "].");
10292 
10293   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.ADD_OBSERVER, 
10294                                   this.getClient(id),
10295                                   id);
10296   this.dispatchEvent(e);
10297 };
10298   
10299 /**
10300  * @private
10301  */
10302 net.user1.orbiter.Room.prototype.fireRemoveObserver = function (client) {
10303   if (this.log) this.log.info(this + " Removed observer: " + client + ".");
10304 
10305   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVE_OBSERVER,
10306                                   client,
10307                                   client.getClientID());
10308   this.dispatchEvent(e);
10309 };
10310   
10311 /**
10312  * @private
10313  */
10314 net.user1.orbiter.Room.prototype.fireUpdateClientAttribute = function (client, 
10315                                              scope,
10316                                              attrName,
10317                                              attrVal, 
10318                                              oldVal) {
10319   if (this.log) this.log.info(this + " Client attribute updated on " + client + "."
10320             + " Attribute [" + attrName + "] is now: [" 
10321             + attrVal + "]. Old value was: [" + oldVal + "].");
10322 
10323   var changedAttr = new net.user1.orbiter.Attribute (attrName, 
10324                                              attrVal, 
10325                                              oldVal, 
10326                                              scope);
10327   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE,
10328                                   client,
10329                                   client.getClientID(), 
10330                                   null, 
10331                                   changedAttr);
10332   this.dispatchEvent(e);
10333 };
10334 
10335 /**
10336  * @private
10337  */
10338 net.user1.orbiter.Room.prototype.fireDeleteClientAttribute = function (client, 
10339                                                  scope,
10340                                                  attrName,
10341                                                  attrValue) {
10342   if (this.log) this.log.info(this + " Client attribute deleted from " + client + "."
10343            + " Deleted attribute: [" + attrName + "].");
10344 
10345   var deletedAttr = new net.user1.orbiter.Attribute(attrName, attrValue, null, scope);
10346 
10347   // Trigger event on listeners.
10348   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE,
10349                                   client,
10350                                   client.getClientID(),
10351                                   null,
10352                                   deletedAttr);
10353   this.dispatchEvent(e);
10354 };
10355     
10356 /**
10357  * @private
10358  */
10359 net.user1.orbiter.Room.prototype.fireOccupantCount = function (newNumClients) {
10360   if (this.log) this.log.info(this + " New occupant count: " + newNumClients);
10361 
10362   // Trigger event on listeners.
10363   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OCCUPANT_COUNT, 
10364                              null, null, null, null, newNumClients);
10365   this.dispatchEvent(e);
10366 };
10367     
10368 /**
10369  * @private
10370  */
10371 net.user1.orbiter.Room.prototype.fireObserverCount = function (newNumClients) {
10372   if (this.log) this.log.info(this + " New observer count: " + newNumClients);
10373 
10374   // Trigger event on listeners.
10375   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVER_COUNT, 
10376                              null, null, null, null, newNumClients);
10377   this.dispatchEvent(e);
10378 };
10379     
10380 /**
10381  * @private
10382  */
10383 net.user1.orbiter.Room.prototype.fireSynchronize = function (status) {
10384   if (this.log) this.log.info(this + " Synchronization complete.");
10385   
10386   // Trigger event on listeners.
10387   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.SYNCHRONIZE,
10388                                           null, null, status);
10389   this.dispatchEvent(e);
10390 };
10391 
10392 /**
10393  * @private
10394  */
10395 net.user1.orbiter.Room.prototype.fireObserve = function () {
10396   if (this.log) this.log.info(this + " Room observed.");
10397   
10398   // Trigger event on listeners.
10399   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE);
10400   this.dispatchEvent(e);
10401 };
10402 
10403 /**
10404  * @private
10405  */
10406 net.user1.orbiter.Room.prototype.fireObserveResult = function (status) {
10407   if (this.log) this.log.info(this + " Observe result: " + status);
10408   
10409   // Trigger event on listeners.
10410   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE_RESULT, null, null, status);
10411   this.dispatchEvent(e);
10412 };
10413 
10414 /**
10415  * @private
10416  */
10417 net.user1.orbiter.Room.prototype.fireStopObserving = function () {
10418   if (this.log) this.log.info(this + " Observation stopped.");
10419   
10420   // Trigger event on listeners.
10421   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING);
10422   this.dispatchEvent(e);
10423 };
10424     
10425 /**
10426  * @private
10427  */
10428 net.user1.orbiter.Room.prototype.fireStopObservingResult = function (status) {
10429   if (this.log) this.log.info(this + "Stop observing result:  " + 
10430            status);
10431   
10432   // Trigger event on listeners.
10433   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT,
10434                                   null, null, status);
10435   this.dispatchEvent(e);
10436 };
10437     
10438 /**
10439  * @private
10440  */
10441 net.user1.orbiter.Room.prototype.fireRemoved = function () {
10442   // Trigger event on listeners.
10443   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVED);
10444   this.dispatchEvent(e);
10445 };
10446 
10447 //==============================================================================
10448 // CLEANUP and DISPOSAL
10449 //==============================================================================
10450 /** private */
10451 net.user1.orbiter.Room.prototype.purgeRoomData = function () {
10452   if (this.disposed) return;
10453   
10454   // Clear the client lists
10455   this.log.debug(this + " Clearing occupant list.");
10456   for (var occupantID in this.occupantList.getAll()) {
10457     this.removeClientAttributeListeners(this.occupantList.getByClientID(occupantID));
10458   }
10459   this.occupantList.removeAll();
10460   
10461   this.log.debug(this + " Clearing observer list.");
10462   for (var observerID in this.observerList.getAll()) {
10463     this.removeClientAttributeListeners(this.observerList.getByClientID(observerID));
10464   }
10465   this.observerList.removeAll();
10466       
10467   // Clear room attributes.
10468   this.log.debug(this + " Clearing room attributes.");
10469   this.attributeManager.removeAll();
10470 };
10471     
10472 /**
10473  * @private
10474  */
10475 net.user1.orbiter.Room.prototype.shutdown = function () {
10476   if (this.disposed) return;
10477   
10478   // Store a temp reference to the log for use in this method after
10479   // the room has released all its resources.
10480   var theLog = this.log;
10481   
10482   theLog.debug(this + " Shutdown started.");
10483 
10484   // Notify the room's listeners that the client left the room.
10485   if (this.clientIsInRoom()) {
10486     theLog.info(this + " Current client is in the room. Forcing the client to leave...");
10487     this.doLeave();
10488   }
10489 
10490   // Notify the room's listeners that the client stopped observing the room.
10491   if (this.clientIsObservingRoom()) {
10492     theLog.info(this + " Current client is observing the room. Forcing the client to stop observing...");
10493     this.doStopObserving();
10494   }
10495   
10496   theLog.info(this + " Dereferencing resources.");
10497   
10498   // Dereference objects.
10499   this.purgeRoomData();
10500   
10501   this.attributeManager.dispose();
10502   // Fire removed before nulling the MessageManager object so that listeners have a
10503   // last chance to respond by communicating with the server (or by
10504   // removing themselves from the connection's listener list)
10505   this.fireRemoved();  
10506   this.dispose();
10507 
10508   theLog.info(this + " Shutdown complete.");
10509 }
10510 
10511 /**
10512  * @private
10513  */
10514 net.user1.orbiter.Room.prototype.dispose = function () {
10515   this.log = null;
10516   this.syncState = null;
10517   this.occupantList = null;
10518   this.observerList = null;
10519   this.attributeManager = null;
10520   this.numOccupants = 0;
10521   this.defaultClientClass = null
10522   this.messageManager = null;
10523   this.roomManager = null;
10524   this.disposed = true;
10525 };
10526 //==============================================================================
10527 // CLASS DECLARATION
10528 //==============================================================================
10529 /**
10530  * @class
10531  */
10532 net.user1.orbiter.RoomClassRegistry = function () {
10533   this.registry = new Object();
10534 };
10535 
10536 net.user1.orbiter.RoomClassRegistry.prototype.setRoomClass = function (roomID, roomClass) {
10537   this.registry[roomID] = roomClass;
10538 };
10539 
10540 net.user1.orbiter.RoomClassRegistry.prototype.clearRoomClass = function (roomID) {
10541   delete this.registry[roomID];
10542 };
10543 
10544 
10545 net.user1.orbiter.RoomClassRegistry.prototype.getRoomClass = function (roomID) {
10546   return this.registry[roomID] ? this.registry[roomID] : net.user1.orbiter.Room;
10547 };
10548 //==============================================================================
10549 // CLASS DECLARATION
10550 //==============================================================================
10551 /** @class
10552     @extends net.user1.events.Event
10553 */
10554 net.user1.orbiter.RoomEvent = function (type,
10555                                         client,
10556                                         clientID, 
10557                                         status, 
10558                                         changedAttr, 
10559                                         numClients,
10560                                         roomID) {
10561   net.user1.events.Event.call(this, type);
10562   
10563   this.client = client;
10564   this.clientID = clientID == "" ? null : clientID;
10565   this.status = status;
10566   this.changedAttr = changedAttr;
10567   this.numClients = numClients;
10568   this.roomID = roomID;
10569 };
10570 
10571 
10572 //==============================================================================
10573 // INHERITANCE
10574 //==============================================================================
10575 net.user1.utils.extend(net.user1.orbiter.RoomEvent, net.user1.events.Event);
10576 
10577 //==============================================================================
10578 // STATIC VARIABLES
10579 //==============================================================================
10580 
10581 /** @constant */
10582 net.user1.orbiter.RoomEvent.JOIN = "JOIN";
10583 /** @constant */
10584 net.user1.orbiter.RoomEvent.JOIN_RESULT = "JOIN_RESULT";
10585 /** @constant */
10586 net.user1.orbiter.RoomEvent.LEAVE = "LEAVE";
10587 /** @constant */
10588 net.user1.orbiter.RoomEvent.LEAVE_RESULT = "LEAVE_RESULT";
10589 /** @constant */
10590 net.user1.orbiter.RoomEvent.OBSERVE = "OBSERVE";
10591 /** @constant */
10592 net.user1.orbiter.RoomEvent.OBSERVE_RESULT = "OBSERVE_RESULT";    
10593 /** @constant */
10594 net.user1.orbiter.RoomEvent.STOP_OBSERVING = "STOP_OBSERVING";
10595 /** @constant */
10596 net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";        
10597 /** @constant */
10598 net.user1.orbiter.RoomEvent.SYNCHRONIZE = "SYNCHRONIZE";
10599 /** @constant */
10600 net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE = "UPDATE_CLIENT_ATTRIBUTE";
10601 /** @constant */
10602 net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE = "DELETE_CLIENT_ATTRIBUTE";
10603 /** @constant */
10604 net.user1.orbiter.RoomEvent.ADD_OCCUPANT = "ADD_OCCUPANT";
10605 /** @constant */
10606 net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT = "REMOVE_OCCUPANT";
10607 /** @constant */
10608 net.user1.orbiter.RoomEvent.ADD_OBSERVER = "ADD_OBSERVER";
10609 /** @constant */
10610 net.user1.orbiter.RoomEvent.REMOVE_OBSERVER = "REMOVE_OBSERVER";
10611 /** @constant */
10612 net.user1.orbiter.RoomEvent.OCCUPANT_COUNT = "OCCUPANT_COUNT";
10613 /** @constant */
10614 net.user1.orbiter.RoomEvent.OBSERVER_COUNT = "OBSERVER_COUNT";
10615 /** @constant */
10616 net.user1.orbiter.RoomEvent.REMOVED = "REMOVED";
10617 
10618 
10619 net.user1.orbiter.RoomEvent.prototype.getRoomID = function () {
10620   return this.roomID;
10621 };
10622 
10623 net.user1.orbiter.RoomEvent.prototype.getClient = function () {
10624   return this.client;
10625 };
10626 
10627 net.user1.orbiter.RoomEvent.prototype.getClientID = function () {
10628   return this.clientID;
10629 };
10630 
10631 net.user1.orbiter.RoomEvent.prototype.getStatus = function () {
10632   return this.status;
10633 };
10634 
10635 net.user1.orbiter.RoomEvent.prototype.getNumClients = function () {
10636   return this.numClients;
10637 };
10638 
10639 net.user1.orbiter.RoomEvent.prototype.getChangedAttr = function () {
10640   return this.changedAttr;
10641 };
10642 
10643 net.user1.orbiter.RoomEvent.prototype.toString = function () {
10644   return "[object RoomEvent]";
10645 };  
10646 //==============================================================================
10647 // CLASS DECLARATION
10648 //==============================================================================
10649   /**
10650    * @private
10651    */  
10652 net.user1.orbiter.RoomList = function () {
10653   // Call superconstructor
10654   net.user1.events.EventDispatcher.call(this);
10655     
10656   this.rooms = new Array();
10657 };
10658 
10659 //==============================================================================    
10660 // INHERITANCE
10661 //============================================================================== 
10662 net.user1.utils.extend(net.user1.orbiter.RoomList, net.user1.events.EventDispatcher);  
10663 
10664 //==============================================================================    
10665 // INSTANCE METHODS
10666 //============================================================================== 
10667 net.user1.orbiter.RoomList.prototype.add = function (room) {
10668   if (!this.contains(room)) {
10669     this.rooms.push(room);
10670     this.dispatchAddItem(room);
10671     return room;
10672   } else {
10673     return null;
10674   }
10675 };
10676 
10677 net.user1.orbiter.RoomList.prototype.remove = function (room) {
10678   var index = net.user1.utils.ArrayUtil.indexOf(this.rooms, room);
10679   if (index != -1) {
10680     this.rooms.splice(index, 1)[0];
10681     this.dispatchRemoveItem(room);
10682     return room;
10683   } else {
10684     return null;
10685   }
10686 };
10687 
10688 net.user1.orbiter.RoomList.prototype.removeAll = function () {
10689   var room;
10690   for (var i = this.rooms.length; --i >= 0; ) {
10691     room = this.rooms.splice(i, 1)[0];
10692     this.dispatchRemoveItem(room);
10693   }
10694 };
10695 
10696 net.user1.orbiter.RoomList.prototype.removeByRoomID = function (roomID) {
10697   var room;
10698   for (var i = this.rooms.length; --i >= 0; ) {
10699     if (this.rooms[i].getRoomID() == roomID) {
10700       room = this.rooms.splice(i, 1)[0];
10701       this.dispatchRemoveItem(room);
10702       return room;
10703     }
10704   }
10705   return null;
10706 };
10707 
10708 net.user1.orbiter.RoomList.prototype.contains = function (room) {
10709   return net.user1.utils.ArrayUtil.indexOf(this.rooms, room) != -1;
10710 }
10711 
10712 net.user1.orbiter.RoomList.prototype.containsRoomID = function (roomID) {
10713   if (roomID == "" || roomID == null) {
10714     return false;
10715   }
10716   return this.getByRoomID(roomID) != null;
10717 }
10718 
10719 net.user1.orbiter.RoomList.prototype.getByRoomID = function (roomID) {
10720   var room;
10721   for (var i = this.rooms.length; --i >= 0;) {
10722     room = this.rooms[i];
10723     if (room.getRoomID() == roomID) {
10724       return room;
10725     }
10726   }
10727   return null;
10728 };
10729 
10730 net.user1.orbiter.RoomList.prototype.getAll = function () {
10731   return this.rooms.slice(0);
10732 };
10733 
10734 net.user1.orbiter.RoomList.prototype.length = function () {
10735   return this.rooms.length;
10736 }
10737 
10738 net.user1.orbiter.RoomList.prototype.dispatchAddItem = function (item) {
10739   this.dispatchEvent(new net.user1.orbiter.CollectionEvent(net.user1.orbiter.CollectionEvent.ADD_ITEM, item));
10740 };
10741 
10742 net.user1.orbiter.RoomList.prototype.dispatchRemoveItem = function (item) {
10743   this.dispatchEvent(new net.user1.orbiter.CollectionEvent(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, item));
10744 };
10745 //==============================================================================
10746 // CLASS DECLARATION
10747 //==============================================================================
10748 /** 
10749  * @class
10750  * @extends net.user1.orbiter.snapshot.Snapshot
10751  */
10752 net.user1.orbiter.snapshot.RoomListSnapshot = function (qualifier, 
10753                                                         recursive) {
10754   // Call superconstructor
10755   net.user1.orbiter.snapshot.Snapshot.call(this);
10756   this.roomList = null;
10757   this.qualifier = null;
10758   this.recursive = null;
10759   this.method = net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT;
10760   this.args   = [qualifier,
10761                  recursive];
10762 };
10763     
10764 //==============================================================================
10765 // INHERITANCE
10766 //==============================================================================
10767 net.user1.utils.extend(net.user1.orbiter.snapshot.RoomListSnapshot, net.user1.orbiter.snapshot.Snapshot);
10768 
10769 //==============================================================================
10770 // INSTANCE METHODS
10771 //==============================================================================                
10772 /**
10773  * @private
10774  */    
10775 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setRoomList = function (value) {
10776   this.roomList = value;
10777 };
10778 
10779 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getRoomList = function () {
10780   if (!this.roomList) {
10781     return null;
10782   }
10783   return this.roomList.slice();
10784 };
10785     
10786 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getQualifier = function () {
10787   return this.qualifier;
10788 };
10789         
10790     /**
10791      * @private
10792      */        
10793 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setQualifier = function (value) {
10794   this.qualifier = value;
10795 };
10796     
10797 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getRecursive = function () {
10798   return this.recursive;
10799 };
10800         
10801 /**
10802  * @private
10803  */    
10804 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setRecursive = function (value) {
10805   this.recursive = value;
10806 };
10807 //==============================================================================
10808 // CLASS DECLARATION
10809 //==============================================================================
10810 /** @class
10811 
10812 The RoomManager class dispatches the following events:
10813 
10814 <ul class="summary">
10815 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT}</li>   
10816 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT}</li>   
10817 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT}</li>   
10818 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT}</li>   
10819 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_ADDED}</li>   
10820 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED}</li>   
10821 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_COUNT}</li>   
10822 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN_RESULT}</li>   
10823 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE_RESULT}</li>   
10824 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE_RESULT}</li>   
10825 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT}</li>   
10826 </ul>
10827 
10828 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
10829 
10830     @extends net.user1.events.EventDispatcher
10831 */
10832 net.user1.orbiter.RoomManager = function (orbiter) {
10833   // Call superconstructor
10834   net.user1.events.EventDispatcher.call(this);
10835     
10836   this.watchedQualifiers = [];
10837   
10838   this.cachedRooms = new net.user1.orbiter.RoomList();
10839   this.occupiedRooms = new net.user1.orbiter.RoomList();
10840   this.observedRooms = new net.user1.orbiter.RoomList();
10841   this.watchedRooms = new net.user1.orbiter.RoomList();
10842   
10843   this.cachedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10844   this.occupiedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10845   this.occupiedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10846   this.observedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10847   this.observedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10848   this.watchedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10849   this.watchedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10850   
10851   this.orbiter = orbiter;
10852 
10853   this.addEventListener(net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
10854                         this.watchForRoomsResultListener, this);
10855   this.addEventListener(net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
10856                         this.stopWatchingForRoomsResultListener, this);
10857                                                  
10858 
10859   this.roomClassRegistry = new net.user1.orbiter.RoomClassRegistry();
10860                              
10861   // Store a reference to the this.log.
10862   this.log = this.orbiter.getLog();
10863 };
10864 
10865 //==============================================================================    
10866 // INHERITANCE
10867 //============================================================================== 
10868 net.user1.utils.extend(net.user1.orbiter.RoomManager, net.user1.events.EventDispatcher);    
10869 
10870 //==============================================================================
10871 // ROOM CREATION AND REMOVAL
10872 //==============================================================================
10873 /**
10874 * @param attributes An array of JavaScript objects that describes the initial room 
10875 * attributes for the room in the following format (note that this format differs
10876 * from the XML format used for createRoom() by Reactor, Union's Flash client framework):
10877 * 
10878 * [
10879 *   attribute: {
10880 *     name:"attrName1",
10881 *     value:"attrValue1",
10882 *     shared:true,
10883 *     persistent:false,
10884 *     immutable:false
10885 *   },
10886 *   attribute: {
10887 *     name:"attrName2",
10888 *     value:"attrValue2",
10889 *     shared:true,
10890 *     persistent:false,
10891 *     immutable:false
10892 *   }
10893 * ]
10894 * </listing>
10895 */
10896 
10897 net.user1.orbiter.RoomManager.prototype.createRoom = function (roomID, 
10898                                                                roomSettings,
10899                                                                attributes,
10900                                                                modules) {
10901   // GET ROOM SETTINGS
10902   if (roomSettings == null) {
10903     roomSettings = new net.user1.orbiter.RoomSettings();
10904   }
10905   
10906   // GET ROOM MODULES
10907   if (modules == null) {
10908     modules = new net.user1.orbiter.RoomModules();
10909   }
10910 
10911   // ERROR CHECKING
10912   
10913   // Abort if invalid module name found.
10914   var moduleIDs = modules.getIdentifiers();
10915   var moduleID;
10916   for (var i = moduleIDs.length; --i >= 0;) {
10917     var moduleID = moduleIDs[i];
10918     if (!net.user1.orbiter.Validator.isValidModuleName(moduleID)) {
10919       throw new Error("[ROOM_MANAGER] createRoom() failed. Illegal room module name: ["
10920                       + moduleID + "]. See net.user1.orbiter.Validator.isValidModuleName().");
10921     }
10922   }
10923 
10924   // If a roomID is specified, we must validated it
10925   if (roomID != null) {
10926     // Abort if the supplied id can't be resolved to a single room
10927     if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
10928       throw new Error("[ROOM_MANAGER] createRoom() failed. Illegal room id: ["
10929                 + roomID + "]. See net.user1.orbiter.Validator.isValidResolvedRoomID().");
10930     }
10931   }
10932 
10933   // MAKE THE ROOM LOCALLY
10934   
10935   // Send "" as the roomID if no roomID is specified. When the server
10936   // receives a request to create a roomID of "", it auto-generates
10937   // the id, and returns it via RoomManagerEvent.CREATE_ROOM_RESULT.
10938   if (roomID == null) {
10939     // Don't make the local room. Instead wait for the server to
10940     // report the new room via u39.
10941     roomID = "";
10942   } else {
10943     // Make the local room.
10944     this.addCachedRoom(roomID);
10945   }
10946 
10947   // TELL THE SERVER TO MAKE THE ROOM
10948 
10949   // Create attributes
10950   if (attributes != null) {
10951     var attr;
10952     var attrArg = "";
10953     for (var i = 0; i < attributes.length; i++) {
10954       attr = attributes[i];
10955       attrSettings = 0;
10956       attrSettings |= attr.shared ? AttributeOptions.FLAG_SHARED : 0;
10957       attrSettings |= attr.persistent ? AttributeOptions.FLAG_PERSISTENT : 0;
10958       attrSettings |= attr.immutable ? AttributeOptions.FLAG_IMMUTABLE : 0;
10959       attrArg += attr.NAME 
10960               + net.user1.orbiter.Tokens.RS + attr.VALUE
10961               + net.user1.orbiter.Tokens.RS + attrSettings.toString();
10962               
10963       if (i < attributes.length-1) {
10964         attrArg += Tokens.RS;
10965       }
10966     }
10967   }
10968 
10969   // Send the create room request to the server.
10970   var msgMan = this.orbiter.getMessageManager();
10971   msgMan.sendUPC(net.user1.orbiter.UPC.CREATE_ROOM, 
10972                  roomID, 
10973                  roomSettings.serialize(), 
10974                  attrArg, 
10975                  modules.serialize());
10976 
10977   // RETURN A REFERENCE TO THE LOCAL ROOM, IF ONE WAS CREATED
10978   if (roomID == "") {
10979     return null;
10980   } else {
10981     return this.getRoom(roomID);
10982   }
10983 };
10984 
10985 net.user1.orbiter.RoomManager.prototype.removeRoom = function (roomID, password) {
10986   // Quit if no room specified.
10987   if (roomID == null || !net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
10988     throw new Error("Invalid room id supplied to removeRoom(): ["
10989                     + roomID + "]. Request not sent.");
10990   }
10991   
10992   if (password == null) {
10993     password = "";
10994   }
10995 
10996   var msgMan = this.orbiter.getMessageManager();  
10997   msgMan.sendUPC(net.user1.orbiter.UPC.REMOVE_ROOM,
10998                  roomID,
10999                  password);
11000 };
11001 
11002 //==============================================================================
11003 // ROOM OBSERVATION
11004 //==============================================================================
11005 
11006 net.user1.orbiter.RoomManager.prototype.observeRoom = function (roomID,
11007                                                                 password,
11008                                                                 updateLevels) {
11009   var theRoom;
11010   
11011   // If the room is not valid, quit
11012   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
11013     throw new Error("Invalid room id supplied to observeRoom(): ["
11014               + roomID + "]. Request not sent."
11015               + " See net.user1.orbiter.Validator.isValidResolvedRoomID().");
11016   }
11017 
11018   // Try to get a reference to the room
11019   theRoom = this.getRoom(roomID);
11020     
11021   // If the room exists
11022   if (theRoom != null) {
11023     if (theRoom.clientIsObservingRoom()) {
11024       this.log.warn("[ROOM_MANAGER] Room observe attempt ignored. Already observing room: '" 
11025                + roomID + "'.");
11026       return null;
11027     } 
11028   } else {
11029     // Make the local room
11030     theRoom = this.addCachedRoom(roomID);
11031   }
11032     
11033   // Validate the password
11034   if (password == null) {
11035     password = "";
11036   }
11037   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
11038     throw new Error("Invalid room password supplied to observeRoom(). "
11039               + " Room ID: [" + roomID + "], password: [" + password + "]." 
11040               + " See net.user1.orbiter.Validator.isValidPassword().");
11041   }
11042 
11043     // If update levels were specified for this room, send them now.
11044   if (updateLevels != null) {
11045     theRoom.setUpdateLevels(updateLevels);
11046   }
11047 
11048   // Send the UPC only if at least one valid room was found      
11049   var msgMan = this.orbiter.getMessageManager();  
11050   msgMan.sendUPC(net.user1.orbiter.UPC.OBSERVE_ROOM, 
11051                  roomID,
11052                  password);
11053   
11054   return theRoom; 
11055 };
11056 
11057 //==============================================================================
11058 // WATCHING FOR ROOMS
11059 //==============================================================================
11060 
11061 net.user1.orbiter.RoomManager.prototype.watchForRooms = function (roomQualifier) {
11062   var recursive = false;
11063   
11064   // null means watch the whole server
11065   if (roomQualifier == null) {
11066     roomQualifier = "";
11067     recursive = true;
11068   }
11069 
11070   var msgMan = this.orbiter.getMessageManager();
11071   msgMan.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_ROOMS,
11072                  roomQualifier,
11073                  recursive.toString());
11074 };
11075 
11076 net.user1.orbiter.RoomManager.prototype.stopWatchingForRooms = function (roomQualifier) {
11077   var recursive = false;
11078   // null means whole server
11079   if (roomQualifier == null) {
11080     roomQualifier = "";
11081     recursive = true;
11082   }
11083 
11084   var msgMan = this.orbiter.getMessageManager();
11085   msgMan.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS, 
11086                  roomQualifier,
11087                  recursive.toString());
11088 };
11089 
11090 net.user1.orbiter.RoomManager.prototype.isWatchingQualifier = function (qualifier) {
11091   return net.user1.utils.ArrayUtil.indexOf(this.watchedQualifiers, qualifier) != -1;
11092 };
11093 
11094 /**
11095  * @private
11096  */
11097 net.user1.orbiter.RoomManager.prototype.watchForRoomsResultListener = function (e) {
11098   if (e.getStatus() == net.user1.orbiter.Status.SUCCESS) {
11099     this.watchedQualifiers.push(e.getRoomIdQualifier());
11100   }
11101 };
11102 
11103 /**
11104  * @private
11105  */
11106 net.user1.orbiter.RoomManager.prototype.stopWatchingForRoomsResultListener = function (e) {
11107   var unwatchedQualifier = e.getRoomIdQualifier();
11108   var unwatchedQualifierIndex;
11109   
11110   if (e.getStatus() == net.user1.orbiter.Status.SUCCESS) {
11111     unwatchedQualifierIndex = net.user1.utils.ArrayUtil.indexOf(watchedQualifiers, unwatchedQualifier);
11112     if (unwatchedQualifierIndex != -1) {
11113       watchedQualifiers.splice(unwatchedQualifierIndex, 1);
11114     }
11115   }
11116 };
11117 
11118 //==============================================================================
11119 // SENDING MESSAGES
11120 //==============================================================================
11121 
11122 net.user1.orbiter.RoomManager.prototype.sendMessage = function (messageName, 
11123                                                                 rooms,
11124                                                                 includeSelf,
11125                                                                 filters) {
11126   var rest = Array.prototype.slice.call(arguments).slice(4);
11127   
11128   // An array of arguments to send to the server.
11129   var args;
11130 
11131   // Can't continue without a valid messageName.
11132   if (messageName == null || messageName == "") {
11133     this.log.warn("[ROOM_MANAGER]  sendMessage() failed. No messageName supplied.");
11134     return;
11135   }
11136   
11137   // Send the UPC.
11138   var msgMan = this.orbiter.getMessageManager();
11139   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS, 
11140           messageName, 
11141           rooms.join(net.user1.orbiter.Tokens.RS),
11142           String(includeSelf),
11143           filters != null ? filters.toXMLString() : ""];
11144   msgMan.sendUPC.apply(msgMan, args.concat(rest));
11145 }
11146 
11147 
11148 //==============================================================================
11149 // JOINING ROOMS
11150 //==============================================================================
11151 
11152 net.user1.orbiter.RoomManager.prototype.joinRoom = function (roomID, 
11153                                                              password,
11154                                                              updateLevels) {
11155   if (!this.orbiter.isReady()) { 
11156     this.log.warn("[ROOM_MANAGER] Connection not open. Request to join room ["
11157               + roomID + "] could not be sent.");
11158     return null;
11159   }
11160   
11161   // If the room ID is not valid, quit
11162   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
11163     this.log.error("[ROOM_MANAGER] Invalid room id supplied to joinRoom(): ["
11164                    + roomID + "]. Join request not sent."
11165                    + " See net.user1.orbiter.Validator.isValidResolvedRoomID().");
11166     return null;
11167   }
11168   
11169   // Try to get a reference to the room
11170   var theRoom = this.getRoom(roomID);
11171     
11172   // If the room exists
11173   if (theRoom != null) {
11174     // Can't join a room you're already in.
11175     if (theRoom.clientIsInRoom()) {
11176       this.log.warn("[ROOM_MANAGER] Room join attempt aborted. Already in room: [" 
11177                     + theRoom.getRoomID() + "].");
11178       return theRoom;
11179     }
11180   } else {
11181     // Make the local room.
11182     theRoom = this.addCachedRoom(roomID);
11183   }
11184     
11185   // Validate the password
11186   if (password == null) {
11187     password = "";
11188   }
11189   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
11190     this.log.error("[ROOM_MANAGER] Invalid room password supplied to joinRoom(): ["
11191                    + roomID + "]. Join request not sent." 
11192                    + " See net.user1.orbiter.Validator.isValidPassword().");
11193     return theRoom;
11194   }
11195 
11196   
11197   // If any update levels are specified, send them before joining.
11198   if (updateLevels != null) {
11199     theRoom.setUpdateLevels(updateLevels);
11200   }
11201   
11202   var msgMan = this.orbiter.getMessageManager();
11203   msgMan.sendUPC(net.user1.orbiter.UPC.JOIN_ROOM, 
11204                  roomID, 
11205                  password);
11206   return theRoom;
11207 };
11208 
11209 // =============================================================================
11210 // ROOM OBJECT CREATION/DISPOSAL
11211 // =============================================================================
11212 
11213 /**
11214  * @private
11215  */
11216 net.user1.orbiter.RoomManager.prototype.requestRoom = function (roomID) {
11217   if (roomID == "") {
11218     this.log.warn("[ROOM_MANAGER] requestRoom() failed. Supplied room ID was empty.");
11219     return null;
11220   }
11221   
11222   var theRoom = this.getRoom(roomID);
11223   if (theRoom != null) {
11224     return theRoom;
11225   } else {
11226     this.log.debug("[ROOM_MANAGER] Creating new room object for id: [" + roomID + "]");
11227     var RoomClass = this.roomClassRegistry.getRoomClass(roomID);
11228     theRoom = new RoomClass(roomID, 
11229                             this, 
11230                             this.orbiter.getMessageManager(), 
11231                             this.orbiter.getClientManager(), 
11232                             this.orbiter.getAccountManager(), 
11233                             this.log);
11234     return theRoom;
11235   }
11236 };
11237 
11238 /**
11239  * @private
11240  */
11241 net.user1.orbiter.RoomManager.prototype.disposeRoom = function (roomID) {
11242   var room = this.getRoom(roomID);
11243   if (room != null) {
11244     this.log.debug("[ROOM_MANAGER] Disposing room: " + room);
11245     this.removeCachedRoom(roomID);
11246     this.removeWatchedRoom(roomID);
11247     this.removeOccupiedRoom(roomID);
11248     this.removeObservedRoom(roomID);
11249   } else {
11250     this.log.debug("[ROOM_MANAGER] disposeRoom() called for unknown room: [" + roomID + "]");
11251   }
11252 };
11253 
11254 /**
11255  * @private
11256  */    
11257 net.user1.orbiter.RoomManager.prototype.removeAllRooms = function () {
11258   this.log.debug("[ROOM_MANAGER] Removing all local room object references.");
11259   this.cachedRooms.removeAll();
11260   this.watchedRooms.removeAll();
11261   this.occupiedRooms.removeAll();
11262   this.observedRooms.removeAll();
11263 };
11264 
11265 // =============================================================================
11266 // CACHED ROOMS
11267 // =============================================================================
11268 
11269 /**
11270  * @private
11271  */    
11272 net.user1.orbiter.RoomManager.prototype.addCachedRoom = function (roomID) {
11273   var cachedRoom = this.cachedRooms.getByRoomID(roomID);
11274   if (cachedRoom == null) {
11275     this.log.debug("[ROOM_MANAGER] Adding cached room: [" + roomID + "]"); 
11276     return this.cachedRooms.add(this.requestRoom(roomID));
11277   } else {
11278     return cachedRoom;
11279   }
11280 };
11281 
11282 /**
11283  * @private
11284  */    
11285 net.user1.orbiter.RoomManager.prototype.removeCachedRoom = function (roomID) {
11286   if (this.cachedRooms.containsRoomID(roomID)) {
11287     this.cachedRooms.removeByRoomID(roomID);
11288   } else {
11289     throw new Error("[ROOM_MANAGER] Could not remove cached room: [" + roomID + "]." 
11290                     + " Room not found.");
11291   }
11292 };
11293 
11294 net.user1.orbiter.RoomManager.prototype.hasCachedRoom = function (roomID) {
11295   return this.cachedRooms.containsRoomID(roomID);
11296 };
11297 
11298 net.user1.orbiter.RoomManager.prototype.disposeCachedRooms = function () {
11299   var room;
11300   var rooms = cachedRooms.getAll();
11301   for (var i = 0; i <= rooms.length; i++) {
11302     room = rooms[i];
11303     removeCachedRoom(room.getRoomID());
11304   }
11305 };
11306 
11307 // =============================================================================
11308 // WATCHED ROOMS
11309 // =============================================================================
11310   
11311 /**
11312  * @private
11313  */    
11314 net.user1.orbiter.RoomManager.prototype.addWatchedRoom = function (roomID) {
11315   this.log.debug("[ROOM_MANAGER] Adding watched room: [" + roomID + "]"); 
11316   var room = this.watchedRooms.add(this.requestRoom(roomID));
11317   room.updateSyncState();
11318 };
11319   
11320 /**
11321  * @private
11322  */    
11323 net.user1.orbiter.RoomManager.prototype.removeWatchedRoom = function (roomID) {
11324   var room = this.watchedRooms.removeByRoomID(roomID);
11325   if (room != null) {
11326     room.updateSyncState();
11327   } else {
11328     this.log.debug("[ROOM_MANAGER] Request to remove watched room [" 
11329               + roomID + "] ignored; room not in watched list.");
11330   }
11331 };
11332   
11333 /**
11334  * @private
11335  */    
11336 net.user1.orbiter.RoomManager.prototype.removeAllWatchedRooms = function () {
11337   var rooms = this.watchedRooms.getAll();
11338   var room;
11339   for (var i = 0; i <= rooms.length; i++) {
11340     room = rooms[i];
11341     removeWatchedRoom(room.getRoomID());
11342     room.updateSyncState();
11343   }
11344 };
11345   
11346 /**
11347  * @private
11348  */
11349 net.user1.orbiter.RoomManager.prototype.setWatchedRooms = function (qualifier, newRoomIDs) {
11350   // Remove rooms from local list
11351   var rooms = this.getRoomsWithQualifier(qualifier);
11352   var room;
11353   for (var i = 0; i < rooms.length; i++) {
11354     room = rooms[i];
11355     if (net.user1.utils.ArrayUtil.indexOf(newRoomIDs, room.getSimpleRoomID()) == -1) {
11356       this.removeWatchedRoom(room.getRoomID());
11357     }
11358   }
11359   // Add rooms to local list
11360   var fullRoomID;
11361   var roomID;
11362   for (var i = 0; i < newRoomIDs.length; i++) {
11363     roomID = newRoomIDs[i];
11364     fullRoomID = qualifier + (qualifier != "" ? "." : "") + roomID;
11365     if (!this.watchedRooms.containsRoomID(fullRoomID)) {
11366       this.addWatchedRoom(fullRoomID);
11367     }
11368   }
11369 };
11370 
11371 net.user1.orbiter.RoomManager.prototype.hasWatchedRoom = function (roomID) {
11372   return this.watchedRooms.containsRoomID(roomID);
11373 }
11374 
11375 // =============================================================================
11376 // OCCUPIED ROOMS
11377 // =============================================================================
11378   
11379 /**
11380  * @private
11381  */    
11382 net.user1.orbiter.RoomManager.prototype.addOccupiedRoom = function (roomID) {
11383   this.log.debug("[ROOM_MANAGER] Adding occupied room: [" + roomID + "]"); 
11384   var room = this.occupiedRooms.add(this.requestRoom(roomID));
11385   room.updateSyncState();
11386   return room;
11387 };
11388   
11389 /**
11390  * @private
11391  */    
11392 net.user1.orbiter.RoomManager.prototype.removeOccupiedRoom = function (roomID) {
11393   var room = this.occupiedRooms.removeByRoomID(roomID);
11394   if (room != null) {
11395     room.updateSyncState();
11396   } else {
11397     this.log.debug("[ROOM_MANAGER] Request to remove occupied room [" 
11398               + roomID + "] ignored; client is not in room."); 
11399   }
11400 };
11401 
11402 net.user1.orbiter.RoomManager.prototype.hasOccupiedRoom = function (roomID) {
11403   return this.occupiedRooms.containsRoomID(roomID);
11404 };
11405   
11406 // =============================================================================
11407 // OBSERVED ROOMS
11408 // =============================================================================
11409   
11410 /**
11411  * @private
11412  */    
11413 net.user1.orbiter.RoomManager.prototype.addObservedRoom = function (roomID) {
11414   this.log.debug("[ROOM_MANAGER] Adding observed room: [" + roomID + "]");
11415   var room = this.observedRooms.add(this.requestRoom(roomID));
11416   room.updateSyncState();
11417   return room;
11418 };
11419   
11420 /**
11421  * @private
11422  */    
11423 net.user1.orbiter.RoomManager.prototype.removeObservedRoom = function (roomID) {
11424   var room = this.observedRooms.removeByRoomID(roomID);
11425   if (room != null) {
11426     room.updateSyncState();
11427   } else {
11428     this.log.debug("[ROOM_MANAGER] Request to remove observed room [" 
11429               + roomID + "] ignored; client is not observing room."); 
11430   }
11431 };
11432 
11433 net.user1.orbiter.RoomManager.prototype.hasObservedRoom = function (roomID) {
11434   return this.observedRooms.containsRoomID(roomID);
11435 };
11436 
11437 //==============================================================================
11438 // ROOM LIST LISTENERS
11439 //==============================================================================
11440 
11441 /**
11442  * @private
11443  */    
11444 net.user1.orbiter.RoomManager.prototype.addRoomListener = function (e) {
11445   var room = e.getItem();
11446   
11447   // Only trigger added for first known reference
11448   if (this.getKnownReferenceCount(room.getRoomID()) == 1) {
11449     this.fireRoomAdded(room.getQualifier(), room.getRoomID(), room);
11450     this.fireRoomCount(this.getNumRooms());
11451   }
11452 };
11453 
11454 /**
11455  * @private
11456  */    
11457 net.user1.orbiter.RoomManager.prototype.removeRoomListener = function (e) {
11458   var room = e.getItem();
11459   var knownReferenceCount = this.getKnownReferenceCount(room.getRoomID());
11460   
11461   switch (e.target) {
11462     case this.occupiedRooms:
11463       this.log.debug("[ROOM_MANAGER] Removed occupied room: " + room);
11464       if (knownReferenceCount == 0) {
11465         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11466         this.fireRoomCount(this.getNumRooms());
11467       }
11468       break;
11469     
11470     case this.observedRooms:
11471       this.log.debug("[ROOM_MANAGER] Removed observed room: " + room); 
11472       if (knownReferenceCount == 0) {
11473         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11474         this.fireRoomCount(this.getNumRooms());
11475       }
11476       break;
11477     
11478     case this.watchedRooms:
11479       this.log.debug("[ROOM_MANAGER] Removed watched room: " + room); 
11480       if (knownReferenceCount == 0) {
11481         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11482         this.fireRoomCount(this.getNumRooms());
11483       }
11484       break;
11485     
11486     case this.cachedRooms:
11487       this.log.debug("[ROOM_MANAGER] Removed cached room: " + room); 
11488       break;
11489   }
11490   
11491   // When the RoomManager has no more references to the room, shut it down
11492   if (knownReferenceCount == 0 && !this.cachedRooms.contains(room)) {
11493     room.shutdown();
11494   }
11495 };
11496 
11497 //==============================================================================
11498 // ROOM ACCESS
11499 //==============================================================================
11500 
11501 /**
11502  * @private 
11503  */    
11504 net.user1.orbiter.RoomManager.prototype.getKnownReferenceCount = function (roomID) {
11505   var count = 0;
11506   count += this.hasObservedRoom(roomID) ? 1 : 0;
11507   count += this.hasOccupiedRoom(roomID) ? 1 : 0;
11508   count += this.hasWatchedRoom(roomID) ? 1 : 0;
11509   return count;
11510 }
11511 
11512 net.user1.orbiter.RoomManager.prototype.getRooms = function () {
11513   var roomlist = net.user1.utils.ArrayUtil.combine(this.occupiedRooms.getAll(),
11514                                                    this.observedRooms.getAll(),
11515                                                    this.watchedRooms.getAll());
11516   return roomlist;
11517 };
11518 
11519 net.user1.orbiter.RoomManager.prototype.roomIsKnown = function (roomID) {
11520   var rooms = this.getRooms();
11521   var room;
11522   for (var i = rooms.length; --i >= 0;) {
11523     room = rooms[i];
11524     if (room.getRoomID() == roomID) {
11525       return true;
11526     }
11527   }
11528   return false;
11529 };
11530 
11531 net.user1.orbiter.RoomManager.prototype.getRoomIDs = function () {
11532   var roomIDs = new Array();
11533   var rooms = this.getRooms();
11534 
11535   for (var i = 0; i <= rooms.length; i++) {
11536     roomIDs.push(rooms[i].getRoomID());
11537   }
11538 
11539   return roomIDs;
11540 };
11541 
11542 net.user1.orbiter.RoomManager.prototype.getAllRooms = function () {
11543   var roomlist = net.user1.utils.ArrayUtil.combine(this.occupiedRooms.getAll(),
11544                                                    this.observedRooms.getAll(),
11545                                                    this.watchedRooms.getAll(),
11546                                                    this.cachedRooms.getAll());
11547   
11548   return roomlist;
11549 };
11550 
11551 net.user1.orbiter.RoomManager.prototype.getRoomsWithQualifier = function (qualifier) {
11552   if (qualifier == null)  {
11553     return this.getRooms();
11554   }
11555   
11556   var roomlist = [];
11557   var rooms = this.getRooms();
11558   var room;
11559   for (var i = 0; i < rooms.length; i++) {
11560     room = rooms[i];
11561     if (net.user1.orbiter.RoomIDParser.getQualifier(room.getRoomID()) == qualifier) {
11562       roomlist.push(room);
11563     }
11564   }
11565   
11566   return roomlist;
11567 };
11568 
11569 net.user1.orbiter.RoomManager.prototype.getNumRooms = function (qualifier) {
11570   return this.getRoomsWithQualifier(qualifier).length;
11571 }
11572 
11573 net.user1.orbiter.RoomManager.prototype.getRoom = function (roomID) {
11574   var rooms = this.getAllRooms();
11575   var room;
11576   for (var i = rooms.length; --i >= 0;) {
11577     room = rooms[i];
11578     if (room.getRoomID() == roomID) {
11579       return room;
11580     }
11581   }
11582   return null;
11583 };
11584 
11585 // =============================================================================
11586 // ROOM CLASS REGISTRY
11587 // =============================================================================
11588 
11589 net.user1.orbiter.RoomManager.prototype.getRoomClassRegistry = function () {
11590   return this.roomClassRegistry;
11591 };
11592 
11593 // =============================================================================
11594 // CLIENT ACCESS
11595 // =============================================================================
11596 
11597 /**
11598  * @private
11599  */        
11600 net.user1.orbiter.RoomManager.prototype.getAllClients = function () {
11601   var clientSets = [];
11602   var rooms = this.getRooms();
11603   var room;
11604   for (var i = rooms.length; --i >= 0;) {
11605     room = rooms[i];
11606     clientSets.push(room.getOccupantsInternal());
11607     clientSets.push(room.getObserversInternal());
11608   }
11609   return net.user1.utils.ObjectUtil.combine(clientSets);
11610 };
11611 
11612 net.user1.orbiter.RoomManager.prototype.clientIsKnown = function (clientID) {
11613   var clientSets = [];
11614   
11615   var rooms = this.getRooms();
11616   var room;
11617   for (var i = rooms.length; --i >= 0;) {
11618     room = rooms[i];
11619     clientSets.push(room.getOccupantsInternal());
11620     clientSets.push(room.getObserversInternal());
11621   }
11622   
11623   for (var i = clientSets.length; --i >= 0;) {
11624     if (clientSets[i][clientID] != null) {
11625       return true;
11626     }
11627   }
11628   return false;
11629 };
11630 
11631 // =============================================================================
11632 // EVENT DISPATCHING
11633 // =============================================================================
11634 
11635 /**
11636  * @private
11637  */
11638 net.user1.orbiter.RoomManager.prototype.fireWatchForRoomsResult = function (roomIDQualifier,
11639                                                                             status) {
11640   var e = new net.user1.orbiter.RoomManagerEvent(
11641                               net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
11642                               null, status, roomIDQualifier);
11643   this.dispatchEvent(e);  
11644 };
11645 
11646 /**
11647  * @private
11648  */
11649 net.user1.orbiter.RoomManager.prototype.fireStopWatchingForRoomsResult = function (roomIDQualifier,
11650                                                                                    status) {
11651   var e = new net.user1.orbiter.RoomManagerEvent(
11652                             net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
11653                             null, status, roomIDQualifier);
11654   this.dispatchEvent(e);  
11655 };
11656 
11657 /**
11658  * @private
11659  */
11660 net.user1.orbiter.RoomManager.prototype.fireCreateRoomResult = function (roomIDQualifier, 
11661                                                                          roomID,
11662                                                                          status) {
11663   var e = new net.user1.orbiter.RoomManagerEvent(
11664                                        net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT,
11665                                        roomID, status, roomIDQualifier);
11666   this.dispatchEvent(e);
11667 };
11668 
11669 /**
11670  * @private
11671  */
11672 net.user1.orbiter.RoomManager.prototype.fireRemoveRoomResult = function (roomIDQualifier, 
11673                                                                          roomID,
11674                                                                          status) {
11675   var e = new net.user1.orbiter.RoomManagerEvent(
11676                                        net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT, 
11677                                        roomID, status, roomIDQualifier);
11678 
11679   this.dispatchEvent(e);
11680 }
11681 
11682 /**
11683  * @private
11684  */
11685 net.user1.orbiter.RoomManager.prototype.fireRoomAdded = function (roomIDQualifier, 
11686                                                                   roomID,
11687                                                                   theRoom) {
11688   var e = new net.user1.orbiter.RoomManagerEvent(
11689                                      net.user1.orbiter.RoomManagerEvent.ROOM_ADDED, 
11690                                      roomID, null, roomIDQualifier, theRoom);
11691   this.dispatchEvent(e);
11692 }
11693 
11694 /**
11695  * @private
11696  */
11697 net.user1.orbiter.RoomManager.prototype.fireRoomRemoved = function (roomIDQualifier, 
11698                                                                     roomID,
11699                                                                     theRoom) {
11700   var e = new net.user1.orbiter.RoomManagerEvent(
11701                                      net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED, 
11702                                      roomID, 
11703                                      null,
11704                                      roomIDQualifier,
11705                                      theRoom);
11706   this.dispatchEvent(e);
11707 }
11708 
11709 /**
11710  * @private
11711  */
11712 net.user1.orbiter.RoomManager.prototype.fireRoomCount = function (numRooms) {
11713   this.dispatchEvent(new net.user1.orbiter.RoomManagerEvent(net.user1.orbiter.RoomManagerEvent.ROOM_COUNT, 
11714                                                             null, null, null, null, numRooms));
11715 };
11716 
11717 /**
11718  * @private
11719  */
11720 net.user1.orbiter.RoomManager.prototype.fireJoinRoomResult = function (roomID, status) {
11721   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN_RESULT, null, null, 
11722                                                      status, null, 0, roomID));
11723 }
11724 
11725 /**
11726  * @private
11727  */
11728 net.user1.orbiter.RoomManager.prototype.fireLeaveRoomResult = function (roomID, status) {
11729   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE_RESULT, null, null, 
11730                                                      status, null, 0, roomID));
11731 };
11732 
11733 /**
11734  * @private
11735  */
11736 net.user1.orbiter.RoomManager.prototype.fireObserveRoomResult = function (roomID, status) {
11737   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE_RESULT, null, null, 
11738                                                      status, null, 0, roomID));
11739 };
11740 
11741 /**
11742  * @private
11743  */
11744 net.user1.orbiter.RoomManager.prototype.fireStopObservingRoomResult = function (roomID, status) {
11745   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT, null, null, 
11746                                                      status, null, 0, roomID));
11747 };
11748 
11749 
11750 // =============================================================================
11751 // CLEANUP and DISPOSAL
11752 // =============================================================================
11753 
11754 /**
11755  * @private
11756  * 
11757  * Clears all resources. The object remains alive, and can be reused. To 
11758  * permanently deactivate this object, use dispose().
11759  */
11760 net.user1.orbiter.RoomManager.prototype.cleanup = function () {
11761   this.log.info("[ROOM_MANAGER] Cleaning resources.");
11762   this.removeAllRooms();
11763   this.watchedQualifiers = [];
11764 };
11765 
11766 /**
11767  * @private
11768  */
11769 net.user1.orbiter.RoomManager.prototype.dispose = function () {
11770   this.log.info("[ROOM_MANAGER] Disposing resources.");
11771   this.watchedQualifiers = null;
11772   var room;
11773   var rooms = this.getAllRooms();
11774   for (var i = this.getAllRooms().length; --i >= 0;) {
11775     room = rooms[i];
11776     room.dispose();
11777   }
11778   this.cachedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11779   this.occupiedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11780   this.occupiedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11781   this.observedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11782   this.observedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11783   this.watchedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11784   this.watchedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11785   this.occupiedRooms = null;
11786   this.observedRooms = null;
11787   this.watchedRooms  = null;
11788   this.cachedRooms  = null;
11789   this.log = null;
11790   this.orbiter = null;
11791   this.roomClassRegistry = null;
11792 };
11793 //==============================================================================
11794 // CLASS DECLARATION
11795 //==============================================================================
11796 /** @class
11797     @extends net.user1.events.Event
11798 */
11799 net.user1.orbiter.RoomManagerEvent = function (type,
11800                                                roomID, 
11801                                                status,
11802                                                roomIdQualifier,
11803                                                room,
11804                                                numRooms) {
11805   net.user1.events.Event.call(this, type);
11806 
11807   this.roomID = roomID;
11808   this.status = status;
11809   this.roomIdQualifier  = roomIdQualifier;
11810   this.room = room;
11811   this.numRooms = numRooms;
11812 };
11813 
11814 //==============================================================================
11815 // INHERITANCE
11816 //==============================================================================
11817 net.user1.utils.extend(net.user1.orbiter.RoomManagerEvent, net.user1.events.Event);
11818 
11819 //==============================================================================
11820 // STATIC VARIABLES
11821 //==============================================================================
11822 
11823 /** @constant */
11824 net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT = "CREATE_ROOM_RESULT";
11825 /** @constant */
11826 net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT = "REMOVE_ROOM_RESULT";
11827 /** @constant */
11828 net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT = "WATCH_FOR_ROOMS_RESULT";
11829 /** @constant */
11830 net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT = "STOP_WATCHING_FOR_ROOMS_RESULT";
11831 /** @constant */
11832 net.user1.orbiter.RoomManagerEvent.ROOM_ADDED = "ROOM_ADDED";
11833 /** @constant */
11834 net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED = "ROOM_REMOVED";
11835 /** @constant */
11836 net.user1.orbiter.RoomManagerEvent.ROOM_COUNT = "ROOM_COUNT";
11837 
11838 net.user1.orbiter.RoomManagerEvent.prototype.getRoomIdQualifier = function () {
11839   if (this.roomIdQualifier == null && this.room != null) {
11840     return this.room.getQualifier();
11841   } else {
11842     return this.roomIdQualifier;
11843   }
11844 };
11845 
11846 net.user1.orbiter.RoomManagerEvent.prototype.getRoomID = function () {
11847   var fullRoomID;
11848   var qualifier;
11849   
11850   if (this.room != null) {
11851     return this.room.getRoomID();
11852   } else if (this.roomID == null) {
11853     return null;
11854   } else {
11855     qualifier = this.getRoomIdQualifier();
11856     fullRoomID = qualifier == "" || qualifier == null 
11857                  ? this.roomID
11858                  : qualifier + "." + this.roomID;
11859     return fullRoomID;
11860   }
11861 };
11862 
11863 net.user1.orbiter.RoomManagerEvent.prototype.getSimpleRoomID = function () {
11864   if (this.roomID == null && this.room != null) {
11865     return this.room.getSimpleRoomID();
11866   } else {
11867     return this.roomID;
11868   }
11869 };
11870 
11871 net.user1.orbiter.RoomManagerEvent.prototype.getRoom = function () {
11872   return this.room;
11873 }
11874 
11875 net.user1.orbiter.RoomManagerEvent.prototype.getStatus = function () {
11876   return this.status;
11877 }
11878 
11879 net.user1.orbiter.RoomManagerEvent.prototype.getNumRooms = function () {
11880   return this.numRooms;
11881 }
11882 
11883 net.user1.orbiter.RoomManagerEvent.prototype.toString = function () {
11884   return "[object RoomManagerEvent]";
11885 };
11886 //==============================================================================
11887 // CLASS DECLARATION
11888 //==============================================================================
11889   /**
11890    * @private
11891    */  
11892 net.user1.orbiter.RoomManifest = function () {
11893 };
11894   
11895 net.user1.orbiter.RoomManifest.prototype.deserialize = function (roomID,
11896                                                                  serializedAttributes,
11897                                                                  clientList,
11898                                                                  occupantCount,
11899                                                                  observerCount) {
11900   this.roomID = roomID;
11901   this.attributes = null;
11902   this.occupantCount = occupantCount;
11903   this.observerCount = observerCount;
11904   this.occupants = [];
11905   this.observers = [];
11906   
11907   this.deserializeAttributes(serializedAttributes);
11908   this.deserializeClientList(clientList);
11909 };
11910         
11911 /**
11912  * @private
11913  */        
11914 net.user1.orbiter.RoomManifest.prototype.deserializeAttributes = function (serializedAttributes) {
11915   var attrList = serializedAttributes.split(net.user1.orbiter.Tokens.RS);
11916   this.attributes = new net.user1.orbiter.AttributeCollection();
11917   
11918   for (var i = attrList.length-2; i >= 0; i -=2) {
11919     this.attributes.setAttribute(attrList[i], attrList[i+1], net.user1.orbiter.Tokens.GLOBAL_ATTR);
11920   }
11921 };
11922     
11923 /**
11924  * @private
11925  */        
11926 net.user1.orbiter.RoomManifest.prototype.deserializeClientList = function (clientList) {
11927   var clientManifest;
11928   
11929   for (var i = clientList.length-5; i >= 0; i -=5) {
11930     clientManifest = new net.user1.orbiter.ClientManifest();
11931     clientManifest.deserialize(clientList[i], clientList[i+1], null, null, clientList[i+3], [this.roomID, clientList[i+4]]);
11932     if (clientList[i+2] == "0") {
11933       this.occupants.push(clientManifest);
11934     } else {
11935       this.observers.push(clientManifest);
11936     }
11937   }
11938 };
11939 //==============================================================================
11940 // CLASS DECLARATION
11941 //==============================================================================
11942 /** @class
11943  *
11944  * @example
11945  * var modules = new net.user1.orbiter.RoomModules();
11946  * modules.addModule("com.business.StockTickerListener", net.user1.orbiter.ModuleType.CLASS);
11947  * orbiter.getRoomManager().createRoom("someRoomID",
11948  *                           null,
11949  *                           null,
11950  *                           modules);
11951  */
11952 net.user1.orbiter.RoomModules = function () {
11953   this.modules = new Array();
11954 };
11955 
11956 net.user1.orbiter.RoomModules.prototype.addModule = function (identifier, 
11957                                                   type) {
11958   this.modules.push([type, identifier]);
11959 };
11960 
11961 net.user1.orbiter.RoomModules.prototype.serialize = function () {
11962   var modulesString = "";
11963 
11964   var numModules = this.modules.length;
11965   for (var i = 0; i < numModules; i++) {
11966     modulesString += this.modules[i][0] + net.user1.orbiter.Tokens.RS + this.modules[i][1];
11967     if (i < numModules-1) {
11968       modulesString += net.user1.orbiter.Tokens.RS;
11969     } 
11970   }
11971 
11972   return modulesString;
11973 };
11974 
11975 net.user1.orbiter.RoomModules.prototype.getIdentifiers = function () {
11976   var ids = new Array();
11977   
11978   var module;
11979   for (var i = 0; i < this.modules.length; i++) {
11980     module = this.modules[i];
11981     ids.push(module[1]);
11982   }
11983   return ids;
11984 };
11985 //==============================================================================
11986 // CLASS DECLARATION
11987 //==============================================================================
11988 /** @class */
11989 net.user1.orbiter.RoomSettings = function () {
11990   this.removeOnEmpty = null;
11991   this.maxClients = null; 
11992   this.password = null;
11993 };
11994 
11995 net.user1.orbiter.RoomSettings.prototype.serialize = function () {
11996   var RS = net.user1.orbiter.Tokens.RS;
11997   var settingsString =
11998       net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR + RS + 
11999       (this.removeOnEmpty == null ? "true" : this.removeOnEmpty.toString()) 
12000       + RS + net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR + RS + 
12001       (this.maxClients == null ? "-1" : this.maxClients.toString()) 
12002       + RS + net.user1.orbiter.Tokens.PASSWORD_ATTR + RS +
12003       (this.password == null ? "" : this.password);
12004   return settingsString;
12005 }
12006 //==============================================================================
12007 // CLASS DECLARATION
12008 //==============================================================================
12009 /** 
12010  * @class
12011  * @extends net.user1.orbiter.snapshot.Snapshot
12012  */
12013 net.user1.orbiter.snapshot.RoomSnapshot = function (roomID, password, updateLevels) {
12014   // Call superconstructor
12015   net.user1.orbiter.snapshot.Snapshot.call(this);
12016   this.manifest = null;
12017   this.method = net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT;
12018   this.args   = [roomID, password, updateLevels != null ? updateLevels.toInt() : ""];
12019   this.hasStatus = true;
12020 };
12021 //==============================================================================
12022 // INHERITANCE
12023 //==============================================================================
12024 net.user1.utils.extend(net.user1.orbiter.snapshot.RoomSnapshot, net.user1.orbiter.snapshot.Snapshot);
12025 
12026 //==============================================================================
12027 // INSTANCE METHODS
12028 //==============================================================================                
12029 /**
12030  * @private
12031  */    
12032 net.user1.orbiter.snapshot.RoomSnapshot.prototype.setManifest = function (value) {
12033   this.manifest = value;
12034 };
12035     
12036 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getAttribute = function (name) {
12037   if (!this.manifest) {
12038     return null;
12039   }
12040   return this.manifest.attributes.getAttribute(name, net.user1.orbiter.Tokens.GLOBAL_ATTR);
12041 };
12042 
12043 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getAttributes = function () {
12044   if (!this.manifest) {
12045     return null;
12046   }
12047   return this.manifest.attributes.getByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR);
12048 };
12049 
12050 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getRoomID = function () {
12051   if (!this.manifest) {
12052     return null;
12053   }
12054   return this.manifest.roomID;
12055 };
12056 
12057 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getOccupants = function () {
12058   return this.manifest.occupants.slice();
12059 }
12060 
12061 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getObservers = function () {
12062   return this.manifest.observers.slice();
12063 }
12064 
12065 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getOccupant = function (clientID) {
12066   var client;
12067   for (var i = this.manifest.occupants.length; --i >= 0;) {
12068     if (this.manifest.occupants[i].clientID == clientID) {
12069       return this.manifest.occupants[i];
12070     } 
12071   }
12072   return null;
12073 };
12074 
12075 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getObserver = function (clientID) {
12076   var client;
12077   for (var i = this.manifest.observers.length; --i >= 0;) {
12078     if (this.manifest.observers[i].clientID == clientID) {
12079       return this.manifest.observers[i];
12080     } 
12081   }
12082   return null;
12083 };
12084 
12085 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getNumOccupants = function () {
12086   return Math.max(this.manifest.occupants.length, this.manifest.occupantCount);
12087 }
12088 
12089 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getNumObservers = function () {
12090   return Math.max(this.manifest.observers.length, this.manifest.observerCount);
12091 }
12092 //==============================================================================
12093 //  SECURITY_ROLE CONSTANTS
12094 //==============================================================================
12095 /** @class */
12096 net.user1.orbiter.SecurityRole = new Object();
12097 /** @constant */
12098 net.user1.orbiter.SecurityRole.MODERATOR = "MODERATOR";
12099 //==============================================================================
12100 // CLASS DECLARATION
12101 //==============================================================================
12102 /** @class
12103 
12104 The Server class dispatches the following events:
12105 
12106 <ul class="summary">
12107 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.TIME_SYNC}</li> 
12108 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.UPC_PROCESSED}</li> 
12109 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT}</li> 
12110 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT}</li> 
12111 </ul>
12112 
12113 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
12114 
12115 @extends net.user1.events.EventDispatcher
12116 */
12117 net.user1.orbiter.Server = function (orbiter) {
12118   // Call superconstructor
12119   net.user1.events.EventDispatcher.call(this);
12120 
12121   this.orbiter = orbiter;
12122   this.version = null;
12123   this.upcVersion = null;
12124   this.localAgeAtLastSync = NaN;
12125   this.lastKnownServerTime = NaN;
12126   this._isWatchingForProcessedUPCs = false;
12127   
12128   this.log = orbiter.getLog();
12129   
12130   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.readyListener, this);
12131 }
12132 
12133 //==============================================================================    
12134 // INHERITANCE
12135 //============================================================================== 
12136 net.user1.utils.extend(net.user1.orbiter.Server, net.user1.events.EventDispatcher);
12137 
12138 // =============================================================================
12139 // SERVER-WIDE MESSAGING
12140 // =============================================================================
12141 net.user1.orbiter.Server.prototype.sendMessage = function (messageName, 
12142                                                            includeSelf,
12143                                                            filters) {
12144   var rest = Array.prototype.slice.call(arguments).slice(3);
12145   var args;
12146 
12147   if (messageName == null || messageName == "") {
12148     this.log.warn("Server.sendMessage() failed. No messageName supplied.");
12149     return;
12150   }
12151 
12152   var msgMan = this.orbiter.getMessageManager();
12153   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER, 
12154           messageName, 
12155           includeSelf.toString(),
12156           filters != null ? filters.toXMLString() : ""];
12157   msgMan.sendUPC.apply(msgMan, args.concat(rest));
12158 };
12159 
12160 // =============================================================================
12161 // SERVER MODULES
12162 // =============================================================================
12163 net.user1.orbiter.Server.prototype.sendModuleMessage = function (moduleID,
12164                                                                  messageName, 
12165                                                                  messageArguments) {
12166   var sendupcArgs = [net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE, moduleID, messageName];
12167   
12168   for (var arg in messageArguments) {
12169     sendupcArgs.push(arg + net.user1.orbiter.Tokens.RS + messageArguments[arg]);
12170   }
12171         
12172   this.orbiter.getMessageManager().sendUPC.apply(this.orbiter.getMessageManager(), sendupcArgs);
12173 };
12174 
12175 net.user1.orbiter.Server.prototype.clearModuleCache = function () {
12176   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.CLEAR_MODULE_CACHE);
12177 };
12178 
12179 // =============================================================================
12180 // VERSION ACCESS
12181 // =============================================================================
12182 
12183 /**
12184  * @private
12185  */
12186 net.user1.orbiter.Server.prototype.setVersion = function (value) {
12187   this.version = value;
12188 };
12189 
12190 net.user1.orbiter.Server.prototype.getVersion = function () {
12191   return this.version;
12192 };
12193 
12194 /**
12195  * @private
12196  */
12197 net.user1.orbiter.Server.prototype.setUPCVersion = function (value) {
12198   this.upcVersion = value;
12199 };
12200 
12201 net.user1.orbiter.Server.prototype.getUPCVersion = function () {
12202   return this.upcVersion;
12203 };
12204 
12205 // =============================================================================
12206 // UPC STATS AND PROCESSING
12207 // =============================================================================
12208 
12209 net.user1.orbiter.Server.prototype.resetUPCStats = function () {
12210   this.orbiter.getMessageManager().sendUPC(UPC.RESET_UPC_STATS);
12211 };
12212 
12213 net.user1.orbiter.Server.prototype.watchForProcessedUPCs = function () {
12214   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS);
12215 };
12216 
12217 net.user1.orbiter.Server.prototype.stopWatchingForProcessedUPCs = function () {
12218   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS);
12219 };
12220 
12221 net.user1.orbiter.Server.prototype.isWatchingForProcessedUPCs = function () {
12222   return this._isWatchingForProcessedUPCs;
12223 };
12224 
12225 net.user1.orbiter.Server.prototype.setIsWatchingForProcessedUPCs = function (value) {
12226   this._isWatchingForProcessedUPCs = value;
12227 };
12228 
12229 // =============================================================================
12230 // TIME RETRIEVAL METHODS
12231 // =============================================================================
12232 net.user1.orbiter.Server.prototype.getServerTime = function () {
12233   var self = this.orbiter.self();
12234   var lastServerTime = NaN;
12235   var estimatedServerTime = NaN;
12236   
12237   if (self != null) {
12238     lastServerTime = isNaN(this.lastKnownServerTime) 
12239                      ? self.getConnectTime() 
12240                      : this.lastKnownServerTime;
12241                      
12242     estimatedServerTime = isNaN(lastServerTime) 
12243                          ? NaN
12244                          : (lastServerTime + (new Date().getTime()-this.localAgeAtLastSync));
12245   }
12246  
12247   if (estimatedServerTime == 0) {
12248     log.warn("Server time requested, but is unknown.");
12249   }
12250   
12251   return estimatedServerTime;
12252 };
12253 
12254 net.user1.orbiter.Server.prototype.syncTime = function () {
12255   var msgMan = this.orbiter.getMessageManager();
12256   msgMan.sendUPC(net.user1.orbiter.UPC.SYNC_TIME);
12257 };
12258 
12259 /**
12260  * @private 
12261  */ 
12262 net.user1.orbiter.Server.prototype.readyListener = function (e) {
12263   this.orbiter.getMessageManager().addMessageListener(net.user1.orbiter.UPC.SERVER_TIME_UPDATE, this.u50);  
12264   this.localAgeAtLastSync = new Date().getTime();;
12265 };
12266 
12267 // =============================================================================
12268 // EVENT DISPATCHING
12269 // =============================================================================
12270 
12271 /**
12272  * @private
12273  */
12274 net.user1.orbiter.Server.prototype.fireTimeSync = function () {
12275   this.dispatchEvent(new net.user1.orbiter.ServerEvent(ServerEvent.TIME_SYNC));
12276 };
12277 
12278 /**
12279  * @private
12280  */
12281 net.user1.orbiter.Server.prototype.dispatchWatchForProcessedUPCsResult = function (status) {
12282   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT,
12283                      null, status));
12284 };
12285 
12286 /**
12287  * @private
12288  */
12289 net.user1.orbiter.Server.prototype.dispatchStopWatchingForProcessedUPCsResult = function (status) {
12290   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT,
12291                      null, status));
12292 };
12293 
12294 /**
12295  * @private
12296  */
12297 net.user1.orbiter.Server.prototype.dispatchUPCProcessed = function (record) {
12298   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.UPC_PROCESSED, record));
12299 };
12300 
12301 /**
12302  * @private
12303  */
12304 net.user1.orbiter.Server.prototype.dispatchResetUPCStatsResult = function (status) {
12305   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.RESET_UPC_STATS_RESULT,
12306                      null, status));
12307 };
12308 
12309 //==============================================================================
12310 // UPC LISTENERS
12311 //==============================================================================
12312 
12313 /**
12314  * @private
12315  */
12316 net.user1.orbiter.Server.prototype.u50 = function (newTime) {             // SERVER_TIME
12317   this.lastKnownServerTime = Number(newTime);
12318   this.localAgeAtLastSync  = new Date().getTime();
12319   this.fireTimeSync();
12320 }
12321 
12322 //==============================================================================
12323 // CLEANUP AND DISPOSAL
12324 //==============================================================================  
12325 /**
12326  * @private
12327  */    
12328 net.user1.orbiter.Server.prototype.cleanup = function () {
12329   this.log.info("[SERVER] Cleaning resources.");
12330   this.setIsWatchingForProcessedUPCs(false);
12331 }
12332 //==============================================================================
12333 // CLASS DECLARATION
12334 //==============================================================================
12335 /** 
12336  * @class
12337  * @extends net.user1.orbiter.snapshot.Snapshot
12338  */
12339 net.user1.orbiter.snapshot.GatewaysSnapshot = function () {
12340   // Call superconstructor
12341   net.user1.orbiter.snapshot.Snapshot.call(this);
12342   this.gateways = null;
12343   this.method = net.user1.orbiter.UPC.GET_GATEWAYS_SNAPSHOT;
12344 };
12345 
12346 //==============================================================================
12347 // INHERITANCE
12348 //==============================================================================
12349 net.user1.utils.extend(net.user1.orbiter.snapshot.GatewaysSnapshot, net.user1.orbiter.snapshot.Snapshot);
12350 
12351 //==============================================================================
12352 // INSTANCE METHODS
12353 //==============================================================================         
12354 /**
12355  * @private
12356  */    
12357 net.user1.orbiter.snapshot.GatewaysSnapshot.prototype.setGateways = function (value) {
12358   this.gateways = value;
12359 };
12360 
12361 net.user1.orbiter.snapshot.GatewaysSnapshot.prototype.getGateways = function () {
12362   if (!this.gateways) {
12363     return [];
12364   }
12365   return this.gateways.slice();
12366 };
12367 //==============================================================================
12368 // CLASS DECLARATION
12369 //==============================================================================
12370 /** @class
12371     @extends net.user1.events.Event
12372 */
12373 net.user1.orbiter.ServerEvent = function (type, 
12374                                           upcProcessingRecord,
12375                                           status) {
12376   net.user1.events.Event.call(this, type);
12377   
12378   this.upcProcessingRecord = upcProcessingRecord;
12379   this.status = status;
12380 };
12381 
12382 //==============================================================================
12383 // INHERITANCE
12384 //==============================================================================
12385 net.user1.utils.extend(net.user1.orbiter.ServerEvent, net.user1.events.Event);
12386 
12387 //==============================================================================
12388 // STATIC VARIABLES
12389 //==============================================================================
12390 
12391 /** @constant */
12392 net.user1.orbiter.ServerEvent.TIME_SYNC = "TIME_SYNC";
12393 /** @constant */
12394 net.user1.orbiter.ServerEvent.UPC_PROCESSED = "UPC_PROCESSED";
12395 /** @constant */
12396 net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT = "WATCH_FOR_PROCESSED_UPCS_RESULT";
12397 /** @constant */
12398 net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT = "STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT";
12399 /** @constant */
12400 net.user1.orbiter.ServerEvent.RESET_UPC_STATS_RESULT = "RESET_UPC_STATS_RESULT";
12401 
12402 //==============================================================================
12403 // VARIABLES
12404 //==============================================================================
12405 net.user1.orbiter.ServerEvent.prototype.getUPCProcessingRecord = function () {
12406   return upcProcessingRecord;
12407 }
12408   
12409 net.user1.orbiter.ServerEvent.prototype.getStatus = function () {
12410   return status;
12411 }
12412     
12413 
12414 net.user1.orbiter.ServerEvent.prototype.toString = function () {
12415   return "[object ServerEvent]";
12416 };
12417 //==============================================================================
12418 // CLASS DECLARATION
12419 //==============================================================================
12420 /** 
12421  * @class
12422  * @extends net.user1.orbiter.snapshot.Snapshot
12423  */
12424 net.user1.orbiter.snapshot.ServerModuleListSnapshot = function () {
12425   // Call superconstructor
12426   net.user1.orbiter.snapshot.Snapshot.call(this);
12427   this.moduleList = null;
12428   this.method = net.user1.orbiter.UPC.GET_SERVERMODULELIST_SNAPSHOT;
12429 };
12430 
12431 //==============================================================================
12432 // INHERITANCE
12433 //==============================================================================
12434 net.user1.utils.extend(net.user1.orbiter.snapshot.ServerModuleListSnapshot, net.user1.orbiter.snapshot.Snapshot);
12435 
12436 //==============================================================================
12437 // INSTANCE METHODS
12438 //==============================================================================         
12439 /**
12440  * @private
12441  */    
12442 net.user1.orbiter.snapshot.ServerModuleListSnapshot.prototype.setModuleList = function (value) {
12443   this.moduleList = value;
12444 }
12445 
12446 net.user1.orbiter.snapshot.ServerModuleListSnapshot.prototype.getModuleList = function () {
12447   if (!this.moduleList) {
12448     return null;
12449   }
12450   return this.moduleList.slice();
12451 };
12452 //==============================================================================
12453 // CLASS DECLARATION
12454 //==============================================================================
12455 /** 
12456  * @private
12457  * @class
12458  */
12459 net.user1.orbiter.upc.SetAttr = function (name,
12460                                           value,
12461                                           options) {
12462     
12463   // Abort if name is invalid.
12464   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
12465     throw new Error("Cannot set attribute. Illegal name" + 
12466       " (see Validator.isValidAttributeName()). " +
12467       " Illegal attribute is: " + name + "=" + value);
12468   }
12469 
12470   // Abort if value is invalid.
12471   if (!net.user1.orbiter.Validator.isValidAttributeValue(value)) {
12472     throw new Error("Cannot set attribute. Illegal value" + 
12473       " (see Validator.isValidAttributeValue()). " +
12474       " Illegal attribute is: " + name + "=" + value);
12475   }
12476   
12477   if (value == null) {
12478     value = "";
12479   }
12480   
12481   // Validation passed, so assign instance vars.
12482   this.name = name;
12483   this.value = value;
12484   this.options = options;
12485 };
12486 /**
12487  * @private
12488  */  
12489 net.user1.orbiter.upc.SetClientAttr = function (name, 
12490                                                 value, 
12491                                                 options,
12492                                                 scope,
12493                                                 clientID,
12494                                                 userID) {
12495   // Call superconstructor
12496   net.user1.orbiter.upc.SetAttr.call(this, name, value, options);
12497       
12498   // Abort if scope is invalid.
12499   if (!net.user1.orbiter.Validator.isValidAttributeScope(scope)) {
12500     throw new Error("Cannot set client attribute. Illegal scope" + 
12501              " (see Validator.isValidAttributeScope()). " +
12502              " Illegal attribute is: " + name + "=" + value);
12503   }
12504 
12505   // A scope null means the attribute is global.
12506   if (scope == null) {
12507     scope = net.user1.orbiter.Tokens.GLOBAL_ATTR;
12508   }
12509 
12510   this.method = net.user1.orbiter.UPC.SET_CLIENT_ATTR;
12511   this.args   = [clientID, userID, name, value, scope, options.toString()];
12512 };
12513 
12514 //==============================================================================    
12515 // INHERITANCE
12516 //============================================================================== 
12517 net.user1.utils.extend(net.user1.orbiter.upc.SetClientAttr, net.user1.orbiter.upc.SetAttr);
12518 /**
12519  * @private
12520  */  
12521 net.user1.orbiter.upc.SetRoomAttr = function (name, 
12522                                               value, 
12523                                               options,
12524                                               roomID) {
12525   // Call superconstructor
12526   net.user1.orbiter.upc.SetAttr.call(this, name, value, options);
12527       
12528   this.method = net.user1.orbiter.UPC.SET_ROOM_ATTR;
12529   this.args   = [roomID, name, value, options.toString()];
12530 };
12531 
12532 //==============================================================================    
12533 // INHERITANCE
12534 //============================================================================== 
12535 net.user1.utils.extend(net.user1.orbiter.upc.SetRoomAttr, net.user1.orbiter.upc.SetAttr);
12536 //==============================================================================
12537 // CLASS DECLARATION
12538 //==============================================================================
12539 /** 
12540  * @class
12541  * 
12542  * Note: Due to JavaScript's lack of memory measurement APIs and byte-measurement 
12543  * APIs, Orbiter's Statistics class does not include many of the statistics found
12544  * in the equivalent Reactor Statistics class.
12545  */
12546 net.user1.orbiter.Statistics = function (orbiter) {
12547   this.statsTimer;
12548   this.lastTick = NaN;
12549   this.lastTotalMessages = 0;
12550   this.messagesPerSecond = 0;
12551   
12552   // Peaks
12553   this.peakMessagesPerSecond = 0;
12554 
12555   this.orbiter = null;
12556   this.connection = null;
12557   
12558   this.statsIntervalID = -1;
12559   
12560   this.init(orbiter);
12561 };
12562 
12563 /**
12564  * @private
12565  */
12566 net.user1.orbiter.Statistics.prototype.init = function (orbiter) {
12567   this.setOrbiter(orbiter);
12568   this.start();
12569 };
12570 
12571 /**
12572  * @private
12573  */
12574 net.user1.orbiter.Statistics.prototype.setOrbiter = function (orbiter) {
12575   // Register new orbiter
12576   this.orbiter = orbiter;
12577 };
12578 
12579 net.user1.orbiter.Statistics.prototype.start = function () {
12580   this.stop();
12581   
12582   this.statsIntervalID = setInterval(statsTimerListener, 1000);
12583   
12584   this.lastTick = new Date().getTime();
12585   this.lastTotalMessages = this.getTotalMessages();
12586 };
12587 
12588 net.user1.orbiter.Statistics.prototype.stop = function () {
12589   clearInterval(statsIntervalID);
12590   this.clearStats();
12591 };
12592 
12593 /**
12594  * @private 
12595  */
12596 net.user1.orbiter.Statistics.prototype.clearStats = function () {
12597   this.lastTick = 0;
12598   this.lastTotalMessages = 0;
12599   this.messagesPerSecond = 0;
12600   this.peakMessagesPerSecond = 0;
12601 };
12602 
12603 net.user1.orbiter.Statistics.prototype.getLifetimeNumClientsConnected = function () {
12604   return this.orbiter.getClientManager().getLifetimeNumClientsKnown();
12605 };
12606 
12607 net.user1.orbiter.Statistics.prototype.getCurrentNumClientsConnected = function () {
12608   return this.orbiter.getClientManager().getNumClients();
12609 };
12610 
12611 net.user1.orbiter.Statistics.prototype.getTotalMessagesReceived = function () {
12612   return this.orbiter.getMessageManager().getNumMessagesReceived();
12613 }
12614 
12615 net.user1.orbiter.Statistics.prototype.getTotalMessagesSent = function () {
12616   return this.orbiter.getMessageManager().getNumMessagesSent();
12617 };
12618 
12619 net.user1.orbiter.Statistics.prototype.getTotalMessages = function () {
12620   return this.getTotalMessagesReceived() + this.getTotalMessagesSent();
12621 };
12622 
12623 net.user1.orbiter.Statistics.prototype.getMessagesPerSecond = function () {
12624   return this.messagesPerSecond;
12625 };
12626 
12627 //==============================================================================
12628 // PEAK MESSAGES PER SECOND
12629 //==============================================================================
12630 
12631 net.user1.orbiter.Statistics.prototype.getPeakMessagesPerSecond = function () {
12632   return this.peakMessagesPerSecond;
12633 };
12634 
12635 // =============================================================================
12636 // TIMER LISTENER
12637 // =============================================================================
12638 
12639 /**
12640  * @private 
12641  */
12642 net.user1.orbiter.Statistics.prototype.statsTimerListener = function (e) {
12643   // Check elapsed time
12644   var now = new Date().getTime();
12645   var elapsed = now - lastTick;
12646   lastTick = now;
12647   
12648   // Calculate number of messages sent and received since last tick
12649   var totalMessages = this.getTotalMessages();
12650   var tickNumMsgs   = totalMessages - this.lastTotalMessages;
12651   this.lastTotalMessages        = totalMessages;
12652   this.messagesPerSecond        = Math.round((1000/elapsed) * tickNumMsgs);
12653   if (this.messagesPerSecond > this.peakMessagesPerSecond) {
12654     this.peakMessagesPerSecond = this.messagesPerSecond;
12655   }
12656 };
12657 //==============================================================================
12658 //  STATUS CONSTANTS
12659 //==============================================================================
12660 /** @class */
12661 net.user1.orbiter.Status = new Object();
12662 /** @constant */
12663 net.user1.orbiter.Status.ACCOUNT_EXISTS         = "ACCOUNT_EXISTS";
12664 /** @constant */
12665 net.user1.orbiter.Status.ACCOUNT_NOT_FOUND      = "ACCOUNT_NOT_FOUND";
12666 /** @constant */
12667 net.user1.orbiter.Status.AUTHORIZATION_REQUIRED = "AUTHORIZATION_REQUIRED";
12668 /** @constant */
12669 net.user1.orbiter.Status.AUTHORIZATION_FAILED   = "AUTHORIZATION_FAILED";
12670 /** @constant */
12671 net.user1.orbiter.Status.ALREADY_ASSIGNED       = "ALREADY_ASSIGNED";
12672 /** @constant */
12673 net.user1.orbiter.Status.ALREADY_BANNED         = "ALREADY_BANNED";
12674 /** @constant */
12675 net.user1.orbiter.Status.ALREADY_IN_ROOM        = "ALREADY_IN_ROOM";
12676 /** @constant */
12677 net.user1.orbiter.Status.ALREADY_LOGGED_IN      = "ALREADY_LOGGED_IN";
12678 /** @constant */
12679 net.user1.orbiter.Status.ALREADY_OBSERVING      = "ALREADY_OBSERVING";
12680 /** @constant */
12681 net.user1.orbiter.Status.ALREADY_SYNCHRONIZED   = "ALREADY_SYNCHRONIZED";
12682 /** @constant */
12683 net.user1.orbiter.Status.ALREADY_WATCHING       = "ALREADY_WATCHING";  
12684 /** @constant */
12685 net.user1.orbiter.Status.ATTR_NOT_FOUND         = "ATTR_NOT_FOUND";
12686 /** @constant */
12687 net.user1.orbiter.Status.CLIENT_NOT_FOUND       = "CLIENT_NOT_FOUND";
12688 /** @constant */
12689 net.user1.orbiter.Status.ERROR                  = "ERROR";
12690 /** @constant */
12691 net.user1.orbiter.Status.EVALUATION_FAILED      = "EVALUATION_FAILED";
12692 /** @constant */
12693 net.user1.orbiter.Status.DUPLICATE_VALUE        = "DUPLICATE_VALUE";
12694 /** @constant */
12695 net.user1.orbiter.Status.IMMUTABLE              = "IMMUTABLE";
12696 /** @constant */
12697 net.user1.orbiter.Status.INVALID_QUALIFIER      = "INVALID_QUALIFIER";
12698 /** @constant */
12699 net.user1.orbiter.Status.NAME_NOT_FOUND         = "NAME_NOT_FOUND";
12700 /** @constant */
12701 net.user1.orbiter.Status.NAME_EXISTS            = "NAME_EXISTS";
12702 /** @constant */
12703 net.user1.orbiter.Status.NOT_ASSIGNED           = "NOT_ASSIGNED";
12704 /** @constant */
12705 net.user1.orbiter.Status.NOT_BANNED             = "NOT_BANNED";
12706 /** @constant */
12707 net.user1.orbiter.Status.NOT_IN_ROOM            = "NOT_IN_ROOM";
12708 /** @constant */
12709 net.user1.orbiter.Status.NOT_LOGGED_IN          = "NOT_LOGGED_IN";
12710 /** @constant */
12711 net.user1.orbiter.Status.NOT_OBSERVING          = "NOT_OBSERVING";
12712 /** @constant */
12713 net.user1.orbiter.Status.NOT_WATCHING           = "NOT_WATCHING";
12714 /** @constant */
12715 net.user1.orbiter.Status.PERMISSION_DENIED      = "PERMISSION_DENIED";
12716 /** @constant */
12717 net.user1.orbiter.Status.REMOVED                = "REMOVED";
12718 /** @constant */
12719 net.user1.orbiter.Status.ROLE_NOT_FOUND         = "ROLE_NOT_FOUND";
12720 /** @constant */
12721 net.user1.orbiter.Status.ROOM_EXISTS            = "ROOM_EXISTS";
12722 /** @constant */
12723 net.user1.orbiter.Status.ROOM_FULL              = "ROOM_FULL";
12724 /** @constant */
12725 net.user1.orbiter.Status.ROOM_NOT_FOUND         = "ROOM_NOT_FOUND";
12726 /** @constant */
12727 net.user1.orbiter.Status.SERVER_ONLY            = "SERVER_ONLY";
12728 /** @constant */
12729 net.user1.orbiter.Status.SUCCESS                = "SUCCESS";
12730 //==============================================================================
12731 //  STATUS CONSTANTS
12732 //==============================================================================
12733 /** @class */
12734 net.user1.orbiter.SynchronizationState = new Object();
12735 /** @constant */
12736 net.user1.orbiter.SynchronizationState.SYNCHRONIZED     = "SYNCHRONIZED";
12737 /** @constant */
12738 net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED = "NOT_SYNCHRONIZED";
12739 /** @constant */
12740 net.user1.orbiter.SynchronizationState.SYNCHRONIZING    = "SYNCHRONIZING";
12741 //==============================================================================
12742 // CLASS DECLARATION
12743 //==============================================================================
12744 /** 
12745  * @class
12746  */
12747 net.user1.orbiter.UPCProcessingRecord = function () {
12748   /**
12749    * @field
12750    */
12751   this.fromClientID = null;
12752   /**
12753    * @field
12754    */
12755   this.fromUserID = null;
12756   /**
12757    * @field
12758    */
12759   this.fromClientAddress = null;
12760   /**
12761    * @field
12762    */
12763   this.processingStartedAt = NaN;
12764   /**
12765    * @field
12766    */
12767   this.processingFinishedAt = NaN;
12768   /**
12769    * @field
12770    */
12771   this.processingDuration = NaN;
12772   /**
12773    * @field
12774    */
12775   this.queuedAt = NaN;
12776   /**
12777    * @field
12778    */
12779   this.queueDuration = NaN;
12780   /**
12781    * @field
12782    */
12783   this.UPCSource = null;
12784 };
12785 
12786 /** 
12787  * @private
12788  */
12789 net.user1.orbiter.UPCProcessingRecord.prototype.deserialize = function (serializedRecord) {
12790   var recordParts = [];
12791   var numSignificantSeparators = 6;
12792   var separatorIndices = [];
12793   var thisSeparatorIndex = 0;
12794   var previousSeparatorIndex = -1;
12795   
12796   // Don't use split because the source might contain the record separator
12797   for (var i = 0; i < numSignificantSeparators; i++) {
12798     thisSeparatorIndex = serializedRecord.indexOf(net.user1.orbiter.Tokens.RS, previousSeparatorIndex+1);
12799     recordParts.push(serializedRecord.substring(previousSeparatorIndex+1, thisSeparatorIndex));
12800     previousSeparatorIndex = thisSeparatorIndex;
12801   }
12802   recordParts.push(serializedRecord.substring(thisSeparatorIndex+1));
12803   
12804   this.deserializeParts(recordParts[0],
12805                         recordParts[1],
12806                         recordParts[2],
12807                         recordParts[3],
12808                         recordParts[4],
12809                         recordParts[5],
12810                         recordParts[6]);
12811 };
12812 
12813 /** 
12814  * @private
12815  */
12816 net.user1.orbiter.UPCProcessingRecord.prototype.deserializeParts = function (fromClientID,
12817                                                                              fromUserID,
12818                                                                              fromClientAddress,
12819                                                                              queuedAt,
12820                                                                              processingStartedAt,
12821                                                                              processingFinishedAt,
12822                                                                              source) {
12823   this.fromClientID = fromClientID;
12824   this.fromUserID = fromUserID;
12825   this.fromClientAddress = fromClientAddress;
12826   this.processingStartedAt = parseFloat(processingStartedAt);
12827   this.processingFinishedAt = parseFloat(processingFinishedAt);
12828   this.processingDuration = this.processingFinishedAt - this.processingStartedAt;
12829   this.queuedAt = parseFloat(queuedAt);
12830   this.queueDuration = this.processingStartedAt - this.queuedAt;
12831   this.UPCSource = source;
12832   var escapedCDStart = /<!\(\[CDATA\[/gi; 
12833   var escapedCDEnd = /\]\]\)>/gi; 
12834   this.UPCSource = this.UPCSource.replace(escapedCDStart, "<![CDATA[");
12835   this.UPCSource = this.UPCSource.replace(escapedCDEnd, "]]>");
12836 }
12837 //==============================================================================
12838 // CLASS DECLARATION
12839 //==============================================================================
12840 /** 
12841  * @class
12842  * @extends net.user1.orbiter.snapshot.Snapshot
12843  */
12844 net.user1.orbiter.snapshot.UPCStatsSnapshot = function () {
12845   // Call superconstructor
12846   net.user1.orbiter.snapshot.Snapshot.call(this);
12847   this.totalUPCsProcessed;
12848   this.numUPCsInQueue;
12849   this.lastQueueWaitTime;
12850   this.longestUPCProcesses;
12851   this.method = net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT;
12852   this.hasStatus = true;
12853 };
12854 
12855 //==============================================================================
12856 // INHERITANCE
12857 //==============================================================================
12858 net.user1.utils.extend(net.user1.orbiter.snapshot.UPCStatsSnapshot, net.user1.orbiter.snapshot.Snapshot);
12859 
12860 //==============================================================================
12861 // INSTANCE METHODS
12862 //==============================================================================         
12863 /**
12864  * @private
12865  */
12866 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setTotalUPCsProcessed = function (value) {
12867   this.totalUPCsProcessed = value;
12868 };
12869     
12870 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getTotalUPCsProcessed = function () {
12871   return this.totalUPCsProcessed;
12872 };
12873         
12874 /**
12875  * @private
12876  */
12877 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setNumUPCsInQueue = function (value) {
12878   this.numUPCsInQueue = value;
12879 };
12880 
12881 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getNumUPCsInQueue = function () {
12882   return this.numUPCsInQueue;
12883 };
12884     
12885 /**
12886  * @private
12887  */
12888 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setLastQueueWaitTime = function (value) {
12889   this.lastQueueWaitTime = value;
12890 };
12891 
12892 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getLastQueueWaitTime = function () {
12893   return this.lastQueueWaitTime;
12894 };
12895     
12896 /**
12897  * @private
12898  */
12899 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setLongestUPCProcesses = function (value) {
12900   this.longestUPCProcesses = value;
12901 };
12902 
12903 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getLongestUPCProcesses = function () {
12904   if (!this.longestUPCProcesses) {
12905     return null;
12906   }
12907   return this.longestUPCProcesses.slice();
12908 };
12909 //==============================================================================
12910 // CLASS DECLARATION
12911 //==============================================================================
12912 net.user1.orbiter.UpdateLevels = function () {
12913   this.restoreDefaults();
12914 };
12915 
12916 //==============================================================================    
12917 // STATIC VARIABLES
12918 //==============================================================================
12919 net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES     = 1;
12920 net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES = 1 << 1;
12921 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT = 1 << 2;
12922 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT = 1 << 3;
12923 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST = 1 << 4;
12924 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST = 1 << 5;
12925 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM = 1 << 6;
12926 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM = 1 << 7;
12927 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL = 1 << 8;
12928 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL = 1 << 9;
12929 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF = 1 << 10;
12930 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF = 1 << 11;
12931 net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES = 1 << 12;
12932 
12933 //==============================================================================    
12934 // INSTANCE METHODS
12935 //==============================================================================
12936 net.user1.orbiter.UpdateLevels.prototype.clearAll = function () {
12937   this.roomMessages = false;
12938   this.sharedRoomAttributes = false;
12939   this.occupantCount = false;
12940   this.observerCount = false;
12941   this.occupantList = false;
12942   this.observerList = false;
12943   this.sharedOccupantAttributesRoom = false;
12944   this.sharedOccupantAttributesGlobal = false;
12945   this.sharedObserverAttributesRoom = false;
12946   this.sharedObserverAttributesGlobal = false;
12947   this.occupantLoginLogoff = false;
12948   this.observerLoginLogoff = false;
12949   this.allRoomAttributes = false;
12950 };
12951 
12952 net.user1.orbiter.UpdateLevels.prototype.restoreDefaults = function () {
12953   this.roomMessages = true;
12954   this.sharedRoomAttributes = true;
12955   this.occupantCount = false;
12956   this.observerCount = false;
12957   this.occupantList = true;
12958   this.observerList = false;
12959   this.sharedOccupantAttributesRoom = true;
12960   this.sharedOccupantAttributesGlobal = true;
12961   this.sharedObserverAttributesRoom = false;
12962   this.sharedObserverAttributesGlobal = false;
12963   this.occupantLoginLogoff = true;
12964   this.observerLoginLogoff = false;
12965   this.allRoomAttributes = false;
12966 };
12967     
12968 net.user1.orbiter.UpdateLevels.prototype.toInt = function () {
12969   var levels = (this.roomMessages ? net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES : 0)
12970    | (this.sharedRoomAttributes ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES : 0)
12971    | (this.occupantCount ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT : 0)
12972    | (this.observerCount ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT : 0)
12973    | (this.occupantList ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST : 0)
12974    | (this.observerList ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST : 0)
12975    | (this.sharedOccupantAttributesRoom ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM : 0)
12976    | (this.sharedOccupantAttributesGlobal ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL : 0)
12977    | (this.sharedObserverAttributesRoom ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM : 0)
12978    | (this.sharedObserverAttributesGlobal ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL : 0)
12979    | (this.occupantLoginLogoff ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF : 0)
12980    | (this.observerLoginLogoff ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF : 0)
12981    | (this.allRoomAttributes ? net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES : 0);
12982   
12983   return levels;
12984 };
12985     
12986 net.user1.orbiter.UpdateLevels.prototype.fromInt = function (levels) {
12987   roomMessages                   = levels & net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES;
12988   sharedRoomAttributes           = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES;
12989   occupantCount                  = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT;
12990   observerCount                  = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT;
12991   occupantList                   = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST;
12992   observerList                   = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST;
12993   sharedOccupantAttributesRoom   = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM;
12994   sharedOccupantAttributesGlobal = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL;
12995   sharedObserverAttributesRoom   = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM;
12996   sharedObserverAttributesGlobal = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL;
12997   occupantLoginLogoff            = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF;
12998   observerLoginLogoff            = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF;
12999   allRoomAttributes              = levels & net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES;
13000 };
13001 //==============================================================================
13002 // CLASS DECLARATION
13003 //==============================================================================
13004 /** @class
13005 
13006 The UserAccount class dispatches the following events:
13007 
13008 <ul class="summary">
13009   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>    
13010   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF_RESULT}</li>    
13011   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>    
13012   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT}</li>    
13013   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD}</li>    
13014   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.SYNCHRONIZE}</li>    
13015   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE}</li> 
13016   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE_RESULT}</li>  
13017   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING}</li>    
13018   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT}</li>    
13019   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT}</li>    
13020   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT}</li>    
13021 </ul>
13022 
13023 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
13024 
13025     @extends net.user1.events.EventDispatcher
13026 */
13027 
13028 net.user1.orbiter.UserAccount = function (userID, 
13029                                           log,
13030                                           accountManager,
13031                                           clientManager,
13032                                           roomManager) {
13033   net.user1.events.EventDispatcher.call(this);
13034   
13035   this.userID = userID;
13036   this.attributeManager = null;
13037   this.connectionState = 0;
13038   this.password = null;
13039   this.lastAttemptedPassword = null;
13040   this._client = null;
13041   this._accountManager = null;
13042   this._clientManager = null;
13043   this._roomManager = null;
13044   this._log = null;
13045   
13046   this.setLog(log);
13047   this.setAccountManager(accountManager);
13048   this.setClientManager(clientManager);
13049   this.setRoomManager(roomManager);
13050 };
13051 
13052 //==============================================================================    
13053 // INHERITANCE
13054 //============================================================================== 
13055 net.user1.utils.extend(net.user1.orbiter.UserAccount, net.user1.events.EventDispatcher);
13056 
13057 //==============================================================================
13058 // STATIC VARIABLES
13059 //==============================================================================
13060 /** @private */    
13061 net.user1.orbiter.UserAccount.FLAG_MODERATOR = 1 << 1;
13062     
13063 //==============================================================================
13064 // DEPENDENCIES
13065 //==============================================================================
13066 
13067 /**
13068  * @private
13069  */        
13070 net.user1.orbiter.UserAccount.prototype.getAttributeCollection = function () {
13071   return this.attributeManager.getAttributeCollection();
13072 };
13073 
13074 /**
13075  * @private
13076  */        
13077 net.user1.orbiter.UserAccount.prototype.setAttributeManager = function (value) {
13078   this.attributeManager = value;
13079 };
13080 
13081 /**
13082  * @private
13083  */        
13084 net.user1.orbiter.UserAccount.prototype.getAttributeManager = function () {
13085   return this.attributeManager;
13086 };
13087 
13088 /**
13089  * @private
13090  */        
13091 net.user1.orbiter.UserAccount.prototype.getClientManager = function () {
13092   return this._clientManager;
13093 };
13094 
13095 /**
13096  * @private
13097  */        
13098 net.user1.orbiter.UserAccount.prototype.setClientManager = function (value) {
13099   this._clientManager = value;
13100 };
13101 
13102 /**
13103  * @private
13104  */        
13105 net.user1.orbiter.UserAccount.prototype.getRoomManager = function () {
13106   return this._roomManager;
13107 };
13108 
13109 /**
13110  * @private
13111  */        
13112 net.user1.orbiter.UserAccount.prototype.setRoomManager = function (value) {
13113   this._roomManager = value;
13114 };
13115 
13116 /**
13117  * @private
13118  */        
13119 net.user1.orbiter.UserAccount.prototype.getLog = function () {
13120   return this._log;
13121 };
13122 
13123 /**
13124  * @private
13125  */        
13126 net.user1.orbiter.UserAccount.prototype.setLog = function (value) {
13127   this._log = value;
13128 };
13129 
13130 /**
13131  * @private
13132  */        
13133 net.user1.orbiter.UserAccount.prototype.getAccountManager = function () {
13134   return this._accountManager;
13135 };
13136 
13137 /**
13138  * @private
13139  */        
13140 net.user1.orbiter.UserAccount.prototype.setAccountManager = function (value) {
13141   this._accountManager = value;
13142 };
13143 
13144 net.user1.orbiter.UserAccount.prototype.getClient = function () {
13145   var customClient;
13146   this.validateClientReference();
13147   if (this._client != null) {
13148     customClient = this._client.getCustomClient(null);
13149     return customClient == null ? this._client : customClient;
13150   } else {
13151     return null;
13152   }
13153 };
13154 
13155 net.user1.orbiter.UserAccount.prototype.getInternalClient = function () {
13156   this.validateClientReference();
13157   return this._client;
13158 }
13159 
13160 /**
13161  * @private
13162  */        
13163 net.user1.orbiter.UserAccount.prototype.setClient = function (value) {
13164   if (value == null) {
13165     this._client = null;
13166   } else {
13167     if (this._client != value) {
13168       this._client = value;
13169       this._client.setAccount(this);
13170     }
13171   }
13172 };
13173 
13174 /**
13175  * @private
13176  */
13177 net.user1.orbiter.UserAccount.prototype.validateClientReference = function () {
13178   if (this._client != null) {
13179     if (!this._client.isSelf()
13180         && !this._clientManager.isWatchingForClients()
13181         && !this._accountManager.isObservingAccount(this.getUserID()) 
13182         && !this._clientManager.isObservingClient(this._client.getClientID())
13183         && !this._roomManager.clientIsKnown(this._client.getClientID())) {
13184       this.setClient(null);
13185     }
13186   }
13187 };
13188 
13189 //==============================================================================
13190 // IS SELF
13191 //==============================================================================
13192 
13193 net.user1.orbiter.UserAccount.prototype.isSelf = function () {
13194   return this._client == null ? false : this._client.isSelf();
13195 };
13196 
13197 //==============================================================================
13198 // CONNECTION STATE
13199 //==============================================================================
13200 
13201 net.user1.orbiter.UserAccount.prototype.getConnectionState = function () {
13202   if (this.getInternalClient() != null) {
13203     return net.user1.orbiter.ConnectionState.LOGGED_IN;
13204   } else if (!this._accountManager.isObservingAccount(this.getUserID())) {
13205       return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
13206   } else if (this._clientManager.isWatchingForClients()) {
13207     return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
13208   } else {
13209     // Not observing this user, not watching for clients, and no client means
13210     // this account's state is unknown. (This happens when watching for user
13211     // accounts).
13212     return net.user1.orbiter.ConnectionState.UNKNOWN;
13213   }
13214 };
13215 
13216 net.user1.orbiter.UserAccount.prototype.isLoggedIn = function () {
13217   return this.getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN;
13218 };
13219 
13220 //==============================================================================
13221 // USER ID
13222 //==============================================================================
13223 
13224 net.user1.orbiter.UserAccount.prototype.getUserID = function () {
13225   return this.userID;
13226 };
13227 
13228 /**
13229  * @private
13230  */ 
13231 net.user1.orbiter.UserAccount.prototype.setUserID = function (userID) {
13232   if (this.userID != userID) {
13233     this.userID = userID;
13234   }
13235 };
13236 
13237 // =============================================================================
13238 // LOGOFF
13239 // =============================================================================
13240 
13241 net.user1.orbiter.UserAccount.prototype.logoff = function (password) {
13242   this._accountManager.logoff(this.getUserID(), password);
13243 };
13244 
13245 // =============================================================================
13246 // CHANGE PASSWORD
13247 // =============================================================================
13248 
13249 net.user1.orbiter.UserAccount.prototype.changePassword = function (newPassword, oldPassword) {
13250   this._accountManager.changePassword(this.getUserID(), newPassword, oldPassword);
13251 };
13252 
13253 // =============================================================================
13254 // ROLES
13255 // =============================================================================
13256 
13257 net.user1.orbiter.UserAccount.prototype.addRole = function (role) {
13258   this._accountManager.addRole(this.getUserID(), role);
13259 };
13260 
13261 net.user1.orbiter.UserAccount.prototype.removeRole = function (userID, role) {
13262   this._accountManager.removeRole(this.getUserID(), role);
13263 };
13264 
13265 net.user1.orbiter.UserAccount.prototype.isModerator = function () {
13266   var rolesAttr = this.getAttribute(net.user1.orbiter.Tokens.ROLES_ATTR);
13267   var roles;
13268   if (rolesAttr != null) {
13269     return (parseInt(rolesAttr) & UserAccount.FLAG_MODERATOR) > 0;
13270   } else {
13271     this.getLog().warn(this.toString() + " Could not determine moderator status because the account is not synchronized.");
13272     return false;
13273   }
13274 };
13275 
13276 // =============================================================================
13277 // LOGIN/LOGOFF TASKS
13278 // =============================================================================
13279 
13280 /**
13281  * @private
13282  */ 
13283 net.user1.orbiter.UserAccount.prototype.doLoginTasks = function () {
13284   this.fireLogin();
13285 };
13286 
13287 /**
13288  * @private
13289  */ 
13290 net.user1.orbiter.UserAccount.prototype.doLogoffTasks = function () {
13291   this.setClient(null);
13292   this.fireLogoff();
13293 };
13294 
13295 // =============================================================================
13296 // OBSERVATION
13297 // =============================================================================
13298 
13299 net.user1.orbiter.UserAccount.prototype.observe = function () {
13300   this._accountManager.observeAccount(this.getUserID());
13301 };
13302 
13303 net.user1.orbiter.UserAccount.prototype.stopObserving = function () {
13304   this._accountManager.stopObservingAccount(this.getUserID());
13305 };
13306 
13307 // =============================================================================
13308 // ATTRIBUTES: PUBLIC API
13309 // =============================================================================
13310 
13311 net.user1.orbiter.UserAccount.prototype.setAttribute = function (attrName, 
13312                                                                  attrValue, 
13313                                                                  attrScope, 
13314                                                                  isShared, 
13315                                                                  evaluate) {
13316   // Create an integer to hold the attribute options
13317   var attrOptions = net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT
13318                       | (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
13319                       | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
13320   
13321   // Set the attribute on the server.
13322   this.attributeManager.setAttribute(new net.user1.orbiter.upc.SetClientAttr(attrName, attrValue, attrOptions, attrScope, null, this.getUserID()));
13323 };
13324 
13325 net.user1.orbiter.UserAccount.prototype.deleteAttribute = function (attrName, attrScope) {
13326   var deleteRequest = new net.user1.orbiter.upc.RemoveClientAttr(null, this.getUserID(), attrName, attrScope);
13327   this.attributeManager.deleteAttribute(deleteRequest);
13328 };
13329 
13330 net.user1.orbiter.UserAccount.prototype.getAttribute = function (attrName, attrScope) {
13331   return this.attributeManager.getAttribute(attrName, attrScope);
13332 };
13333 
13334 net.user1.orbiter.UserAccount.prototype.getAttributes = function () {
13335   return this.attributeManager.getAttributes();
13336 }; 
13337 
13338 net.user1.orbiter.UserAccount.prototype.getAttributesByScope = function (scope) {
13339   return this.attributeManager.getAttributesByScope(scope);
13340 };
13341 
13342 //==============================================================================
13343 // TOSTRING
13344 //==============================================================================
13345 
13346 net.user1.orbiter.UserAccount.prototype.toString = function () {
13347   return "[USER_ACCOUNT userid: " + this.getUserID() + ", clientid: " + (this._client == null ? "" : this._client.getClientID()) + "]";
13348 };
13349 
13350 //==============================================================================
13351 // EVENT DISPATCHING
13352 //==============================================================================
13353 
13354 /**
13355  * @private
13356  */
13357 net.user1.orbiter.UserAccount.prototype.fireLogin = function () {
13358   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
13359                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13360   this.dispatchEvent(e);
13361 };    
13362 
13363 /**
13364  * @private
13365  */
13366 net.user1.orbiter.UserAccount.prototype.fireLogoffResult = function (status) {
13367   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF_RESULT,
13368                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13369   this.dispatchEvent(e);
13370 };    
13371 
13372 /**
13373  * @private
13374  */
13375 net.user1.orbiter.UserAccount.prototype.fireLogoff = function () {
13376   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
13377                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13378   this.dispatchEvent(e);
13379 };    
13380 
13381 /**
13382  * @private
13383  */
13384 net.user1.orbiter.UserAccount.prototype.fireChangePasswordResult = function (status) {
13385   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT,
13386                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13387   this.dispatchEvent(e);
13388 };    
13389 
13390 /**
13391  * @private
13392  */
13393 net.user1.orbiter.UserAccount.prototype.fireChangePassword = function () {
13394   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
13395                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13396   this.dispatchEvent(e);
13397 };    
13398 
13399 /**
13400  * @private
13401  */
13402 net.user1.orbiter.UserAccount.prototype.fireSynchronize = function () {
13403   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.SYNCHRONIZE, 
13404                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13405   this.dispatchEvent(e);
13406 };
13407 
13408 /**
13409  * @private
13410  */
13411 net.user1.orbiter.UserAccount.prototype.fireObserve = function () {
13412   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE, 
13413                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13414   this.dispatchEvent(e);
13415 };
13416 
13417 /**
13418  * @private
13419  */
13420 net.user1.orbiter.UserAccount.prototype.fireObserveResult = function (status) {
13421   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE_RESULT, 
13422                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13423   this.dispatchEvent(e);
13424 };
13425 
13426 /**
13427  * @private
13428  */
13429 net.user1.orbiter.UserAccount.prototype.fireStopObserving = function () {
13430   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING, 
13431                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13432   this.dispatchEvent(e);
13433 };
13434 
13435 /**
13436  * @private
13437  */
13438 net.user1.orbiter.UserAccount.prototype.fireStopObservingResult = function (status) {
13439   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT, 
13440                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13441   this.dispatchEvent(e);
13442 };
13443 
13444 /**
13445  * @private
13446  */
13447 net.user1.orbiter.UserAccount.prototype.fireAddRoleResult = function (role, status) {
13448   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT, 
13449                                              status, this.getUserID(), 
13450                                              (this._client == null ? null : this._client.getClientID()), role);
13451   this.dispatchEvent(e);
13452 }
13453 
13454 /**
13455  * @private
13456  */
13457 net.user1.orbiter.UserAccount.prototype.fireRemoveRoleResult = function (role, status) {
13458   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT, 
13459                                              status, this.getUserID(), 
13460                                              (this._client == null ? null : this._client.getClientID()), role);
13461   this.dispatchEvent(e);
13462 }
13463 
13464 
13465 
13466 
13467 
13468 
13469 
13470 
13471 
13472 
13473 
13474 
13475 
13476 //==============================================================================
13477 // VALIDATION UTILITIES
13478 //==============================================================================
13479 net.user1.orbiter.Validator = new Object();
13480 
13481 net.user1.orbiter.Validator.isValidRoomID = function (value) {
13482   // Can't be null, nor the empty string
13483   if (value == null || value == "") {
13484     return false;
13485   }
13486   // Can't contain "."
13487   if (value.indexOf(".") != -1) {
13488     return false;
13489   }
13490   // Can't contain RS
13491   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13492     return false;
13493   }
13494   // Can't contain WILDCARD
13495   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13496     return false;
13497   }
13498   
13499   return true;
13500 };
13501 
13502 net.user1.orbiter.Validator.isValidRoomQualifier = function (value) {
13503   if (value == null || value == "") {
13504     return false;
13505   }
13506   // "*" is valid (it means the unnamed qualifier)
13507   if (value == "*") {
13508     return true;
13509   }
13510   
13511   // Can't contain RS
13512   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13513     return false;
13514   }
13515   // Can't contain WILDCARD
13516   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13517     return false;
13518   }
13519   
13520   return true;
13521 };
13522 
13523 net.user1.orbiter.Validator.isValidResolvedRoomID = function (value) {
13524   // Can't be null, nor the empty string
13525   if (value == null || value == "") {
13526     return false;
13527   }
13528   
13529   // Can't contain RS
13530   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13531     return false;
13532   }
13533   // Can't contain WILDCARD
13534   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13535     return false;
13536   }
13537   
13538   return true;
13539 };
13540 
13541 net.user1.orbiter.Validator.isValidAttributeName = function (value) {
13542   // Can't be empty 
13543   if (value == "" || value == null) {
13544     return false;
13545   }
13546   
13547   // Can't contain RS
13548   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13549     return false;
13550   }
13551         
13552   return true;
13553 };
13554 
13555 net.user1.orbiter.Validator.isValidAttributeValue = function (value) {
13556   // Can't contain RS
13557   if (typeof value != "string") {
13558     // Non-string attribute values are coerced to strings at send time
13559     value = value.toString();
13560   }
13561   if (value.indexOf(net.user1.orbiter.Tokens.RS) == -1) {
13562     return true;
13563   } else {
13564     return false;
13565   }
13566 };
13567 
13568 net.user1.orbiter.Validator.isValidAttributeScope = function (value) {
13569   // Can't contain RS
13570   if (value != null) {
13571     return this.isValidResolvedRoomID(value);
13572   } else {
13573     return true;
13574   }
13575 };
13576 
13577 net.user1.orbiter.Validator.isValidModuleName = function (value) {
13578   // Can't be empty (can be null)
13579   if (value == "") {
13580     return false;
13581   }
13582   
13583   // Can't contain RS
13584   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13585     return false;
13586   }
13587   
13588   return true;
13589 };
13590 
13591 net.user1.orbiter.Validator.isValidPassword = function (value) {
13592   // Can't contain RS
13593   if (value != null && value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13594     return false;
13595   }
13596   
13597   return true;
13598 };
13599 //==============================================================================
13600 // CLASS DECLARATION
13601 //==============================================================================
13602 /** @class
13603  * The Orbiter class is the root class of every Orbiter application.
13604  * It provides basic tools for connecting to Union server, and gives
13605  * the application access to the core Orbiter system modules.
13606  * Orbiter dispatches the following events:
13607 
13608 <ul class="summary">
13609 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.READY}</li>
13610 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CLOSE}</li>
13611 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED}</li>
13612 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE}</li>
13613 </ul>
13614 
13615  * To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
13616  *
13617  * @param configURL The URL of a connection-configuration file. When the file
13618  * finishes loading, the Orbiter client automatically attempts to connect to
13619  * Union Server at the specified address(es). Note that the configuration file
13620  * need not be loaded at construction time; it can be loaded later via Orbiter's
13621  * loadConfig() method. For configuration file details, see loadConfig().
13622  *
13623  * @param traceLogMessages A flag indicating whether to send log messages to the
13624  * JavaScript output console. Applies to environments that support the
13625  * console.log() function only.
13626  *
13627  * @extends net.user1.events.EventDispatcher
13628  */
13629 net.user1.orbiter.Orbiter = function (configURL,
13630                                       traceLogMessages) {
13631   // Call superconstructor
13632   net.user1.events.EventDispatcher.call(this);
13633 
13634   // Initialization. For non-browser environments, set window to null.
13635   this.window = typeof window == "undefined" ? null : window;
13636 
13637   traceLogMessages = traceLogMessages == null ? true : traceLogMessages; 
13638 
13639   this.useSecureConnect = false;
13640   this.statistics = null;
13641   this.sessionID = null;
13642 
13643   // Initialize system versions
13644   this.system = new net.user1.orbiter.System(this.window);
13645                            
13646   // Set up the this.log.
13647   this.log = new net.user1.logger.Logger();
13648 
13649   // Output host version information.
13650   if (typeof navigator != "undefined") {
13651     this.log.info("User Agent: " + navigator.userAgent + " " + navigator.platform);
13652   }
13653   this.log.info("Union Client Version: " + this.system.getClientType() + " " + this.system.getClientVersion().toStringVerbose());
13654   this.log.info("Client UPC Protocol Version: " + this.system.getUPCVersion().toString());
13655   this.consoleLogger = null;
13656 
13657   // Set up the connection manager.
13658   this.connectionMan = new net.user1.orbiter.ConnectionManager(this);
13659   
13660   // Set up the room manager.
13661   this.roomMan = new net.user1.orbiter.RoomManager(this);
13662   
13663   // Set up the message manager.
13664   this.messageMan = new net.user1.orbiter.MessageManager(this.log, this.connectionMan);
13665 
13666   // Set up the server
13667   this.server = new net.user1.orbiter.Server(this);
13668   
13669   // Make the account manager.
13670   this.accountMan = new net.user1.orbiter.AccountManager(this.log);
13671   
13672   // Set up the client manager.
13673   this.clientMan = new net.user1.orbiter.ClientManager(this.roomMan, this.accountMan, this.connectionMan, this.messageMan, this.server, this.log);
13674 
13675   // Set up the account manager.
13676   this.accountMan.setClientManager(this.clientMan);
13677   this.accountMan.setMessageManager(this.messageMan);
13678   this.accountMan.setRoomManager(this.roomMan);
13679 
13680   // Set up the snapshot manager.
13681   this.snapshotMan = new net.user1.orbiter.SnapshotManager(this.messageMan);
13682   
13683   // Set up the core message listener
13684   this.coreMsgListener = new net.user1.orbiter.CoreMessageListener(this);
13685   
13686   // Log the core Reactor events
13687   this.coreEventLogger = new net.user1.orbiter.CoreEventLogger(this.log, this.connectionMan, this.roomMan, 
13688                                              this.accountMan, this.server, this.clientMan,
13689                                              this);
13690 
13691   // Register for ConnectionManager events.
13692   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.READY, 
13693                                  this.readyListener, this);
13694   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE, 
13695                                  this.connectFailureListener, this);
13696   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT, 
13697                                  this.disconnectListener, this);
13698   
13699   // Set up the connection monitor
13700   this.connectionMonitor = new net.user1.orbiter.ConnectionMonitor(this);
13701   this.connectionMonitor.restoreDefaults();
13702 
13703   // Register to be notified when a new connection is about to be opened 
13704   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
13705                                       this.selectConnectionListener, this);
13706   
13707   // Enable HTTP failover connections
13708   this.httpFailoverEnabled = true;
13709 
13710   if (traceLogMessages) {
13711     this.enableConsole();
13712   }
13713   
13714   // If the Reactor wasn't constructed with a config argument...
13715   if (configURL == null || configURL == "") {
13716     this.log.info("[ORBITER] Initialization complete.");
13717   } else {
13718     // ...otherwise, retrieve system settings from specified config file.
13719     this.loadConfig(configURL);
13720   }
13721 };
13722 
13723 //==============================================================================
13724 // INHERITANCE
13725 //==============================================================================
13726 net.user1.utils.extend(net.user1.orbiter.Orbiter, net.user1.events.EventDispatcher);
13727  
13728 //==============================================================================
13729 // XML CONFIG METHODS
13730 //==============================================================================
13731 
13732 /**
13733  * Loads the client-configuration file. When the file load completes,
13734  * Orbiter automatically attempts to connect to Union Server using
13735  * the settings specified by the configuration file.
13736  *
13737  * The configuration file has the following format:
13738  *
13739  * <pre>
13740  * <?xml version="1.0"?>
13741  * <config>
13742  *   <connections>
13743  *     <connection host="hostNameOrIP1" port="portNumber1" type="connectionType1" senddelay="milliseconds1" secure="false" />
13744  *     <connection host="hostNameOrIP2" port="portNumber2" type="connectionType2" senddelay="milliseconds2" secure="false" />
13745  *     ...
13746  *     <connection host="hostNameOrIPn" port="portNumbern" type="connectionTypen" senddelay="millisecondsn" secure="false" />
13747  *   </connections>
13748  *   <autoreconnectfrequency>frequency</autoreconnectfrequency>
13749  *   <connectiontimeout>duration</connectiontimeout>
13750  *   <heartbeatfrequency>frequency</heartbeatfrequency>
13751  *   <readytimeout>timeout</readytimeout>
13752  *   <loglevel>level</loglevel>
13753  * </config>
13754  * </pre>
13755  *
13756  * When the <code>secure</code> attribute is true, communication is
13757  * conducted over WSS or HTTPS using the environment's TLS implementation.
13758  */
13759 net.user1.orbiter.Orbiter.prototype.loadConfig = function (configURL) {
13760   this.log.info("[ORBITER] Loading config from " + configURL +".");
13761   var request = new XMLHttpRequest();
13762   var self = this;
13763   
13764   request.onerror = function () {
13765     self.configErrorListener();
13766   };
13767   
13768   request.onreadystatechange = function (state) {
13769     if (request.readyState == 4) {
13770       self.configLoadCompleteListener(request);
13771     }
13772   }
13773   request.open("GET", configURL);
13774   request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
13775   request.send(null);
13776 };
13777 
13778 /**
13779  * @private
13780  */
13781 net.user1.orbiter.Orbiter.prototype.getTextForNode = function (tree, tagname) {
13782   var nodes = tree.getElementsByTagName(tagname);
13783   var node;
13784   if (nodes.length > 0) {
13785     node = nodes[0];
13786   }
13787   
13788   if (node != null 
13789       && node.firstChild != null
13790       && node.firstChild.nodeType == 3
13791       && node.firstChild.nodeValue.length > 0) {
13792     return node.firstChild.nodeValue;
13793   } else {
13794     return null;
13795   }
13796 };
13797 
13798 
13799 /**
13800  * @private
13801  */
13802 net.user1.orbiter.Orbiter.prototype.configLoadCompleteListener = function (request) {
13803   var config = request.responseXML;
13804   if ((request.status != 200 && request.status != 0) || config == null) {
13805     this.log.error("[ORBITER] Configuration file failed to load.");
13806     return;
13807   }
13808   this.log.error("[ORBITER] Configuration file loaded.");
13809   try {
13810     var loglevel = this.getTextForNode(config, "logLevel");
13811     if (loglevel != null) {
13812       this.log.setLevel(loglevel);
13813     }
13814 
13815     var autoreconnectfrequencyNodes = config.getElementsByTagName("autoreconnectfrequency");
13816     var autoreconnectfrequencyNode = null;
13817     if (autoreconnectfrequencyNodes.length == 1) {
13818       autoreconnectfrequencyNode = autoreconnectfrequencyNodes[0];
13819       var nodetext = this.getTextForNode(config, "autoreconnectfrequency");
13820       if (nodetext != null && !isNaN(parseInt(nodetext))) {
13821         this.connectionMonitor.setAutoReconnectFrequency(
13822             parseInt(nodetext),
13823             parseInt(nodetext),
13824             autoreconnectfrequencyNode.getAttribute("delayfirstattempt") == null ? false :
13825             autoreconnectfrequencyNode.getAttribute("delayfirstattempt").toLowerCase() == "true"
13826           );
13827       } else {
13828         this.connectionMonitor.setAutoReconnectFrequency(
13829             parseInt(autoreconnectfrequencyNode.getAttribute("minms")),
13830             parseInt(autoreconnectfrequencyNode.getAttribute("maxms")),
13831             autoreconnectfrequencyNode.getAttribute("delayfirstattempt") == null ? false :
13832               autoreconnectfrequencyNode.getAttribute("delayfirstattempt").toLowerCase() == "true"
13833           );
13834       }
13835       if (autoreconnectfrequencyNode.getAttribute("maxattempts") != null
13836           && autoreconnectfrequencyNode.getAttribute("maxattempts").length > 0) {
13837         this.connectionMonitor.setAutoReconnectAttemptLimit(
13838             parseInt(autoreconnectfrequencyNode.getAttribute("maxattempts"))
13839           );
13840       }
13841     }
13842 
13843     var connectiontimeout = this.getTextForNode(config, "connectionTimeout"); 
13844     if (connectiontimeout != null) {
13845       this.connectionMonitor.setConnectionTimeout(parseInt(connectiontimeout));
13846     }
13847     
13848     var heartbeatfrequency = this.getTextForNode(config, "heartbeatFrequency"); 
13849     if (heartbeatfrequency != null) {
13850       this.connectionMonitor.setHeartbeatFrequency(parseInt(heartbeatfrequency));
13851     }
13852     
13853     var readytimeout = this.getTextForNode(config, "readyTimeout"); 
13854     if (readytimeout != null) {
13855       this.connectionMan.setReadyTimeout(parseInt(readytimeout));
13856     }
13857     
13858     var connections = config.getElementsByTagName("connection");
13859     if (connections.length == 0) {      
13860       this.log.error("[ORBITER] No connections specified in Orbiter configuration file.");
13861       return;
13862     }
13863     
13864     // Make connections
13865     var connection;
13866     var host;
13867     var port;
13868     var type;
13869     var secure;
13870     var sendDelay;
13871     
13872     for (var i = 0; i < connections.length; i++) {
13873       connection = connections[i];
13874       host = connection.getAttribute("host");
13875       port = connection.getAttribute("port");
13876       type = connection.getAttribute("type");
13877       if (type != null) {
13878         type = type.toUpperCase();
13879       }
13880       secure = connection.getAttribute("secure");
13881       sendDelay = connection.getAttribute("senddelay");
13882   
13883       switch (type) {
13884         // No type means make a socket connection with an http backup
13885         case null:
13886           if (secure === "true") {
13887             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET, -1);
13888             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP, sendDelay);
13889           } else {
13890             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.WEBSOCKET, -1);
13891             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.HTTP, sendDelay);
13892           }
13893           break;
13894 
13895         case net.user1.orbiter.ConnectionType.WEBSOCKET:
13896           if (secure === "true") {
13897             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET, -1);
13898           } else {
13899             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.WEBSOCKET, -1);
13900           }
13901           break;
13902 
13903         case net.user1.orbiter.ConnectionType.HTTP:
13904           if (secure === "true") {
13905             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP, sendDelay);
13906           } else {
13907             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.HTTP, sendDelay);
13908           }
13909           break;
13910         
13911         default:
13912           this.log.error("[ORBITER] Unrecognized connection type in Orbiter configuration file: [" + type + "]. Connection ignored.");
13913       }
13914     }
13915   } catch (error) {
13916     this.log.error("[ORBITER] Error parsing connection in Orbiter configuration file: \n" 
13917                    + request.responseText + "\n" + error.toString());
13918   }
13919   
13920   this.connect();
13921 };
13922 
13923 /** @private */   
13924 net.user1.orbiter.Orbiter.prototype.buildConnection = function (host, port, type, sendDelay) {
13925   var connection;
13926   
13927   switch (type) {
13928     case net.user1.orbiter.ConnectionType.HTTP:
13929       if (this.system.hasHTTPDirectConnection()) {
13930         connection = new net.user1.orbiter.HTTPDirectConnection();
13931       } else {
13932         connection = new net.user1.orbiter.HTTPIFrameConnection();
13933       }
13934       break;
13935 
13936     case net.user1.orbiter.ConnectionType.SECURE_HTTP:
13937       if (this.system.hasHTTPDirectConnection()) {
13938         connection = new net.user1.orbiter.SecureHTTPDirectConnection();
13939       } else {
13940         connection = new net.user1.orbiter.SecureHTTPIFrameConnection();
13941       }
13942       break;
13943 
13944     case net.user1.orbiter.ConnectionType.WEBSOCKET:
13945       connection = new net.user1.orbiter.WebSocketConnection();
13946       break;
13947 
13948     case net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET:
13949       connection = new net.user1.orbiter.SecureWebSocketConnection();
13950       break;
13951 
13952     default:
13953       throw new Error("[ORBITER] Error at buildConnection(). Invalid type specified: [" + type + "]");
13954   }
13955   
13956   try {
13957     connection.setServer(host, port);
13958   } catch (e) {
13959     this.log.error("[CONNECTION] " + connection.toString() + " " + e);
13960   } finally {
13961     this.connectionMan.addConnection(connection);
13962     if (connection instanceof net.user1.orbiter.HTTPConnection) {
13963       // Set delay after adding connection so the connection object has
13964       // access to this Orbiter object
13965       if (sendDelay != null && sendDelay != "") {
13966         connection.setSendDelay(sendDelay);
13967       }
13968     }
13969   }
13970 };
13971 
13972 /**
13973  * @private
13974  */
13975 net.user1.orbiter.Orbiter.prototype.configErrorListener = function (e) {
13976   this.log.fatal("[ORBITER] Configuration file could not be loaded.");
13977 };
13978 
13979 //==============================================================================
13980 // CONNECTION METHODS
13981 //==============================================================================
13982 
13983 /**
13984  * <p>
13985  * The connect() method attempts to connect to Union Server at the specified
13986  * host and ports. If no host and ports are specified, Orbiter attempts to
13987  * connect using the ConnectionManager's current list of hosts and ports.
13988  * </p>
13989  *
13990  * @param host
13991  * @param port1
13992  * @param port2
13993  * @param ...
13994  * @param portn
13995  */
13996 net.user1.orbiter.Orbiter.prototype.connect = function (host) {
13997   this.useSecureConnect = false;
13998   this.doConnect.apply(this, arguments);
13999 };
14000 
14001 /**
14002  * <p>
14003  * The secureConnect() method is identical to the connect() method, except that
14004  * it uses an encrypted connection (TLS or SSL) rather than an
14005  * unencrypted connection. Before secureConnect() can be used, Union Server
14006  * must be configured to accept client communications over a secure gateway,
14007  * which includes the installation of a server-side security certificate. For
14008  * instructions on configuring Union Server for secure communications, see
14009  * Union Server's documentation at http://unionplatform.com.
14010  * </p>
14011  *
14012  * @see net.user1.orbiter.Orbiter#connect
14013  */
14014 net.user1.orbiter.Orbiter.prototype.secureConnect = function (host) {
14015   this.useSecureConnect = true;
14016   this.doConnect.apply(this, arguments);
14017 };
14018 
14019 /**
14020  * @private
14021  */
14022 net.user1.orbiter.Orbiter.prototype.doConnect = function (host) {
14023   var ports = Array.prototype.slice.call(arguments).slice(1);
14024   if (host != null) {
14025     this.setServer.apply(this, [host].concat(ports));
14026   }
14027   this.log.info("[ORBITER] Connecting to Union...");
14028   this.connectionMan.connect();
14029 };
14030 
14031 net.user1.orbiter.Orbiter.prototype.disconnect = function () {
14032   this.connectionMan.disconnect();
14033 };
14034 
14035 net.user1.orbiter.Orbiter.prototype.setServer = function (host) {
14036   var ports = Array.prototype.slice.call(arguments).slice(1);
14037   if (host != null && ports.length > 0) {
14038     if (this.connectionMan.getConnections().length > 0) {
14039       this.connectionMan.removeAllConnections();
14040     }
14041     // Where possible, create WebSocket connections for the specified
14042     // host and its ports.
14043     var connectionType;
14044     if (this.system.hasWebSocket()) {
14045       for (var i = 1; i < arguments.length; i++) {
14046         connectionType = this.useSecureConnect
14047                          ? net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET
14048                          : net.user1.orbiter.ConnectionType.WEBSOCKET;
14049         this.buildConnection(host, arguments[i], connectionType, -1);
14050       }
14051     } else {
14052       this.log.info("[ORBITER] WebSocket not found in host environment. Trying HTTP.");
14053     }
14054     // Next, if failover is enabled or WebSocket is not supported, create HTTPConnections
14055     if (this.isHTTPFailoverEnabled() || !this.system.hasWebSocket()) {
14056       for (i = 1; i < arguments.length; i++) {
14057         connectionType = this.useSecureConnect
14058                          ? net.user1.orbiter.ConnectionType.SECURE_HTTP
14059                          : net.user1.orbiter.ConnectionType.HTTP;
14060         this.buildConnection(host,
14061                              arguments[i], 
14062                              connectionType,
14063                              net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY);
14064       }
14065     }
14066   } else {
14067     this.log.error("[ORBITER] setServer() failed. Invalid host [" + host + "] or port [" + ports.join(",") + "].");
14068   }
14069 };
14070 
14071 net.user1.orbiter.Orbiter.prototype.isReady = function () {
14072   return this.connectionMan.isReady();
14073 };
14074 
14075 //==============================================================================
14076 // HTTP FAILOVER CONFIGURATION
14077 //==============================================================================
14078 
14079 net.user1.orbiter.Orbiter.prototype.enableHTTPFailover = function () {
14080   this.httpFailoverEnabled = true;
14081 };
14082 
14083 net.user1.orbiter.Orbiter.prototype.disableHTTPFailover = function () {
14084   this.httpFailoverEnabled = false;
14085 };
14086 
14087 net.user1.orbiter.Orbiter.prototype.isHTTPFailoverEnabled = function () {
14088   return this.httpFailoverEnabled;
14089 };
14090 
14091 //==============================================================================
14092 // STATISTICS MANAGEMENT
14093 //==============================================================================
14094 
14095 net.user1.orbiter.Orbiter.prototype.enableStatistics = function () {
14096   if (this.statistics == null) {
14097     this.statistics = new net.user1.orbiter.Statistics(this);
14098   }
14099 }
14100 
14101 net.user1.orbiter.Orbiter.prototype.disableStatistics = function () {
14102   if (this.statistics != null) {
14103     this.statistics.stop();
14104   }
14105 }
14106 
14107 net.user1.orbiter.Orbiter.prototype.getStatistics = function () {
14108   return this.statistics;
14109 }
14110 
14111 //==============================================================================  
14112 // MANAGER AND SERVICE RETRIEVAL
14113 //==============================================================================
14114 net.user1.orbiter.Orbiter.prototype.getSystem = function () {
14115   return this.system;
14116 };
14117 
14118 net.user1.orbiter.Orbiter.prototype.getRoomManager = function () {
14119   return this.roomMan;
14120 };
14121 
14122 net.user1.orbiter.Orbiter.prototype.getConnectionManager = function () {
14123   return this.connectionMan;
14124 };
14125 
14126 net.user1.orbiter.Orbiter.prototype.getClientManager = function () {
14127   return this.clientMan;
14128 };
14129 
14130 net.user1.orbiter.Orbiter.prototype.getAccountManager = function () {
14131   return this.accountMan;
14132 };
14133 
14134 net.user1.orbiter.Orbiter.prototype.getMessageManager = function () {
14135   return this.messageMan;
14136 };
14137 
14138 net.user1.orbiter.Orbiter.prototype.getServer = function () {
14139   return this.server;
14140 };
14141 
14142 net.user1.orbiter.Orbiter.prototype.getConnectionMonitor = function () {
14143   return this.connectionMonitor;
14144 };
14145 
14146 /**
14147  * @private
14148  */
14149 net.user1.orbiter.Orbiter.prototype.getCoreMessageListener = function () {
14150   return this.coreMsgListener;
14151 }
14152 
14153 net.user1.orbiter.Orbiter.prototype.getLog = function () {
14154   return this.log;
14155 }
14156 
14157 net.user1.orbiter.Orbiter.prototype.self = function () {
14158   var customGlobalClient;
14159   
14160   if (this.clientMan == null || !this.isReady()) {
14161     return null;
14162   } else {
14163     customGlobalClient = this.clientMan.self().getCustomClient(null);
14164     if (customGlobalClient != null) {
14165       return customGlobalClient;
14166     } else {
14167       return this.clientMan.self();
14168     }
14169   } 
14170 };
14171 
14172 /**
14173  * @private
14174  */    
14175 net.user1.orbiter.Orbiter.prototype.getSnapshotManager = function () {
14176   return this.snapshotMan;
14177 };
14178 
14179 //==============================================================================
14180 // SNAPSHOT API
14181 //==============================================================================
14182 
14183 net.user1.orbiter.Orbiter.prototype.updateSnapshot = function (snapshot) {
14184   this.snapshotMan.updateSnapshot(snapshot);
14185 }
14186 
14187 //==============================================================================
14188 // CONNECTION EVENT LISTENERS
14189 //==============================================================================
14190 
14191 /**
14192  * @private 
14193  * Responds to a connection failure. 
14194  */
14195 net.user1.orbiter.Orbiter.prototype.connectFailureListener = function (e) {
14196   // Tell listeners that the connection is now closed.
14197   this.fireClose();
14198 };
14199 
14200 /**
14201  * @private 
14202  * Triggers a CLOSE event when the connection is lost. 
14203  */
14204 net.user1.orbiter.Orbiter.prototype.disconnectListener = function (e) {
14205   this.accountMan.cleanup();
14206   this.roomMan.cleanup();
14207   this.clientMan.cleanup();
14208   this.server.cleanup();
14209   this.fireClose();
14210 };
14211 
14212 /**
14213  * @private 
14214  * Triggers a READY event when the connection is ready. 
14215  */
14216 net.user1.orbiter.Orbiter.prototype.readyListener = function (e) {
14217   this.fireReady();
14218 };
14219 
14220 net.user1.orbiter.Orbiter.prototype.selectConnectionListener = function (e) {
14221   this.messageMan.addMessageListener(net.user1.orbiter.UPC.SERVER_HELLO, this.u66, this);
14222   this.messageMan.addMessageListener(net.user1.orbiter.UPC.CONNECTION_REFUSED, this.u164, this);
14223 }
14224 
14225 //==============================================================================
14226 // CLIENT ID
14227 //==============================================================================
14228 net.user1.orbiter.Orbiter.prototype.getClientID = function () {
14229   return this.self() ? this.self().getClientID() : "";
14230 };
14231 
14232 //==============================================================================
14233 // EVENT DISPATCHING
14234 //==============================================================================
14235 
14236 /**
14237  * @private
14238  * Notifies listeners that this Orbiter object's connection to the server
14239  * was lost or could not be established.
14240  */
14241 net.user1.orbiter.Orbiter.prototype.fireClose = function () {
14242   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CLOSE));
14243 };
14244 
14245 /**
14246  * @private
14247  * Notifies listeners that the Orbiter is fully initialized and 
14248  * ready to transact with the server.
14249  */
14250 net.user1.orbiter.Orbiter.prototype.fireReady = function () {
14251   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.READY));
14252 };
14253 
14254 /**
14255  * @private
14256  * Notifies listeners that the Orbiter is fully initialized and 
14257  * ready to transact with the server.
14258  */
14259 net.user1.orbiter.Orbiter.prototype.fireProtocolIncompatible = function (serverUPCVersion) {
14260   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE,
14261                                  serverUPCVersion));
14262 };
14263 
14264 /**
14265  * @private
14266  * Notifies listeners that the server refused the connection.
14267  */
14268 net.user1.orbiter.Orbiter.prototype.dispatchConnectRefused = function (refusal) {
14269   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED,
14270                                  null, refusal));
14271 };
14272 
14273 //==============================================================================
14274 // UPC Listeners
14275 //==============================================================================
14276 
14277 /**
14278  * @private
14279  * SERVER_HELLO
14280  */
14281 net.user1.orbiter.Orbiter.prototype.u66 = function (serverVersion, 
14282                                                     sessionID,
14283                                                     serverUPCVersionString,
14284                                                     protocolCompatible,
14285                                                     affinityAddress,
14286                                                     affinityDuration) {
14287   var serverUPCVersion = new net.user1.orbiter.VersionNumber();
14288   serverUPCVersion.fromVersionString(serverUPCVersionString);
14289   if (protocolCompatible == "false") {
14290     this.fireProtocolIncompatible(serverUPCVersion);
14291   }
14292 };
14293 
14294 /**
14295  * @private
14296  * CONNECTION_REFUSED
14297  */
14298 net.user1.orbiter.Orbiter.prototype.u164 = function (reason, description) {
14299   this.connectionMonitor.setAutoReconnectFrequency(-1);
14300   this.dispatchConnectRefused(new net.user1.orbiter.ConnectionRefusal(reason, description));
14301 };
14302 
14303 //==============================================================================
14304 // SESSION ID
14305 //==============================================================================
14306     
14307 net.user1.orbiter.Orbiter.prototype.getSessionID = function () {
14308   return this.sessionID == null ? "" : this.sessionID;
14309 };
14310     
14311 /**
14312  * @private
14313  */        
14314 net.user1.orbiter.Orbiter.prototype.setSessionID = function (id) {
14315   this.sessionID = id;
14316 };
14317 
14318 //==============================================================================    
14319 // CONSOLE LOGGING
14320 //==============================================================================
14321 net.user1.orbiter.Orbiter.prototype.enableConsole = function () {
14322   if (this.consoleLogger == null) {
14323     this.consoleLogger = new net.user1.logger.ConsoleLogger(this.log);
14324   }
14325 };
14326 
14327 net.user1.orbiter.Orbiter.prototype.disableConsole = function () {
14328   if (this.consoleLogger != null) {
14329     this.consoleLogger.dispose();
14330     this.consoleLogger = null;
14331   }
14332 };
14333 
14334 //==============================================================================
14335 // CLEANUP
14336 //==============================================================================
14337 
14338 /**
14339  * Permanently disables this object and releases all of its
14340  * resources. Once dispose() is called, the object can never
14341  * be used again. Use dispose() only when purging an object
14342  * from memory, as is required when unloading an iframe. 
14343  * 
14344  * To simply disconnect an Orbiter object, use disconnect().
14345  */
14346 net.user1.orbiter.Orbiter.prototype.dispose = function () {
14347   this.log.info("[ORBITER] Beginning disposal of all resources...");
14348   this.connectionMan.dispose();
14349   this.roomMan.dispose();
14350   this.connectionMonitor.dispose();
14351   this.clientMan.dispose();
14352   this.messageMan.dispose();
14353   if (this.statistics != null) {
14354     this.statistics.stop();
14355   }
14356   this.log.info("[ORBITER] Disposal complete.");
14357 }
14358 //==============================================================================
14359 // CONNECTION STATE CONSTANTS
14360 //==============================================================================
14361 /** @class */
14362 net.user1.orbiter.ConnectionState = new Object();
14363 /** @constant */
14364 net.user1.orbiter.ConnectionState.UNKNOWN                    = -1;
14365 /** @constant */
14366 net.user1.orbiter.ConnectionState.NOT_CONNECTED              = 0;
14367 /** @constant */
14368 net.user1.orbiter.ConnectionState.READY                      = 1;
14369 /** @constant */
14370 net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS     = 2;
14371 /** @constant */
14372 net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS  = 3;
14373 /** @constant */
14374 net.user1.orbiter.ConnectionState.LOGGED_IN                  = 4;
14375 //==============================================================================
14376 // CLASS DECLARATION
14377 //==============================================================================
14378 /** @class
14379     @extends net.user1.events.Event
14380 */
14381 net.user1.orbiter.ConnectionEvent = function (type, upc, data, connection, status) {
14382   net.user1.events.Event.call(this, type);
14383   
14384   this.upc = upc;
14385   this.data = data;
14386   this.connection = connection
14387   this.status = status;
14388 };
14389 
14390 //==============================================================================
14391 // INHERITANCE
14392 //==============================================================================
14393 net.user1.utils.extend(net.user1.orbiter.ConnectionEvent, net.user1.events.Event);
14394 
14395 //==============================================================================
14396 // STATIC VARIABLES
14397 //==============================================================================
14398 
14399 /** @constant */
14400 net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT = "BEGIN_CONNECT";
14401 /** @constant */
14402 net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE = "BEGIN_HANDSHAKE";
14403 /** @constant */
14404 net.user1.orbiter.ConnectionEvent.READY = "READY";
14405 /** @constant */
14406 net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE = "CONNECT_FAILURE";
14407 /** @constant */
14408 net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT = "CLIENT_KILL_CONNECT";
14409 /** @constant */
14410 net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT = "SERVER_KILL_CONNECT";
14411 /** @constant */
14412 net.user1.orbiter.ConnectionEvent.DISCONNECT = "DISCONNECT";
14413 /** @constant */
14414 net.user1.orbiter.ConnectionEvent.RECEIVE_UPC = "RECEIVE_UPC";
14415 /** @constant */
14416 net.user1.orbiter.ConnectionEvent.SEND_DATA = "SEND_DATA";
14417 /** @constant */
14418 net.user1.orbiter.ConnectionEvent.RECEIVE_DATA = "RECEIVE_DATA";
14419 /** @constant */
14420 net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
14421 /** @constant */
14422 net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND = "SESSION_NOT_FOUND";
14423   
14424 //==============================================================================
14425 // INSTANCE METHODS
14426 //==============================================================================
14427 
14428 net.user1.orbiter.ConnectionEvent.prototype.getUPC = function () {
14429   return this.upc;
14430 }
14431 
14432 net.user1.orbiter.ConnectionEvent.prototype.getData = function () {
14433   return this.data;
14434 }
14435 
14436 net.user1.orbiter.ConnectionEvent.prototype.getStatus = function () {
14437   return this.status;
14438 }
14439 
14440 net.user1.orbiter.ConnectionEvent.prototype.toString = function () {
14441   return "[object ConnectionEvent]";
14442 }  
14443 
14444 //==============================================================================
14445 // HTTP REQUEST MODE CONSTANTS
14446 //==============================================================================
14447 /** @class */
14448 net.user1.orbiter.ConnectionType = new Object();
14449 /** @constant */
14450 net.user1.orbiter.ConnectionType.HTTP =  "HTTP";
14451 /** @constant */
14452 net.user1.orbiter.ConnectionType.SECURE_HTTP =  "SECURE_HTTP";
14453 /** @constant */
14454 net.user1.orbiter.ConnectionType.WEBSOCKET =  "WEBSOCKET";
14455 /** @constant */
14456 net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET =  "SECURE_WEBSOCKET";
14457 //==============================================================================
14458 // CLASS DECLARATION
14459 //==============================================================================
14460 /** @class
14461  * Connection is the abstract superclass of HTTPConnection and WebSocketConnection;
14462  * it is used internally by Orbiter, and is not intended for direct use by Orbiter
14463  * developers. For information on communication with Union Server, see
14464  * Orbiter's connect() method, the WebSocketConnection class and the
14465  * HTTPDirectConnection and HTTPIFrameConnection classes.
14466  *
14467  * The Connection class dispatches the following events:
14468 
14469 <ul class="summary">
14470 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT}</li>
14471 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE}</li>
14472 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.READY}</li>
14473 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE}</li>
14474 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT}</li>
14475 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT}</li>
14476 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.DISCONNECT}</li>
14477 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_UPC}</li>
14478 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SEND_DATA}</li>
14479 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_DATA}</li>
14480 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED}</li>
14481 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND}</li>
14482 </ul>
14483 
14484 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
14485 
14486     @extends net.user1.events.EventDispatcher
14487 
14488  *
14489  * @see net.user1.orbiter.Orbiter#connect
14490  * @see net.user1.orbiter.Orbiter#secureConnect
14491  * @see net.user1.orbiter.HTTPDirectConnection
14492  * @see net.user1.orbiter.HTTPIFrameConnection
14493  * @see net.user1.orbiter.WebSocketConnection
14494  * @see net.user1.orbiter.SecureHTTPDirectConnection
14495  * @see net.user1.orbiter.SecureHTTPIFrameConnection
14496  * @see net.user1.orbiter.SecureWebSocketConnection
14497  */
14498 net.user1.orbiter.Connection = function (host, port, type) {
14499   // Call superconstructor
14500   net.user1.events.EventDispatcher.call(this);
14501 
14502   // Variables
14503   this.mostRecentConnectAchievedReady = false;
14504   this.mostRecentConnectTimedOut = false;
14505   this.readyCount = 0;
14506   this.connectAttemptCount = 0;
14507   this.connectAbortCount = 0;
14508   this.readyTimeoutID = 0;
14509   this.readyTimeout = 0;
14510   this.orbiter = null;
14511   this.disposed = false;
14512   this.requestedHost = null;
14513   
14514   // Initialization
14515   this.setServer(host, port);
14516   this.connectionType = type;
14517   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
14518 };
14519 
14520 //==============================================================================    
14521 // INHERITANCE
14522 //============================================================================== 
14523 net.user1.utils.extend(net.user1.orbiter.Connection, net.user1.events.EventDispatcher);
14524 
14525 //==============================================================================    
14526 // DEPENDENCIES
14527 //============================================================================== 
14528 /** @private */
14529 net.user1.orbiter.Connection.prototype.setOrbiter = function (orbiter) {
14530   if (this.orbiter != null) {
14531     this.orbiter.getMessageManager().removeMessageListener("u63", this.u63);
14532     this.orbiter.getMessageManager().removeMessageListener("u66", this.u66);
14533     this.orbiter.getMessageManager().removeMessageListener("u84", this.u84);
14534     this.orbiter.getMessageManager().removeMessageListener("u85", this.u85);
14535   }
14536   this.orbiter = orbiter;
14537 }
14538   
14539 //==============================================================================    
14540 // CONNECT/DISCONNECT
14541 //============================================================================== 
14542 net.user1.orbiter.Connection.prototype.connect = function () {
14543   this.disconnect();
14544   this.applyAffinity();
14545   this.orbiter.getLog().info(this.toString() + " Attempting connection...");
14546   this.connectAttemptCount++;
14547   this.mostRecentConnectAchievedReady = false;
14548   this.mostRecentConnectTimedOut = false;
14549   this.connectionState = net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS;
14550   // Start the ready timer. Ready state must be achieved before the timer
14551   // completes or the connection will auto-disconnect.
14552   this.startReadyTimer();
14553   this.dispatchBeginConnect();
14554 }
14555   
14556 net.user1.orbiter.Connection.prototype.disconnect = function () {
14557   var state = this.connectionState;
14558  
14559   if (state != net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
14560     this.deactivateConnection();
14561  
14562     if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
14563       this.connectAbortCount++;
14564       this.dispatchConnectFailure("Client closed connection before READY state was achieved.");
14565     } else {
14566       this.dispatchClientKillConnect();
14567     }
14568   }
14569 }
14570     
14571 /** @private */
14572 net.user1.orbiter.Connection.prototype.deactivateConnection = function () {
14573   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
14574   this.stopReadyTimer();
14575   this.orbiter.setSessionID("");
14576 }
14577   
14578 //==============================================================================    
14579 // CONNECTION CONFIGURATION
14580 //==============================================================================    
14581 net.user1.orbiter.Connection.prototype.setServer = function (host,
14582                                                              port) {
14583   this.requestedHost = host;
14584       
14585   // Check for valid ports
14586   if (port < 1 || port > 65536) {
14587     throw new Error("Illegal port specified [" + port + "]. Must be greater than 0 and less than 65537.");
14588   }
14589   this.port  = port;
14590 }
14591 
14592 net.user1.orbiter.Connection.prototype.getRequestedHost = function () {
14593   return this.requestedHost;
14594 };
14595 
14596 net.user1.orbiter.Connection.prototype.getHost = function () {
14597   if (this.host == null) {
14598     return this.getRequestedHost();
14599   } else {
14600     return this.host;
14601   }
14602 };
14603 
14604 net.user1.orbiter.Connection.prototype.getPort = function () {
14605   return this.port;
14606 };
14607 
14608 net.user1.orbiter.Connection.prototype.getType = function () {
14609   return this.connectionType;
14610 };
14611     
14612 //==============================================================================
14613 // READY HANDSHAKE
14614 //==============================================================================
14615 /** @private */
14616 net.user1.orbiter.Connection.prototype.beginReadyHandshake = function () {
14617   this.dispatchBeginHandshake();
14618   
14619   if (!this.orbiter.getMessageManager().hasMessageListener("u63", this.u63)) {
14620     this.orbiter.getMessageManager().addMessageListener("u63", this.u63, this);
14621     this.orbiter.getMessageManager().addMessageListener("u66", this.u66, this);
14622     this.orbiter.getMessageManager().addMessageListener("u84", this.u84, this);
14623     this.orbiter.getMessageManager().addMessageListener("u85", this.u85, this);
14624   }
14625   
14626   this.sendHello();
14627 }
14628 
14629 /** @private */
14630 net.user1.orbiter.Connection.prototype.sendHello = function() {
14631   var helloString = this.buildHelloMessage();
14632   this.orbiter.getLog().debug(this.toString() + " Sending CLIENT_HELLO: " + helloString);
14633   this.transmitHelloMessage(helloString);
14634 }
14635 
14636 /** @private */
14637 net.user1.orbiter.Connection.prototype.buildHelloMessage = function () {
14638   var helloString = "<U><M>u65</M>"
14639     + "<L>"
14640     + "<A>" + this.orbiter.getSystem().getClientType() + "</A>"
14641     + "<A>" + (typeof navigator != "undefined" ? navigator.userAgent + ";" : "") 
14642     + this.orbiter.getSystem().getClientVersion().toStringVerbose() + "</A>"
14643     + "<A>" + this.orbiter.getSystem().getUPCVersion().toString() + "</A></L></U>";
14644   return helloString;
14645 }
14646 
14647 /** @private */
14648 net.user1.orbiter.Connection.prototype.transmitHelloMessage = function (helloString) {
14649   this.send(helloString);
14650 }
14651     
14652 //==============================================================================
14653 // READY TIMER
14654 //==============================================================================
14655 /** @private */
14656 net.user1.orbiter.Connection.prototype.readyTimerListener = function () {
14657   this.stopReadyTimer();
14658   if (this.connectionState == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
14659     this.orbiter.getLog().warn("[CONNECTION] " + this.toString() + " Failed to achieve" + 
14660             " ready state after " + this.readyTimeout + "ms. Aborting connection...");
14661     this.mostRecentConnectTimedOut = true;
14662     this.disconnect();
14663   }
14664 }
14665 
14666 /** @private */
14667 net.user1.orbiter.Connection.prototype.stopReadyTimer = function () {
14668   if (this.readyTimeoutID != -1) {
14669     clearTimeout(this.readyTimeoutID);
14670   }
14671 }
14672 
14673 /** @private */
14674 net.user1.orbiter.Connection.prototype.startReadyTimer = function () {
14675   var currentObj = this;
14676   var callback   = this.readyTimerListener;
14677   this.stopReadyTimer();
14678   this.readyTimeout = this.orbiter.getConnectionManager().getReadyTimeout();
14679   var self = this;
14680   this.readyTimeoutID = setTimeout (function () {
14681     callback.call(currentObj);
14682   }, self.readyTimeout);
14683 }
14684 
14685 //==============================================================================
14686 // READY STATE ACCESS
14687 //==============================================================================
14688 /** @private */
14689 net.user1.orbiter.Connection.prototype.getReadyCount = function () {
14690   return this.readyCount;
14691 }
14692     
14693 net.user1.orbiter.Connection.prototype.isReady = function () {
14694   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
14695 }
14696 
14697 /** @private */
14698 net.user1.orbiter.Connection.prototype.isValid = function () {
14699   if (this.mostRecentConnectAchievedReady) {
14700     this.orbiter.getLog().debug(this.toString() + " Connection is"
14701       + " valid because its last connection attempt succeeded.");
14702     return true;
14703   }
14704   
14705   if (this.connectAttemptCount == 0) {
14706     this.orbiter.getLog().debug(this.toString() + " Connection is"
14707       + " valid because it has either never attempted to connect, or has not"
14708       + " attempted to connect since its last successful connection.");
14709     return true;
14710   }
14711   
14712   if ((this.connectAttemptCount > 0) && 
14713       (this.connectAttemptCount == this.connectAbortCount)
14714       && !this.mostRecentConnectTimedOut) {
14715     this.orbiter.getLog().debug(this.toString() + " Connection is"
14716       + " valid because either all connection attempts ever or all"
14717       + " connection attempts since its last successful connection were"
14718       + " aborted before the ready timeout was reached.");
14719     return true;
14720   }
14721   
14722   this.orbiter.getLog().debug(this.toString() + " Connection is not"
14723     + " valid; its most recent connection failed to achieve a ready state.");
14724   return false;
14725 }
14726 
14727     
14728 //==============================================================================
14729 // UPC LISTENERS
14730 //==============================================================================
14731 /** @private */
14732 net.user1.orbiter.Connection.prototype.u63 = function () {
14733   this.stopReadyTimer();
14734   this.connectionState = net.user1.orbiter.ConnectionState.READY;
14735   this.mostRecentConnectAchievedReady = true;
14736   this.readyCount++;
14737   this.connectAttemptCount = 0;
14738   this.connectAbortCount   = 0;
14739   this.dispatchReady();
14740 }    
14741 
14742 /** @private */
14743 net.user1.orbiter.Connection.prototype.u66 = function (serverVersion, 
14744                                                        sessionID, 
14745                                                        upcVersion, 
14746                                                        protocolCompatible,
14747                                                        affinityAddress,
14748                                                        affinityDuration) {
14749   this.orbiter.setSessionID(sessionID);
14750 };
14751 
14752 /** @private */
14753 net.user1.orbiter.Connection.prototype.u84 = function () {
14754   this.dispatchSessionTerminated();
14755 }    
14756 
14757 /** @private */
14758 net.user1.orbiter.Connection.prototype.u85 = function () {
14759   this.dispatchSessionNotFound();
14760 }    
14761 
14762 //==============================================================================    
14763 // SERVER AFFINITY
14764 //==============================================================================
14765 /** @private */
14766 net.user1.orbiter.Connection.prototype.applyAffinity = function () {
14767   var affinityAddress = this.orbiter.getConnectionManager().getAffinity(this.requestedHost);
14768   if (affinityAddress == this.requestedHost) {
14769     this.orbiter.getLog().info(this.toString() + " No affinity address found for requested host [" 
14770                                + this.requestedHost + "]. Using requested host for next connection attempt.");
14771   } else {
14772     this.orbiter.getLog().info(this.toString() + " Applying affinity address [" + affinityAddress + "] for supplied host [" + this.requestedHost + "].");
14773   }
14774   this.host = affinityAddress;
14775 }
14776 
14777 //==============================================================================    
14778 // TOSTRING
14779 //==============================================================================     
14780 net.user1.orbiter.Connection.prototype.toString = function () {
14781   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
14782           + ", host: " + (this.host == null ? "" : this.host) 
14783           + ", port: " + this.port + "]";
14784   return s;
14785 }
14786     
14787 //==============================================================================    
14788 // EVENT DISPATCHING
14789 //==============================================================================  
14790 /** @private */
14791 net.user1.orbiter.Connection.prototype.dispatchSendData = function (data) {
14792   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SEND_DATA,
14793                                     null, data, this));
14794 }
14795 
14796 /** @private */
14797 net.user1.orbiter.Connection.prototype.dispatchReceiveData = function (data) {
14798   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_DATA,
14799                                     null, data, this));
14800 }
14801 
14802 /** @private */
14803 net.user1.orbiter.Connection.prototype.dispatchConnectFailure = function (status) {
14804   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
14805                                     null, null, this, status));
14806 }
14807 
14808 /** @private */
14809 net.user1.orbiter.Connection.prototype.dispatchBeginConnect = function () {
14810   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT,
14811                                     null, null, this));
14812 }
14813 
14814 /** @private */
14815 net.user1.orbiter.Connection.prototype.dispatchBeginHandshake = function () {
14816   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE,
14817                                     null, null, this));
14818 }
14819 
14820 /** @private */
14821 net.user1.orbiter.Connection.prototype.dispatchReady = function () {
14822   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.READY,
14823                                     null, null, this));
14824 }
14825 
14826 /** @private */
14827 net.user1.orbiter.Connection.prototype.dispatchServerKillConnect  = function () {
14828   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT,
14829                                     null, null, this));
14830   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
14831                                     null, null, this));
14832 }
14833 
14834 /** @private */
14835 net.user1.orbiter.Connection.prototype.dispatchClientKillConnect = function () {
14836     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT,
14837                                       null, null, this));
14838     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
14839                                       null, null, this));
14840 }
14841 
14842 /** @private */
14843 net.user1.orbiter.Connection.prototype.dispatchSessionTerminated = function () {
14844   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED,
14845                                     null, null, this));
14846 }
14847 
14848 /** @private */
14849 net.user1.orbiter.Connection.prototype.dispatchSessionNotFound = function () {
14850   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND,
14851                                     null, null, this));
14852 }
14853 
14854 //==============================================================================    
14855 // DISPOSAL
14856 //==============================================================================  
14857 /** @private */
14858 net.user1.orbiter.Connection.prototype.dispose = function () {
14859   this.disposed = true;
14860   this.messageManager.removeMessageListener("u63", this.u63);
14861   this.messageManager.removeMessageListener("u66", this.u66);
14862   this.messageManager.removeMessageListener("u84", this.u84);
14863   this.messageManager.removeMessageListener("u85", this.u85);
14864   this.stopReadyTimer();
14865   this.readyTimer = null;
14866   this.orbiter = null;
14867 }
14868 //==============================================================================    
14869 // CLASS DECLARATION
14870 //==============================================================================
14871 /**
14872  * @class
14873  *
14874  * <p>
14875  * The WebSocketConnection class is used by Orbiter to communicate with
14876  * Union Server over a persistent TCP/IP socket. Normally, developers need not
14877  * use the WebSocketConnection class directly, and should instead make connections
14878  * via the Orbiter class's connect() method. However, the
14879  * WebSocketConnection class is required for fine-grained connection configuration,
14880  * such as defining failover socket connections for multiple Union Servers
14881  * running at different host addresses.
14882  * </p>
14883  *
14884  * <p>
14885  * By default, Orbiter uses WebSocketConnection connections to communicate
14886  * with Union Server. WebSocketConnection connections offer faster response times than
14887  * HTTP connections, but occupy an operating-system-level socket continuously
14888  * for the duration of the connection. If a WebSocketConnection connection
14889  * cannot be established (due to, say, a restrictive firewall), Orbiter
14890  * automatically attempts to communicate using HTTP requests sent via an
14891  * HTTPDirectConnection or HTTPIFrameConnection. Developers can override
14892  * Orbiter's default connection failover system by manually configuring
14893  * connections using the ConnectionManager class and Orbiter's
14894  * disableHTTPFailover() method.</p>
14895  *
14896  * <p>
14897  * For secure WebSocket and HTTP communications, see SecureWebSocketConnection,
14898  * SecureHTTPDirectConnection, and SecureHTTPIFrameConnection.
14899  * </p>
14900  *
14901  * For a list of events dispatched by WebSocketConnection, see
14902  * WebSocketConnection's superclass, {@link net.user1.orbiter.Connection}.
14903  *
14904  * @extends net.user1.orbiter.Connection
14905  *
14906  * @see net.user1.orbiter.Orbiter#connect
14907  * @see net.user1.orbiter.Orbiter#secureConnect
14908  * @see net.user1.orbiter.SecureWebSocketConnection
14909  * @see net.user1.orbiter.SecureHTTPDirectConnection
14910  * @see net.user1.orbiter.SecureHTTPIFrameConnection
14911  */
14912 net.user1.orbiter.WebSocketConnection = function (host, port, type) {
14913   // Invoke superclass constructor
14914   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.WEBSOCKET);
14915   
14916   this.socket = null;
14917 };
14918 
14919 //==============================================================================
14920 // INHERITANCE
14921 //==============================================================================
14922 net.user1.utils.extend(net.user1.orbiter.WebSocketConnection, net.user1.orbiter.Connection);
14923     
14924 //==============================================================================    
14925 // SOCKET OBJECT MANAGEMENT
14926 //==============================================================================
14927 /** @private */     
14928 net.user1.orbiter.WebSocketConnection.prototype.getNewSocket = function () {
14929   // Deactivate the old socket
14930   this.deactivateSocket(this.socket);
14931   
14932   // Create the new socket
14933   if (typeof MozWebSocket != "undefined") {
14934     // Firefox 6
14935     this.socket = new MozWebSocket(this.buildURL());
14936   } else {
14937     // Other browsers
14938     this.socket = new WebSocket(this.buildURL());
14939   }
14940 
14941   // Register for socket events
14942   var self = this;
14943   this.socket.onopen = function (e) {self.connectListener(e)};
14944   this.socket.onmessage = function (e) {self.dataListener(e)};
14945   this.socket.onclose = function (e) {self.closeListener(e)};
14946   this.socket.onerror = function (e) {self.ioErrorListener(e)};
14947 };
14948 
14949 /** @private */
14950 net.user1.orbiter.WebSocketConnection.prototype.buildURL = function () {
14951   return "ws://" + this.host + ":" + this.port;
14952 };
14953 
14954 /** @private */ 
14955 net.user1.orbiter.WebSocketConnection.prototype.deactivateSocket = function (oldSocket) {
14956   if (oldSocket == null) {
14957     return;
14958   }
14959   
14960   this.socket.onopen = null;
14961   this.socket.onmessage = null;
14962   this.socket.onclose = null;
14963   this.socket.onerror = null;
14964   
14965   try {
14966     oldSocket.close()
14967   } catch (e) {
14968     // Do nothing
14969   }
14970   
14971   this.socket = null;
14972 };
14973     
14974 //==============================================================================    
14975 // CONNECTION AND DISCONNECTION
14976 //==============================================================================    
14977   
14978 net.user1.orbiter.WebSocketConnection.prototype.connect = function () {
14979   net.user1.orbiter.Connection.prototype.connect.call(this);
14980       
14981   // Attempt to connect
14982   try {
14983     this.getNewSocket();
14984   } catch (e) {
14985     // Socket could not be opened
14986     this.deactivateConnection();
14987     this.dispatchConnectFailure(e.toString());
14988   }
14989 };
14990 
14991 /** @private */ 
14992 net.user1.orbiter.WebSocketConnection.prototype.deactivateConnection = function () {
14993   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
14994   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
14995   this.deactivateSocket(this.socket);
14996   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
14997 };    
14998 
14999 //==============================================================================    
15000 // SOCKET CONNECTION LISTENERS
15001 //==============================================================================
15002 /** @private */     
15003 net.user1.orbiter.WebSocketConnection.prototype.connectListener = function (e) {
15004   if (this.disposed) return;
15005   
15006   this.orbiter.getLog().debug(this.toString() + " Socket connected.");
15007   this.beginReadyHandshake();
15008 }
15009   
15010 /** @private */ 
15011 net.user1.orbiter.WebSocketConnection.prototype.closeListener = function (e) {
15012   if (this.disposed) return;
15013   
15014   var state = this.connectionState;
15015   this.deactivateConnection();
15016   
15017   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15018     this.dispatchConnectFailure("WebSocket onclose: Server closed connection before READY state was achieved.");
15019   } else {
15020     this.dispatchServerKillConnect();
15021   }
15022 };
15023 
15024 /** @private */ 
15025 net.user1.orbiter.WebSocketConnection.prototype.ioErrorListener = function (e) {
15026   if (this.disposed) return;
15027   
15028   var state = this.connectionState;
15029   this.deactivateConnection();
15030   
15031   // Note: when Union closes the connection, Firefox 7 dispatches onerror, not 
15032   // onclose, so treat onerror like an onclose event
15033   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15034     this.dispatchConnectFailure("WebSocket onerror: Server closed connection before READY state was achieved.");
15035   } else {
15036     this.dispatchServerKillConnect();
15037   }
15038 };
15039 
15040 //==============================================================================    
15041 // DATA RECEIVING AND SENDING
15042 //==============================================================================  
15043 /** @private */ 
15044 net.user1.orbiter.WebSocketConnection.prototype.dataListener = function (dataEvent) {
15045   if (this.disposed) return;
15046 
15047   var data = dataEvent.data;
15048   this.dispatchReceiveData(data);
15049 
15050   if (data.indexOf("<U>") == 0) {
15051     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(
15052                                       net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
15053                                       data));
15054   } else {
15055     // The message isn't UPC. Must be an error...
15056     this.orbiter.getLog().error(this.toString() + " Received invalid message" 
15057                                + " (not UPC or malformed UPC): " + data);
15058   }
15059 };
15060 
15061 /** @private */ 
15062 net.user1.orbiter.WebSocketConnection.prototype.send = function (data) {
15063   this.dispatchSendData(data);
15064   this.socket.send(data);
15065 };
15066     
15067 // =============================================================================
15068 // DISPOSAL
15069 // =============================================================================
15070 /** @private */ 
15071 net.user1.orbiter.WebSocketConnection.prototype.dispose = function () {
15072   net.user1.orbiter.Connection.prototype.dispose.call(this);
15073   this.deactivateSocket(this.socket);
15074 };
15075 //==============================================================================    
15076 // CLASS DECLARATION
15077 //==============================================================================
15078 /** @class
15079  *
15080  * <p>
15081  * The SecureWebSocketConnection class is identical to WebSocketConnection
15082  * except that it performs communications over WSS (i.e., an encrypted TLS or
15083  * SSL socket connection) rather than plain WebSocket.</p>
15084  *
15085  * For a list of events dispatched by SecureWebSocketConnection, see
15086  * {@link net.user1.orbiter.Connection}.
15087  *
15088  * @extends net.user1.orbiter.WebSocketConnection
15089  *
15090  * @see net.user1.orbiter.WebSocketConnection
15091  */
15092 net.user1.orbiter.SecureWebSocketConnection = function (host, port) {
15093   // Invoke superclass constructor
15094   net.user1.orbiter.WebSocketConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET);
15095 };
15096 
15097 //==============================================================================
15098 // INHERITANCE
15099 //==============================================================================
15100 net.user1.utils.extend(net.user1.orbiter.SecureWebSocketConnection, net.user1.orbiter.WebSocketConnection);
15101     
15102 /** @private */
15103 net.user1.orbiter.SecureWebSocketConnection.prototype.buildURL = function () {
15104   return "wss://" + this.host + ":" + this.port;
15105 };
15106 //==============================================================================    
15107 // CLASS DECLARATION
15108 //==============================================================================
15109 /**
15110  * @class
15111  *
15112  * HTTPConnection is the abstract superclass of HTTPDirectConnection and
15113  * HTTPIFrameConnection; it is used internally by Orbiter, and is not intended
15114  * for direct use by Orbiter developers. For information on HTTP communication
15115  * with Union Server, see the HTTPDirectConnection and HTTPIFrameConnection classes.
15116  *
15117  * For a list of events dispatched by HTTPConnection, see HTTPConnection's
15118  * superclass, {@link net.user1.orbiter.Connection}.
15119  *
15120  * @extends net.user1.orbiter.Connection
15121  *
15122  *
15123  * @see net.user1.orbiter.HTTPDirectConnection
15124  * @see net.user1.orbiter.HTTPIFrameConnection
15125  */
15126 net.user1.orbiter.HTTPConnection = function (host, port, type) {
15127   // Invoke superclass constructor
15128   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15129 
15130   // Instance variables
15131   this.url = "";
15132   this.sendDelayTimerEnabled = true;
15133   this.sendDelayTimeoutID = -1;
15134   this.sendDelayTimerRunning = false;
15135   this.sendDelay = net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY;
15136   
15137   this.messageQueue = new Array();
15138   
15139   this.retryDelay = 500;
15140   this.retryHelloTimeoutID = -1;
15141   this.retryIncomingTimeoutID = -1;
15142   this.retryOutgoingTimeoutID = -1;
15143 
15144   this.helloResponsePending = false;
15145   this.outgoingResponsePending = false;
15146   
15147   // Initialization
15148   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED, this.sessionTerminatedListener, this);
15149   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND, this.sessionNotFoundListener, this);
15150 };
15151 
15152 //==============================================================================
15153 // INHERITANCE
15154 //==============================================================================
15155 net.user1.utils.extend(net.user1.orbiter.HTTPConnection, net.user1.orbiter.Connection);
15156 
15157 //==============================================================================    
15158 // STATIC VARIABLES
15159 //==============================================================================    
15160 /** @constant */
15161 net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY = 300;
15162     
15163 //==============================================================================    
15164 // ABSTRACT METHODS (MUST BE IMPLEMENTED BY SUBCLASSES)
15165 //==============================================================================    
15166     
15167 net.user1.orbiter.HTTPConnection.prototype.doRequestDeactivation = net.user1.utils.abstractError;
15168 net.user1.orbiter.HTTPConnection.prototype.doSendHello = net.user1.utils.abstractError;
15169 net.user1.orbiter.HTTPConnection.prototype.doRetryHello = net.user1.utils.abstractError;
15170 net.user1.orbiter.HTTPConnection.prototype.doSendOutgoing = net.user1.utils.abstractError;
15171 net.user1.orbiter.HTTPConnection.prototype.doRetryOutgoing = net.user1.utils.abstractError;
15172 net.user1.orbiter.HTTPConnection.prototype.doSendIncoming = net.user1.utils.abstractError;
15173 net.user1.orbiter.HTTPConnection.prototype.doRetryIncoming = net.user1.utils.abstractError;
15174 net.user1.orbiter.HTTPConnection.prototype.doDispose = net.user1.utils.abstractError;
15175     
15176 //==============================================================================    
15177 // CONNECTION AND DISCONNECTION
15178 //==============================================================================    
15179 net.user1.orbiter.HTTPConnection.prototype.connect = function () {
15180   net.user1.orbiter.Connection.prototype.connect.call(this);
15181 };
15182     
15183 /** @private */
15184 net.user1.orbiter.HTTPConnection.prototype.deactivateConnection = function () {
15185   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
15186   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
15187   this.stopSendDelayTimer();
15188   if (this.retryHelloTimeoutID != -1) {
15189     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled hello-request retry.");
15190     clearTimeout(this.retryHelloTimeoutID);
15191     this.retryHelloTimeoutID = -1
15192   }
15193   if (this.retryIncomingTimeoutID != -1) {
15194     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled incoming-request retry.");
15195     clearTimeout(this.retryIncomingTimeoutID);
15196     this.retryIncomingTimeoutID = -1
15197   }
15198   if (this.retryOutgoingTimeoutID != -1) {
15199     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled outgoing-request retry.");
15200     clearTimeout(this.retryOutgoingTimeoutID);
15201     this.retryOutgoingTimeoutID = -1
15202   }
15203   this.deactivateHTTPRequests();
15204   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
15205 };
15206     
15207 /** @private */
15208 net.user1.orbiter.HTTPConnection.prototype.deactivateHTTPRequests = function () {
15209   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Closing all pending HTTP requests.");
15210   this.doRequestDeactivation();
15211   this.helloResponsePending = false;
15212   this.outgoingResponsePending = false;
15213 };
15214 
15215 //==============================================================================    
15216 // SESSION MANAGEMENT
15217 //==============================================================================     
15218 
15219 /** @private */
15220 net.user1.orbiter.HTTPConnection.prototype.sessionTerminatedListener = function (e) {
15221   var state = this.connectionState;
15222   this.deactivateConnection();
15223   
15224   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15225     this.dispatchConnectFailure("Server terminated session before READY state was achieved.");
15226   } else {
15227     this.dispatchServerKillConnect();
15228   }
15229 };
15230 
15231 /** @private */
15232 net.user1.orbiter.HTTPConnection.prototype.sessionNotFoundListener = function (e) {
15233   var state = this.connectionState;
15234   
15235   this.deactivateConnection();
15236   
15237   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15238     this.dispatchConnectFailure("Client attempted to reestablish an expired session"
15239                                 + " or establish an unknown session.");
15240   } else {
15241     this.dispatchServerKillConnect();
15242   }
15243 }
15244 
15245     
15246 //==============================================================================    
15247 // SERVER ASSIGNMENT
15248 //==============================================================================    
15249 /** @private */
15250 net.user1.orbiter.HTTPConnection.prototype.setServer = function (host, port) {
15251   try {
15252     net.user1.orbiter.Connection.prototype.setServer.call(this, host, port);
15253   } finally {
15254     this.buildURL();
15255   }
15256 }
15257 
15258 /** @private */
15259 net.user1.orbiter.HTTPConnection.prototype.buildURL = function () {
15260   this.url = "http://" + this.host + ":" + this.port;
15261 }
15262 
15263 //==============================================================================    
15264 // OUTGOING DELAY TIMER
15265 //==============================================================================    
15266 /** @private */
15267 net.user1.orbiter.HTTPConnection.prototype.sendDelayTimerListener = function () {
15268   this.sendDelayTimerRunning = false;
15269   if (this.messageQueue.length > 0) {
15270     this.flushMessageQueue();
15271   } else {
15272     // No messages in queue, so take no action.
15273   }
15274 }
15275     
15276 /** @private */
15277 net.user1.orbiter.HTTPConnection.prototype.stopSendDelayTimer = function () {
15278   this.sendDelayTimerRunning = false;
15279   if (this.sendDelayTimeoutID != -1) {
15280     clearTimeout(this.sendDelayTimeoutID);
15281   }
15282   this.sendDelayTimeoutID = -1;
15283 }
15284     
15285 /** @private */
15286 net.user1.orbiter.HTTPConnection.prototype.startSendDelayTimer = function () {
15287   this.stopSendDelayTimer();
15288   var currentObj = this;
15289   var callback   = this.sendDelayTimerListener;
15290   this.sendDelayTimerRunning = true;
15291   this.sendDelayTimeoutID = setTimeout(function () {
15292     callback.call(currentObj);
15293   }, this.sendDelay);
15294 }
15295     
15296 net.user1.orbiter.HTTPConnection.prototype.setSendDelay = function (milliseconds) {
15297   if (milliseconds > 0) {
15298     if ((milliseconds != this.sendDelay)) {
15299       this.sendDelay = milliseconds;
15300       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Send delay set to: ["
15301                              + milliseconds + "]."); 
15302     }
15303     this.sendDelayTimerEnabled = true;
15304   } else if (milliseconds == -1) {
15305     this.orbiter.getLog().debug("[CONNECTION] " + toString() + " Send delay disabled.");
15306     this.sendDelayTimerEnabled = false;
15307     this.stopSendDelayTimer();
15308   } else {
15309     throw new Error("[CONNECTION]" + this.toString() + " Invalid send-delay specified: [" 
15310                     + milliseconds + "]."); 
15311   }
15312 }
15313     
15314 net.user1.orbiter.HTTPConnection.prototype.getSendDelay = function () {
15315   return this.sendDelay;
15316 }
15317 
15318 //==============================================================================    
15319 // RETRY DELAY
15320 //============================================================================== 
15321 net.user1.orbiter.HTTPConnection.prototype.setRetryDelay = function (milliseconds) {
15322   if (milliseconds > -1) {
15323     if (milliseconds != this.retryDelay) {
15324       this.retryDelay = milliseconds;
15325       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Retry delay set to: ["
15326                                   + milliseconds + "]."); 
15327     }
15328   } else {
15329     throw new Error("[CONNECTION]" + this.toString() + " Invalid retry delay specified: [" 
15330                     + milliseconds + "]."); 
15331   }
15332 }
15333 
15334 //==============================================================================    
15335 // DATA SENDING AND QUEUING
15336 //==============================================================================  
15337     
15338 net.user1.orbiter.HTTPConnection.prototype.send = function (data) {
15339   // If the timer isn't running...
15340   if (!this.sendDelayTimerRunning) {
15341     // ...it is either disabled or expired. Either way, it's time to 
15342     // attempt to flush the queue.
15343     this.messageQueue.push(data);
15344     this.flushMessageQueue();
15345   } else {
15346     // The send-delay timer is running, so we can't send yet. Just queue the message.
15347     this.messageQueue.push(data);
15348   }
15349 }
15350     
15351 /** @private */
15352 net.user1.orbiter.HTTPConnection.prototype.flushMessageQueue = function () {
15353   if (!this.outgoingResponsePending) {
15354     this.openNewOutgoingRequest(this.messageQueue.join(""));
15355     this.messageQueue = new Array();
15356   } else {
15357     // AN OUTGOING RESPONSE IS STILL PENDING, SO DON'T SEND A NEW ONE
15358   }
15359 }
15360 
15361 //==============================================================================    
15362 // HELLO REQUEST MANAGEMENT
15363 //==============================================================================  
15364 
15365 /** @private */
15366 net.user1.orbiter.HTTPConnection.prototype.transmitHelloMessage = function (helloString) {
15367   this.dispatchSendData(helloString);
15368   this.helloResponsePending = true;
15369   this.doSendHello(helloString);
15370 }    
15371 
15372 /** @private */
15373 net.user1.orbiter.HTTPConnection.prototype.helloCompleteListener = function (data) {
15374   if (this.disposed) return;
15375   
15376   if (this.helloResponsePending) {
15377     this.helloResponsePending = false;
15378     this.processIncomingData(data);
15379     
15380     // Don't immediately open a request in the complete handler due to Win IE bug
15381     var self = this;
15382     setTimeout(function () {
15383       self.openNewIncomingRequest();
15384     }, 0);
15385   } else {
15386     if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
15387       this.orbiter.getLog().error("[CONNECTION]" + toString() + " u66 (SERVER_HELLO) received, but client is not connected. Ignoring.");
15388     } else {
15389       this.orbiter.getLog().error("[CONNECTION]" + toString() + " Redundant u66 (SERVER_HELLO) received. Ignoring.");
15390     }
15391   }
15392 }
15393 
15394 /** @private */
15395 net.user1.orbiter.HTTPConnection.prototype.helloErrorListener = function () {
15396   if (this.disposed) return;
15397   // There's already a retry scheduled
15398   if (this.retryHelloTimeoutID != -1) return;  
15399   // The connection attempt has been aborted
15400   if (this.connectionState != net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15401     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
15402                                 + " Connection is no longer in progress, so no retry scheduled."); 
15403     return;
15404   }
15405   
15406   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
15407                               + " Retrying in " +  this.retryDelay + "ms."); 
15408   
15409   // Retry
15410   var self = this;
15411   this.retryHelloTimeoutID = setTimeout(function () {
15412     self.retryHelloTimeoutID = -1;
15413     self.doRetryHello();
15414   }, this.retryDelay);
15415 }
15416 
15417 //==============================================================================    
15418 // OUTGOING REQUEST MANAGEMENT
15419 //==============================================================================
15420 
15421 /** @private */
15422 net.user1.orbiter.HTTPConnection.prototype.openNewOutgoingRequest = function (data) {
15423   this.dispatchSendData(data);
15424   this.outgoingResponsePending = true;
15425   this.doSendOutgoing(data);
15426   if (this.sendDelayTimerEnabled == true) {
15427     this.startSendDelayTimer();
15428   }
15429 }
15430 
15431 /** @private */
15432 net.user1.orbiter.HTTPConnection.prototype.outgoingCompleteListener = function () {
15433   if (this.disposed) return;
15434   
15435   this.outgoingResponsePending = false;
15436   
15437   if (!this.sendDelayTimerRunning && this.messageQueue.length > 0) {
15438     // Don't immediately open a request in the complete handler due to Win IE bug
15439     var self = this;
15440     setTimeout(function () {
15441       self.flushMessageQueue();
15442     }, 0);
15443   }
15444 }
15445 
15446 /** @private */
15447 net.user1.orbiter.HTTPConnection.prototype.outgoingErrorListener = function () {
15448   if (this.disposed) return;
15449   // There's already a retry scheduled
15450   if (this.retryOutgoingTimeoutID != -1) return;  
15451   // The connection has been closed
15452   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15453       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15454     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
15455                                 + " Connection is closed, so no retry scheduled."); 
15456     return;
15457   } 
15458   
15459   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
15460                               + " Retrying in " +  this.retryDelay + "ms.");  
15461       
15462   // Retry
15463   var self = this;
15464   this.retryOutgoingTimeoutID = setTimeout(function () {
15465     self.retryOutgoingTimeoutID = -1;
15466     if (self.disposed
15467         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15468         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15469       return;
15470     }
15471     self.doRetryOutgoing();
15472   }, this.retryDelay);
15473 }
15474 
15475 //==============================================================================    
15476 // INCOMING REQUEST MANAGEMENT
15477 //==============================================================================  
15478 
15479 /** @private */
15480 net.user1.orbiter.HTTPConnection.prototype.openNewIncomingRequest = function () {
15481   this.doSendIncoming();
15482 }
15483 
15484 /** @private */
15485 net.user1.orbiter.HTTPConnection.prototype.incomingCompleteListener = function (data) {
15486   if (this.disposed
15487       || this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15488       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15489     // Incoming request complete, but connection is closed. Ignore content.
15490     return;
15491   }
15492   
15493   // Don't immediately open a request in the complete handler due to Win IE bug
15494   var self = this;
15495   setTimeout(function () {
15496     self.processIncomingData(data);
15497     // A message listener might have closed this connection in response to an incoming
15498     // message. Do not open a new incoming request unless the connection is still open.
15499     if (self.disposed
15500         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15501         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15502       return;
15503     }
15504     self.openNewIncomingRequest();
15505   }, 0);
15506 }
15507 
15508 /** @private */
15509 net.user1.orbiter.HTTPConnection.prototype.incomingErrorListener = function () {
15510   if (this.disposed) return;
15511   // There's already a retry scheduled
15512   if (this.retryIncomingTimeoutID != -1) return;  
15513   // The connection has been closed
15514   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15515       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15516     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed."
15517                                 + " Connection is closed, so no retry scheduled."); 
15518     return;
15519   } 
15520 
15521   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed." 
15522                               + " Retrying in " +  this.retryDelay + "ms."); 
15523       
15524   // Retry
15525   var self = this;
15526   this.retryIncomingTimeoutID = setTimeout(function () {
15527     self.retryIncomingTimeoutID = -1;
15528     if (self.disposed
15529         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15530         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15531       return;
15532     }
15533     self.doRetryIncoming();
15534   }, this.retryDelay);
15535 }
15536     
15537 //==============================================================================    
15538 // PROCESS DATA FROM THE SERVER
15539 //==============================================================================
15540  
15541 /** @private */
15542 net.user1.orbiter.HTTPConnection.prototype.processIncomingData = function (data) {
15543   if (this.disposed) return;
15544   var listeners;
15545   
15546   this.dispatchReceiveData(data);
15547   
15548   var upcs = new Array();
15549   var upcEndTagIndex = data.indexOf("</U>");
15550   // Empty responses are valid.
15551   if (upcEndTagIndex == -1 && data.length > 0) {
15552     this.orbiter.getLog().error("Invalid message received. No UPC found: [" + data + "]");
15553     if (!this.isReady()) {
15554       // If invalid XML is received prior to achieving ready, then this
15555       // probably isn't a Union server, so disconnect.
15556       this.disconnect();
15557       return;
15558     }
15559   }
15560   
15561   while (upcEndTagIndex != -1) {
15562     upcs.push(data.substring(0, upcEndTagIndex+4));
15563     data = data.substring(upcEndTagIndex+4);
15564     upcEndTagIndex = data.indexOf("</U>");
15565   }
15566   for (var i = 0; i < upcs.length; i++) {
15567     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, upcs[i]));
15568   }
15569 }
15570 
15571 //==============================================================================    
15572 // SERVER AFFINITY
15573 //==============================================================================
15574 /** @private */ 
15575 net.user1.orbiter.HTTPConnection.prototype.applyAffinity = function (data) {
15576   net.user1.orbiter.Connection.prototype.applyAffinity.call(this);
15577   this.buildURL();
15578 };
15579 
15580 //==============================================================================    
15581 // TOSTRING
15582 //==============================================================================     
15583 net.user1.orbiter.HTTPConnection.prototype.toString = function () {
15584   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
15585           + ", host: " + (this.host == null ? "" : this.host) 
15586           + ", port: " + this.port 
15587           + ", send-delay: " + this.getSendDelay() + "]";
15588   return s;
15589 }
15590     
15591 // =============================================================================
15592 // DISPOSAL
15593 // =============================================================================
15594 /** @private */ 
15595 net.user1.orbiter.HTTPConnection.prototype.dispose = function () {
15596   this.doDispose();
15597   this.stopSendDelayTimer();
15598   net.user1.orbiter.Connection.prototype.dispose.call(this);
15599 }
15600 //==============================================================================    
15601 // CLASS DECLARATION
15602 //==============================================================================
15603 /**
15604  * @class
15605  *
15606  * The HTTPIFrameConnection class is used by Orbiter to communicate with
15607  * Union Server over HTTP in browsers that do not support CORS.
15608  * Rather than using CORS, HTTPIFrameConnection bypasses cross-origin restrictions
15609  * by proxying communications through a hidden HTML iframe.
15610  *
15611  * For a list of events dispatched by HTTPDirectConnection,
15612  * {@link net.user1.orbiter.Connection}.
15613  *
15614  * For more information on HTTP communication with Union Server, see
15615  * the HTTPDirectConnection class.
15616  *
15617  * @extends net.user1.orbiter.HTTPConnection
15618  *
15619  * @see net.user1.orbiter.HTTPDirectConnection
15620  * @see net.user1.orbiter.WebSocketConnection
15621  * @see net.user1.orbiter.SecureHTTPDirectConnection
15622  * @see net.user1.orbiter.SecureHTTPIFrameConnection
15623  * @see net.user1.orbiter.SecureWebSocketConnection
15624  */
15625 net.user1.orbiter.HTTPIFrameConnection = function (host, port, type) {
15626   // Invoke superclass constructor
15627   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15628   this.postMessageInited = false;
15629   this.iFrameReady = false;
15630 };
15631 
15632 //==============================================================================
15633 // INHERITANCE
15634 //==============================================================================
15635 net.user1.utils.extend(net.user1.orbiter.HTTPIFrameConnection, net.user1.orbiter.HTTPConnection);
15636 
15637 //==============================================================================    
15638 // POSTMESSAGE INITIALIZATION
15639 //==============================================================================   
15640 /** @private */ 
15641 net.user1.orbiter.HTTPIFrameConnection.prototype.initPostMessage = function () {
15642   if (this.postMessageInited) {
15643     throw new Error("[HTTPIFrameConnection] Illegal duplicate initialization attempt.");
15644   }
15645   var self = this;
15646   var win = this.orbiter.window;
15647   var errorMsg = null;
15648   
15649   if (win == null) {
15650     errorMsg = "[HTTPIFrameConnection] Unable to create connection." 
15651                + " No window object found.";
15652   } else {
15653     if (typeof win.addEventListener != "undefined") {
15654       // ...the standard way 
15655       win.addEventListener("message", postMessageListener, false);
15656     } else if (typeof win.attachEvent != "undefined") {
15657       // ...the IE-specific way 
15658       win.attachEvent("onmessage", postMessageListener);
15659     } else {
15660       errorMsg = "[HTTPIFrameConnection] Unable to create connection."
15661                + " No event listener registration method found on window object.";
15662     }
15663   }
15664   
15665   if (errorMsg != null) {
15666     this.orbiter.getLog().error(errorMsg);
15667     throw new Error(errorMsg);
15668   }
15669 
15670   /** @private */
15671   function postMessageListener (e) {
15672     // The connection's host might have been reassigned (normally to an ip) due
15673     // to server affinity in a clustered deployment, so allow for posts from both the
15674     // requestedHost and the host.
15675     if (e.origin.indexOf("//" + self.host + (self.port == 80 ? "" : (":" + self.port))) == -1
15676         && e.origin.indexOf("//" + self.requestedHost + (self.port == 80 ? "" : (":" + self.port))) == -1) {
15677       self.orbiter.getLog().error("[CONNECTION] " + self.toString()
15678         + " Ignored message from unknown origin: " + e.origin);
15679       return;
15680     }
15681     
15682     self.processPostMessage(e.data);
15683   }
15684   
15685   this.postMessageInited = true;
15686 };
15687 
15688 //==============================================================================    
15689 // IFRAME MANAGEMENT
15690 //==============================================================================    
15691 /** @private */
15692 net.user1.orbiter.HTTPIFrameConnection.prototype.makeIFrame = function () {
15693   if (typeof this.orbiter.window.document == "undefined") {
15694     var errorMsg = "[HTTPIFrameConnection] Unable to create connection."
15695                  + " No document object found.";
15696     this.orbiter.getLog().error(errorMsg);
15697     throw new Error(errorMsg);
15698   }
15699   var doc = this.orbiter.window.document;
15700   
15701   this.iFrameReady = false;
15702   if (this.iframe != null) {
15703     this.postToIFrame("dispose");
15704     doc.body.removeChild(this.iframe);
15705   }
15706   this.iframe = doc.createElement('iframe');
15707   this.iframe.width = "0px";
15708   this.iframe.height = "0px";
15709   this.iframe.border = "0px";
15710   this.iframe.frameBorder = "0";
15711   this.iframe.style.visibility = "hidden";
15712   this.iframe.style.display = "none";
15713   this.iframe.src = this.url + "/orbiter";
15714   doc.body.appendChild(this.iframe);
15715 }
15716 
15717 /** @private */
15718 net.user1.orbiter.HTTPIFrameConnection.prototype.onIFrameReady = function () {
15719   this.beginReadyHandshake();
15720 }
15721 
15722 /** @private */
15723 net.user1.orbiter.HTTPIFrameConnection.prototype.postToIFrame = function (cmd, data) {
15724   if (this.iframe && this.iFrameReady) {
15725     data = data == undefined ? "" : data;
15726     // In order to post to the iframe, the targetOrigin must match the iframe's origin
15727     this.iframe.contentWindow.postMessage(cmd + "," + data, this.iframe.contentWindow.location.href);
15728   }  
15729 }
15730 
15731 /** @private */
15732 net.user1.orbiter.HTTPIFrameConnection.prototype.processPostMessage = function (postedData) {
15733   var delimiterIndex = postedData.indexOf(",");
15734   var cmd  = postedData.substring(0, delimiterIndex);
15735   var data = postedData.substring(delimiterIndex+1);
15736   
15737   switch (cmd) {
15738     case"ready":
15739       this.iFrameReady = true;
15740       this.onIFrameReady();
15741       break;
15742       
15743     case "hellocomplete":
15744       this.helloCompleteListener(data);
15745       break;
15746     
15747     case "helloerror":
15748       this.helloErrorListener();
15749       break;
15750     
15751     case "outgoingcomplete":
15752       this.outgoingCompleteListener();
15753       break;
15754     
15755     case "outgoingerror":
15756       this.outgoingErrorListener();
15757       break;
15758     
15759     case "incomingcomplete":
15760       this.incomingCompleteListener(data);
15761       break;
15762     
15763     case "incomingerror":
15764       this.incomingErrorListener();
15765       break;
15766   }
15767 }
15768 
15769 //==============================================================================    
15770 // CONNECTION AND DISCONNECTION
15771 //==============================================================================    
15772 net.user1.orbiter.HTTPIFrameConnection.prototype.connect = function () {
15773   if (!this.postMessageInited) {
15774     this.initPostMessage();
15775   }
15776   
15777   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
15778   this.makeIFrame();
15779 };
15780 
15781 /** @private */
15782 net.user1.orbiter.HTTPIFrameConnection.prototype.doRequestDeactivation = function() {
15783   this.postToIFrame("deactivate");
15784 };
15785 
15786 //==============================================================================    
15787 // UPC LISTENERS (IFRAME-SPECIFIC IMPLEMENTATION)
15788 //==============================================================================
15789 
15790 /** @private */
15791 net.user1.orbiter.HTTPIFrameConnection.prototype.u66 = function (serverVersion, 
15792                                                            sessionID, 
15793                                                            upcVersion, 
15794                                                            protocolCompatible) {
15795   net.user1.orbiter.Connection.prototype.u66.call(this,
15796                                                   serverVersion,
15797                                                   sessionID,
15798                                                   upcVersion,
15799                                                   protocolCompatible);
15800   if (this.iframe != null) {
15801     this.postToIFrame("sessionid", sessionID);
15802   }
15803 }
15804 
15805 //==============================================================================    
15806 // HELLO REQUEST MANAGEMENT
15807 //==============================================================================  
15808 
15809 /** @private */
15810 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendHello = function (helloString) {
15811   this.postToIFrame("sendhello", helloString);
15812 };
15813 
15814 /** @private */
15815 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryHello = function () {
15816   this.postToIFrame("retryhello");
15817 }
15818 
15819 //==============================================================================    
15820 // OUTGOING REQUEST MANAGEMENT
15821 //==============================================================================
15822 
15823 /** @private */
15824 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendOutgoing = function (data) {
15825   this.postToIFrame("sendoutgoing", data);
15826 };
15827 
15828 /** @private */
15829 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryOutgoing = function () {
15830   this.postToIFrame("retryoutgoing");
15831 };
15832 
15833 //==============================================================================    
15834 // INCOMING REQUEST MANAGEMENT
15835 //==============================================================================  
15836 
15837 /** @private */
15838 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendIncoming = function () {
15839   this.postToIFrame("sendincoming");
15840 };
15841 
15842 /** @private */
15843 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryIncoming = function () {
15844   this.postToIFrame("retryincoming");
15845 };
15846     
15847 //==============================================================================    
15848 // TOSTRING
15849 //==============================================================================     
15850 net.user1.orbiter.HTTPIFrameConnection.prototype.toString = function () {
15851   var s = "[HTTPIFrameConnection, requested host: " + this.requestedHost 
15852           + ", host: " + (this.host == null ? "" : this.host) 
15853           + ", port: " + this.port 
15854           + ", send-delay: " + this.getSendDelay() + "]";
15855   return s;
15856 };
15857     
15858 //==============================================================================
15859 // DISPOSAL
15860 //==============================================================================
15861 /** @private */ 
15862 net.user1.orbiter.HTTPIFrameConnection.prototype.doDispose = function () {
15863   this.postToIFrame("dispose");
15864 };
15865 //==============================================================================    
15866 // CLASS DECLARATION
15867 //==============================================================================
15868 /**
15869  * @class
15870  * <p>
15871  * The HTTPDirectConnection class is used by Orbiter to communicate with
15872  * Union Server over HTTP; it uses CORS to bypass cross-origin restrictions
15873  * when Union Server is hosted on a domain that does not match the domain at
15874  * which the Orbiter client is hosted. Normally, developers need not use the
15875  * HTTPDirectConnection class directly, and should instead make connections
15876  * via the Orbiter class's connect() method. However, the
15877  * HTTPDirectConnection class is required for fine-grained connection configuration,
15878  * such as defining failover connections for multiple Union Servers
15879  * running at different host addresses.
15880  * </p>
15881  *
15882  * <p>
15883  * By default, Orbiter uses the WebSocketConnection class, not the
15884  * HTTPDirectConnection class, to communicate with Union Server. The
15885  * HTTPDirectConnection class is used as a backup connection
15886  * when the primary WebSocketConnection connection is blocked by a firewall.
15887  * However, on a very heavily loaded server with limited persistent socket
15888  * connections available, communicating with Union Server over HTTP--which uses
15889  * short-lived socket connections--can improve performance at the
15890  * expense of realtime responsiveness. To reduce server load when communicating
15891  * over HTTP, use HTTPDirectConnection's setSendDelay() method to decrease the
15892  * frequency of Orbiter's requests for updates from Union Server. Developers
15893  * that wish to use HTTP connections as the primary form of communication with
15894  * Union Server must do so by manually configuring connections via the
15895  * ConnectionManager class's addConnection() method.</p>
15896  *
15897  * <p>
15898  * In environments that do not support CORS (such as IE8 on Windows), Orbiter
15899  * conducts HTTP communications using HTTPIFrameConnection instead of HTTPDirectConnection.
15900  * </p>
15901  *
15902  * <p>
15903  * For secure HTTP and WebSocket communications, see SecureHTTPDirectConnection,
15904  * SecureHTTPIFrameConnection, and SecureWebSocketConnection.
15905  * </p>
15906  *
15907  *
15908  * For a list of events dispatched by HTTPDirectConnection,
15909  * {@link net.user1.orbiter.Connection}.
15910  *
15911  * @extends net.user1.orbiter.HTTPConnection
15912  *
15913  * @see net.user1.orbiter.Orbiter#connect
15914  * @see net.user1.orbiter.Orbiter#secureConnect
15915  *
15916  * @see net.user1.orbiter.SecureHTTPDirectConnection
15917  * @see net.user1.orbiter.SecureHTTPIFrameConnection
15918  * @see net.user1.orbiter.SecureWebSocketConnection
15919  */
15920 net.user1.orbiter.HTTPDirectConnection = function (host, port, type) {
15921   // Invoke superclass constructor
15922   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15923   
15924   this.outgoingRequestID = 0;
15925   this.incomingRequestID = 0;
15926   
15927   this.lastOutgoingPostData = null;
15928   this.lastIncomingPostData = null;
15929   this.lastHelloPostData    = null;
15930   
15931   this.pendingRequests = [];
15932 };
15933 
15934 //==============================================================================
15935 // INHERITANCE
15936 //==============================================================================
15937 net.user1.utils.extend(net.user1.orbiter.HTTPDirectConnection, net.user1.orbiter.HTTPConnection);
15938 
15939 
15940 //==============================================================================    
15941 // CONNECTION AND DISCONNECTION
15942 //==============================================================================    
15943 net.user1.orbiter.HTTPDirectConnection.prototype.connect = function () {
15944   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
15945   this.beginReadyHandshake();
15946 };
15947 
15948 //==============================================================================    
15949 // HELLO REQUEST MANAGEMENT
15950 //==============================================================================  
15951 
15952 /** @private Abstract method implementation */
15953 net.user1.orbiter.HTTPDirectConnection.prototype.doSendHello = function (helloString) {
15954   this.newHelloRequest(helloString);
15955 };
15956 
15957 /** @private Abstract method implementation */
15958 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryHello = function () {
15959   this.retryHello();
15960 }
15961 
15962 /** @private */
15963 net.user1.orbiter.HTTPDirectConnection.prototype.newHelloRequest = function (data) {
15964   this.lastHelloPostData = this.createHelloPostData(encodeURIComponent(data));
15965   this.transmitRequest(this.lastHelloPostData, 
15966                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
15967                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
15968 }
15969 
15970 /** @private */
15971 net.user1.orbiter.HTTPDirectConnection.prototype.createHelloPostData = function (data) {
15972   return "mode=d" + "&data=" + data;
15973 }
15974 
15975 /** @private */
15976 net.user1.orbiter.HTTPDirectConnection.prototype.retryHello = function () {
15977   this.transmitRequest(this.lastHelloPostData, 
15978                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
15979                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
15980 }
15981 
15982 /** @private */
15983 net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener = function (xhr, connection) {
15984   if (xhr.readyState == 4) {
15985     connection.removePendingRequest(xhr);
15986     if (xhr.status >= 200 && xhr.status <= 299) {
15987       connection.helloCompleteListener(xhr.responseText);
15988     } else {
15989       connection.helloErrorListener();
15990     }
15991   }
15992 }
15993 
15994 /** @private */
15995 net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener = function (xhr, connection) {
15996   connection.removePendingRequest(xhr);
15997   connection.helloErrorListener();
15998 }
15999 
16000 //==============================================================================    
16001 // OUTGOING REQUEST MANAGEMENT
16002 //==============================================================================
16003 
16004 /** @private Abstract method implementation */
16005 net.user1.orbiter.HTTPDirectConnection.prototype.doSendOutgoing = function (data) {
16006   this.newOutgoingRequest(data);
16007 };
16008 
16009 /** @private Abstract method implementation */
16010 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryOutgoing = function () {
16011   this.retryOutgoing();
16012 };
16013 
16014 /** @private */
16015 net.user1.orbiter.HTTPDirectConnection.prototype.newOutgoingRequest = function (data) {
16016   this.lastOutgoingPostData = this.createOutgoingPostData(encodeURIComponent(data));
16017   this.transmitRequest(this.lastOutgoingPostData, 
16018                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
16019                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
16020 }
16021 
16022 /** @private */
16023 net.user1.orbiter.HTTPDirectConnection.prototype.createOutgoingPostData = function (data) {
16024   this.outgoingRequestID++;
16025   return "rid=" + this.outgoingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=s" + "&data=" + data;
16026 }
16027 
16028 /** @private */
16029 net.user1.orbiter.HTTPDirectConnection.prototype.retryOutgoing = function () {
16030   this.transmitRequest(this.lastOutgoingPostData, 
16031                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
16032                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
16033 }
16034 
16035 /** @private */
16036 net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener = function (xhr, connection) {
16037   if (xhr.readyState == 4) {
16038     connection.removePendingRequest(xhr);
16039     if (xhr.status >= 200 && xhr.status <= 299) {
16040       connection.outgoingCompleteListener();
16041     } else {
16042       connection.outgoingErrorListener();
16043     }
16044   }
16045 }
16046 
16047 /** @private */
16048 net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener = function (xhr, connection) {
16049   connection.removePendingRequest(xhr);
16050   connection.outgoingErrorListener();
16051 }
16052 
16053 //==============================================================================    
16054 // INCOMING REQUEST MANAGEMENT
16055 //==============================================================================  
16056 
16057 /** @private Abstract method implementation */
16058 net.user1.orbiter.HTTPDirectConnection.prototype.doSendIncoming = function () {
16059   this.newIncomingRequest();
16060 };
16061 
16062 /** @private Abstract method implementation */
16063 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryIncoming = function () {
16064   this.retryIncoming();
16065 };
16066 
16067 /** @private */ 
16068 net.user1.orbiter.HTTPDirectConnection.prototype.newIncomingRequest = function () {
16069   this.lastIncomingPostData = this.createIncomingPostData();
16070   this.transmitRequest(this.lastIncomingPostData,
16071                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
16072                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
16073 }
16074 
16075 /** @private */ 
16076 net.user1.orbiter.HTTPDirectConnection.prototype.createIncomingPostData = function () {
16077   this.incomingRequestID++;
16078   return "rid=" + this.incomingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=c";
16079 }
16080 
16081 /** @private */ 
16082 net.user1.orbiter.HTTPDirectConnection.prototype.retryIncoming = function () {
16083   this.transmitRequest(this.lastIncomingPostData,
16084                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
16085                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
16086 }
16087 
16088 /** @private */ 
16089 net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener = function (xhr, connection) {
16090   if (xhr.readyState == 4) {
16091     connection.removePendingRequest(xhr);
16092     if (xhr.status >= 200 && xhr.status <= 299) {
16093       connection.incomingCompleteListener(xhr.responseText);
16094     } else {
16095       connection.incomingErrorListener();
16096     }
16097   }
16098 }
16099 
16100 /** @private */ 
16101 net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener = function (xhr, connection) {
16102   connection.removePendingRequest(xhr);
16103   connection.incomingErrorListener();
16104 }
16105 
16106 //==============================================================================
16107 // XHR MANAGEMENT
16108 //==============================================================================
16109 /** @private */
16110 net.user1.orbiter.HTTPDirectConnection.prototype.transmitRequest = function (data, 
16111                                                       readystatechangeListener, 
16112                                                       errorListener) {
16113   var self = this;
16114   var request;
16115   
16116   if (typeof XDomainRequest != "undefined") {
16117     // IE
16118     request = new XDomainRequest();
16119     request.onload = function () {
16120       request.readyState = 4;  // Emulate standards-based API
16121       request.status = 200;
16122       readystatechangeListener(this, self)
16123     };
16124     request.onerror = function () {
16125       errorListener(this, self);
16126     };
16127     request.ontimeout = function () {
16128       errorListener(this, self);
16129     };
16130     request.onprogress = function () {}; // Do nothing (required)
16131   } else {
16132     // All other standards-based browsers
16133     var request = new XMLHttpRequest();
16134     this.pendingRequests.push(request);
16135     request.onreadystatechange = function () {
16136       readystatechangeListener(this, self);
16137     };
16138     request.onerror = function () {
16139       errorListener(this, self);
16140     };
16141   }
16142   // Call open before setting header
16143   request.open("POST", this.url);
16144   // Standards-based browsers (IE doesn't allow the setting of headers)
16145   if (typeof request.setRequestHeader != "undefined") {
16146     request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
16147   }
16148   request.send(data);
16149 }
16150 
16151 /** @private */
16152 net.user1.orbiter.HTTPDirectConnection.prototype.removePendingRequest = function (request) {
16153   for (var i = this.pendingRequests.length; --i >= 0; ) {
16154     if (this.pendingRequests[i] === request) {
16155       this.pendingRequests.splice(i, 1);
16156     }
16157   }
16158 }
16159 
16160 /** @private Abstract method implementation */
16161 net.user1.orbiter.HTTPDirectConnection.prototype.doRequestDeactivation = function () {
16162   for (var i = this.pendingRequests.length; --i >= 0;) {
16163     try {
16164       this.pendingRequests[i].abort();
16165     } catch (e) {
16166       // Do nothing
16167     }
16168   }
16169   this.pendingRequests = [];
16170 }
16171     
16172 //==============================================================================    
16173 // TOSTRING
16174 //==============================================================================     
16175 net.user1.orbiter.HTTPDirectConnection.prototype.toString = function () {
16176   var s = "[HTTPDirectConnection, requested host: " + this.requestedHost 
16177           + ", host: " + (this.host == null ? "" : this.host) 
16178           + ", port: " + this.port 
16179           + ", send-delay: " + this.getSendDelay() + "]";
16180   return s;
16181 };
16182     
16183 //==============================================================================
16184 // DISPOSAL
16185 //==============================================================================
16186 /** @private */ 
16187 net.user1.orbiter.HTTPDirectConnection.prototype.doDispose = function () {
16188   this.deactivateHTTPRequests();
16189 };
16190 //==============================================================================    
16191 // CLASS DECLARATION
16192 //==============================================================================
16193 /** @class
16194  * <p>
16195  * The SecureHTTPDirectConnection class is identical to HTTPDirectConnection
16196  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
16197  * SSL connection) rather than plain HTTP.</p>
16198  *
16199  * For a list of events dispatched by SecureHTTPDirectConnection, see
16200  * {@link net.user1.orbiter.Connection}.
16201  *
16202  * @extends net.user1.orbiter.HTTPDirectConnection
16203  *
16204  * @see net.user1.orbiter.HTTPDirectConnection
16205  */
16206 net.user1.orbiter.SecureHTTPDirectConnection = function (host, port) {
16207   // Invoke superclass constructor
16208   net.user1.orbiter.HTTPDirectConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
16209 };
16210 
16211 //==============================================================================
16212 // INHERITANCE
16213 //==============================================================================
16214 net.user1.utils.extend(net.user1.orbiter.SecureHTTPDirectConnection, net.user1.orbiter.HTTPDirectConnection);
16215 
16216 /** @private */
16217 net.user1.orbiter.SecureHTTPDirectConnection.prototype.buildURL = function () {
16218   this.url = "https://" + this.host + ":" + this.port;
16219 };
16220 
16221 //==============================================================================
16222 // TOSTRING
16223 //==============================================================================
16224 net.user1.orbiter.SecureHTTPDirectConnection.prototype.toString = function () {
16225   var s = "[SecureHTTPDirectConnection, requested host: " + this.requestedHost
16226           + ", host: " + (this.host == null ? "" : this.host)
16227           + ", port: " + this.port
16228           + ", send-delay: " + this.getSendDelay() + "]";
16229   return s;
16230 };
16231 //==============================================================================    
16232 // CLASS DECLARATION
16233 //==============================================================================
16234 /**
16235  * @class
16236  *
16237  * <p>
16238  * The SecureHTTPIFrameConnection class is identical to HTTPIFrameConnection
16239  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
16240  * SSL connection) rather than plain HTTP.</p>
16241  *
16242  * For a list of events dispatched by SecureHTTPIFrameConnection,
16243  * see {@link net.user1.orbiter.Connection}.
16244  *
16245  * @extends net.user1.orbiter.HTTPIFrameConnection
16246  *
16247  * @see net.user1.orbiter.HTTPIFrameConnection
16248  */
16249 net.user1.orbiter.SecureHTTPIFrameConnection = function (host, port) {
16250   // Invoke superclass constructor
16251   net.user1.orbiter.HTTPIFrameConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
16252 };
16253 
16254 //==============================================================================
16255 // INHERITANCE
16256 //==============================================================================
16257 net.user1.utils.extend(net.user1.orbiter.SecureHTTPIFrameConnection, net.user1.orbiter.HTTPIFrameConnection);
16258 
16259 /** @private */
16260 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.buildURL = function () {
16261   this.url = "https://" + this.host + ":" + this.port;
16262 };
16263 
16264 //==============================================================================
16265 // TOSTRING
16266 //==============================================================================
16267 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.toString = function () {
16268   var s = "[SecureHTTPIFrameConnection, requested host: " + this.requestedHost
16269           + ", host: " + (this.host == null ? "" : this.host)
16270           + ", port: " + this.port
16271           + ", send-delay: " + this.getSendDelay() + "]";
16272   return s;
16273 };
16274 //==============================================================================
16275 // CLASS DECLARATION
16276 //==============================================================================
16277 /** @private */
16278 net.user1.orbiter.MessageListener = function (listener,
16279                                               forRoomIDs,
16280                                               thisArg) {
16281   this.listener   = listener;
16282   this.forRoomIDs = forRoomIDs;
16283   this.thisArg    = thisArg;
16284 };
16285 
16286 //==============================================================================
16287 // INSTANCE METHODS
16288 //==============================================================================
16289 /** @private */
16290 net.user1.orbiter.MessageListener.prototype.getListenerFunction = function () {
16291   return this.listener;
16292 };
16293     
16294 /** @private */
16295 net.user1.orbiter.MessageListener.prototype.getForRoomIDs = function () {
16296   return this.forRoomIDs;
16297 };
16298     
16299 /** @private */
16300 net.user1.orbiter.MessageListener.prototype.getThisArg = function () {
16301   return this.thisArg;
16302 };
16303 
16304 /** @private */
16305 net.user1.orbiter.MessageListener.prototype.toString = function () {
16306   return "[object MessageListener]";
16307 };
16308 //==============================================================================
16309 // CLASS DECLARATION
16310 //==============================================================================
16311 /** @class */
16312 net.user1.orbiter.MessageManager = function (log, connectionManager) {
16313   this.log = log;
16314   this.messageListeners = new Object();
16315   this.removeListenersOnDisconnect = true;
16316   this.numMessagesSent = 0;
16317   this.numMessagesReceived = 0;
16318   this.currentConnection = null;
16319   this.connectionManager = connectionManager;
16320   this.connectionManager.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
16321                                           this.selectConnectionListener, this);
16322 };
16323 
16324 //==============================================================================
16325 // INSTANCE METHODS
16326 //==============================================================================
16327 net.user1.orbiter.MessageManager.prototype.getNumMessagesReceived = function () {
16328   return this.numMessagesReceived;
16329 }
16330   
16331 net.user1.orbiter.MessageManager.prototype.getNumMessagesSent = function () {
16332   return this.numMessagesSent;
16333 }
16334   
16335 net.user1.orbiter.MessageManager.prototype.getTotalMessages = function () {
16336   return this.numMessagesSent + this.numMessagesReceived;
16337 }
16338   
16339 /** @private */
16340 net.user1.orbiter.MessageManager.prototype.selectConnectionListener = function (e) {
16341   if (this.currentConnection != null) {
16342     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
16343                                           this.upcReceivedListener, this);
16344     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
16345                                           this.disconnectListener, this);
16346     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
16347                                           this.connectFailureListener, this);
16348   }
16349 
16350   this.currentConnection = e.getConnection(); 
16351 
16352   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
16353                                         this.upcReceivedListener, this);
16354   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
16355                                         this.disconnectListener, this);
16356   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
16357                                         this.connectFailureListener, this);
16358 }
16359   
16360 /** @private */
16361 net.user1.orbiter.MessageManager.prototype.disconnectListener = function (e) {
16362   this.cleanupAfterClosedConnection(e.target);
16363 }
16364     
16365 /** @private */
16366 net.user1.orbiter.MessageManager.prototype.connectFailureListener = function (e) {
16367   this.cleanupAfterClosedConnection(e.target);
16368 }
16369     
16370 /** @private */
16371 net.user1.orbiter.MessageManager.prototype.cleanupAfterClosedConnection = function (connection) {
16372   var listenerList;
16373   if (this.removeListenersOnDisconnect) {
16374     this.log.info("[MESSAGE_MANAGER] Removing registered message listeners.");
16375     for (var message in this.messageListeners) {
16376       listenerList = this.messageListeners[message];
16377       for (var p in listenerList) {
16378         this.removeMessageListener(message, listenerList[p].getListenerFunction());
16379       } 
16380     }
16381   } else {
16382     this.log.warn("[MESSAGE_MANAGER] Leaving message listeners registered. \n"
16383       + "Be sure to remove any unwanted message listeners manually.");
16384   }
16385   
16386   this.numMessagesReceived = 0;
16387   this.numMessagesSent = 0;
16388 }
16389   
16390 net.user1.orbiter.MessageManager.prototype.sendUPC = function (message) {
16391   // Quit if the connection isn't ready...
16392   if (!this.connectionManager.isReady()) {
16393     this.log.warn("[MESSAGE_MANAGER] Connection not ready. UPC not sent. Message: " 
16394     + message);
16395     return;
16396   }
16397 
16398   // Build the UPC to send.
16399   var theUPC = "<U><M>" + message + "</M>";
16400   var a;
16401   
16402   if (arguments.length > 1) {
16403     theUPC += "<L>";
16404     for (var i = 1; i < arguments.length; i++) {
16405       a = arguments[i];
16406       a = a == undefined ? "" : a.toString();
16407       // Wrap any non-filter argument that contains a start tag ("<") in CDATA
16408       if (a.indexOf("<") != -1) {
16409         if (a.indexOf('<f t=') != 0) {
16410           a = "<![CDATA[" + a + "]]>";
16411         }
16412       }
16413       theUPC += "<A>" + a + "</A>";
16414     }
16415     theUPC += "</L>";
16416   }
16417   theUPC += "</U>";
16418 
16419   // Count the message
16420   this.numMessagesSent++;
16421   
16422   // Send the UPC to the server
16423   this.log.debug("[MESSAGE_MANAGER] UPC sent: " + theUPC);
16424   this.connectionManager.getActiveConnection().send(theUPC);
16425 };
16426 
16427 /** @private */
16428 net.user1.orbiter.MessageManager.prototype.sendUPCObject = function (upc) {
16429   var args = upc.args.slice();
16430   args.unshift(upc.method);
16431   this.sendUPC.apply(this, args);
16432 };
16433 
16434 /** @private */
16435 net.user1.orbiter.MessageManager.prototype.upcReceivedListener = function (e) {
16436   this.numMessagesReceived++;
16437   
16438   var upc = e.getUPC();
16439   this.log.debug("[MESSAGE_MANAGER] UPC received: " + upc );
16440   
16441   var method;
16442   var upcArgs = new Array();
16443   
16444   var closeMTagIndex = upc.indexOf("</M>");
16445   method = upc.substring(6, closeMTagIndex);
16446   
16447   var searchBeginIndex = upc.indexOf("<A>", closeMTagIndex);
16448   var closeATagIndex;
16449   var arg;
16450   while (searchBeginIndex != -1) {
16451     closeATagIndex = upc.indexOf("</A>", searchBeginIndex);
16452     arg = upc.substring(searchBeginIndex+3, closeATagIndex);
16453     if (arg.indexOf("<![CDATA[") == 0) {
16454       arg = arg.substr(9, arg.length-12);
16455     }
16456     upcArgs.push(arg);
16457     searchBeginIndex = upc.indexOf("<A>", closeATagIndex);
16458   }     
16459   
16460   this.notifyMessageListeners(method, upcArgs);
16461 };
16462 
16463 net.user1.orbiter.MessageManager.prototype.addMessageListener = function (message, 
16464                                                                           listener,
16465                                                                           thisArg,
16466                                                                           forRoomIDs) {
16467   if (forRoomIDs != null) {
16468     var typeString = Object.prototype.toString.call(forRoomIDs);
16469     if (typeString != "[object Array]") {
16470       throw new Error("[MESSAGE_MANAGER] Illegal argument type " + typeString
16471                       + " supplied for addMessageListener()'s forRoomIDs"
16472                       + " parameter. Value must be an Array.");
16473     }
16474   }
16475   
16476   // Each message gets a list of MessageListener objects. 
16477   // If this message has no such list, make one.
16478   if (this.messageListeners[message] === undefined) {
16479     this.messageListeners[message] = new Array();
16480   } 
16481   var listenerArray = this.messageListeners[message];
16482   
16483   // Quit if the listener is already registered
16484   if (this.hasMessageListener(message, listener)) {
16485     return false;
16486   }
16487   
16488   // Add the listener
16489   var newListener = new net.user1.orbiter.MessageListener(listener,
16490                                             forRoomIDs === undefined ? null : forRoomIDs,
16491                                             thisArg);
16492   listenerArray.push(newListener);
16493   return true;      
16494 };
16495 
16496 net.user1.orbiter.MessageManager.prototype.removeMessageListener = function (message,
16497                                                            listener) {
16498   // Quit if the message has no listeners
16499   var listenerArray = this.messageListeners[message];
16500   if (listenerArray == null) {
16501     return false;
16502   } 
16503   
16504   // Remove the listener
16505   var foundListener;
16506   for (var i = 0; i < listenerArray.length; i++) {
16507     if (listenerArray[i].getListenerFunction() == listener) {
16508       foundListener = true;
16509       listenerArray.splice(i, 1);
16510       break;
16511     }
16512   }
16513   
16514   // Delete the listeners array if it's now empty
16515   if (listenerArray.length == 0) {
16516     delete this.messageListeners[message];
16517   }
16518   
16519   return foundListener;      
16520 };
16521     
16522 net.user1.orbiter.MessageManager.prototype.hasMessageListener = function (message, 
16523                                                         listener) {
16524   // Quit if the message has no listeners
16525   var listenerArray = this.messageListeners[message];
16526   if (listenerArray == null) {
16527     return false;
16528   } 
16529       
16530    // Check for the listener
16531   for (var i = 0; i < listenerArray.length; i++) {
16532     if (listenerArray[i].getListenerFunction() 
16533       == listener) {
16534       return true;
16535     }
16536   }
16537   return false;
16538 };
16539     
16540 net.user1.orbiter.MessageManager.prototype.getMessageListeners = function (message) {
16541   return this.messageListeners[message] != undefined ? this.messageListeners[message] : [];
16542 };
16543 
16544 /** @private */
16545 net.user1.orbiter.MessageManager.prototype.notifyMessageListeners = function (message, args) {
16546   // Retrieve the list of listeners for this message.
16547   var listeners = this.messageListeners[message];
16548   // If there are no listeners registered, then quit
16549   if (listeners === undefined) {
16550     // Log a warning if it's not a UPC
16551     if (!(message.charAt(0) == "u" && parseInt(message.substring(1)) > 1)) {
16552       this.log.warn("Message delivery failed. No listeners found. Message: " + 
16553                message + ". Arguments: " + args.join());
16554     }
16555     return;
16556   } else {
16557     listeners = listeners.slice(0);    
16558   }
16559   var numListeners = listeners.length; 
16560   for (var i = 0; i < numListeners; i++) {
16561     listeners[i].getListenerFunction().apply(listeners[i].getThisArg(), args);
16562   }
16563 };
16564 
16565 net.user1.orbiter.MessageManager.prototype.dispose = function () {
16566   this.log.info("[MESSAGE_MANAGER] Disposing resources.");
16567   this.log = null;
16568   this.orbiter = null;
16569   this.messageListeners = null;
16570   this.numMessagesSent = 0;
16571   this.numMessagesReceived = 0;
16572   this.currentConnection = null;
16573 }
16574   
16575 net.user1.orbiter.MessageManager.prototype.toString = function () {
16576   return "[object MessageManager]";
16577 };
16578 
16579 //==============================================================================
16580 // UPC CONSTANTS
16581 //==============================================================================
16582 /** @class */
16583 net.user1.orbiter.UPC = new Object();
16584 
16585 // CLIENT TO SERVER
16586 /** @constant */
16587 net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS = "u1";            
16588 /** @constant */
16589 net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS = "u2";            
16590 /** @constant */
16591 net.user1.orbiter.UPC.SET_CLIENT_ATTR = "u3";       
16592 /** @constant */
16593 net.user1.orbiter.UPC.JOIN_ROOM = "u4";             
16594 /** @constant */
16595 net.user1.orbiter.UPC.SET_ROOM_ATTR = "u5";         
16596 /** @constant */
16597 net.user1.orbiter.UPC.LEAVE_ROOM = "u10";           
16598 /** @constant */
16599 net.user1.orbiter.UPC.CREATE_ACCOUNT = "u11"; 
16600 /** @constant */
16601 net.user1.orbiter.UPC.REMOVE_ACCOUNT = "u12";
16602 /** @constant */
16603 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD = "u13";
16604 /** @constant */
16605 net.user1.orbiter.UPC.LOGIN = "u14";            
16606 /** @constant */
16607 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT = "u18";                
16608 /** @constant */
16609 net.user1.orbiter.UPC.SYNC_TIME = "u19";
16610 /** @constant */
16611 net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT = "u21";
16612 /** @constant */
16613 net.user1.orbiter.UPC.CREATE_ROOM = "u24";                       
16614 /** @constant */
16615 net.user1.orbiter.UPC.REMOVE_ROOM = "u25";                       
16616 /** @constant */
16617 net.user1.orbiter.UPC.WATCH_FOR_ROOMS = "u26";            
16618 /** @constant */
16619 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS = "u27"; 
16620 /** @constant */
16621 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT = "u55";
16622 /** @constant */
16623 net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER = "u57"; 
16624 /** @constant */
16625 net.user1.orbiter.UPC.OBSERVE_ROOM = "u58"; 
16626 /** @constant */
16627 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM = "u61"; 
16628 /** @constant */
16629 net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS = "u64"; 
16630 /** @constant */
16631 net.user1.orbiter.UPC.CLIENT_HELLO = "u65"; 
16632 /** @constant */
16633 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR = "u67"; 
16634 /** @constant */
16635 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR = "u69"; 
16636 /** @constant */
16637 net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE = "u70"; 
16638 /** @constant */
16639 net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE = "u71"; 
16640 /** @constant */
16641 net.user1.orbiter.UPC.TERMINATE_SESSION = "u83";
16642 /** @constant */
16643 net.user1.orbiter.UPC.LOGOFF = "u86";  
16644 /** @constant */
16645 net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT = "u91";  
16646 /** @constant */
16647 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS = "u92";  
16648 /** @constant */
16649 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS = "u93";  
16650 /** @constant */
16651 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT = "u94";  
16652 /** @constant */
16653 net.user1.orbiter.UPC.OBSERVE_CLIENT = "u95";  
16654 /** @constant */
16655 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT = "u96";  
16656 /** @constant */
16657 net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT = "u97";  
16658 /** @constant */
16659 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS = "u98";  
16660 /** @constant */
16661 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS = "u99";  
16662 /** @constant */
16663 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT = "u100";  
16664 /** @constant */
16665 net.user1.orbiter.UPC.OBSERVE_ACCOUNT = "u121";  
16666 /** @constant */
16667 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT = "u122"; 
16668 /** @constant */
16669 net.user1.orbiter.UPC.ADD_ROLE = "u133";  
16670 /** @constant */
16671 net.user1.orbiter.UPC.REMOVE_ROLE = "u135";  
16672 /** @constant */
16673 net.user1.orbiter.UPC.KICK_CLIENT = "u149";  
16674 /** @constant */
16675 net.user1.orbiter.UPC.BAN = "u137";  
16676 /** @constant */
16677 net.user1.orbiter.UPC.UNBAN = "u139";  
16678 /** @constant */
16679 net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT = "u141";  
16680 /** @constant */
16681 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES = "u143";  
16682 /** @constant */
16683 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES = "u145";  
16684 /** @constant */
16685 net.user1.orbiter.UPC.GET_NODELIST_SNAPSHOT = "u165";  
16686 /** @constant */
16687 net.user1.orbiter.UPC.GET_GATEWAYS_SNAPSHOT = "u167";
16688 
16689 // SERVER TO CLIENT
16690 /** @constant */
16691 net.user1.orbiter.UPC.JOINED_ROOM = "u6";
16692 /** @constant */
16693 net.user1.orbiter.UPC.RECEIVE_MESSAGE = "u7";
16694 /** @constant */
16695 net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE = "u8";
16696 /** @constant */
16697 net.user1.orbiter.UPC.ROOM_ATTR_UPDATE = "u9";
16698 /** @constant */
16699 net.user1.orbiter.UPC.CLIENT_METADATA = "u29";
16700 /** @constant */
16701 net.user1.orbiter.UPC.CREATE_ROOM_RESULT = "u32";
16702 /** @constant */
16703 net.user1.orbiter.UPC.REMOVE_ROOM_RESULT = "u33";
16704 /** @constant */
16705 net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT = "u34";
16706 /** @constant */
16707 net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM = "u36";
16708 /** @constant */
16709 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM = "u37";
16710 /** @constant */
16711 net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT = "u38";
16712 /** @constant */
16713 net.user1.orbiter.UPC.ROOM_ADDED = "u39";
16714 /** @constant */
16715 net.user1.orbiter.UPC.ROOM_REMOVED = "u40";
16716 /** @constant */
16717 net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT = "u42";
16718 /** @constant */
16719 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT = "u43";
16720 /** @constant */
16721 net.user1.orbiter.UPC.LEFT_ROOM = "u44";
16722 /** @constant */
16723 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT = "u46";
16724 /** @constant */
16725 net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT = "u47";
16726 /** @constant */
16727 net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT = "u48";
16728 /** @constant */
16729 net.user1.orbiter.UPC.LOGIN_RESULT = "u49";
16730 /** @constant */
16731 net.user1.orbiter.UPC.SERVER_TIME_UPDATE = "u50";
16732 /** @constant */
16733 net.user1.orbiter.UPC.ROOM_SNAPSHOT = "u54";
16734 /** @constant */
16735 net.user1.orbiter.UPC.OBSERVED_ROOM = "u59";
16736 /** @constant */
16737 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT = "u60";
16738 /** @constant */
16739 net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM = "u62";
16740 /** @constant */
16741 net.user1.orbiter.UPC.CLIENT_READY = "u63";
16742 /** @constant */
16743 net.user1.orbiter.UPC.SERVER_HELLO = "u66";
16744 /** @constant */
16745 net.user1.orbiter.UPC.JOIN_ROOM_RESULT = "u72";
16746 /** @constant */
16747 net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT = "u73";
16748 /** @constant */
16749 net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT = "u74";
16750 /** @constant */
16751 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT = "u75";
16752 /** @constant */
16753 net.user1.orbiter.UPC.LEAVE_ROOM_RESULT = "u76";
16754 /** @constant */
16755 net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT = "u77";
16756 /** @constant */
16757 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT = "u78";
16758 /** @constant */
16759 net.user1.orbiter.UPC.ROOM_ATTR_REMOVED = "u79";
16760 /** @constant */
16761 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT = "u80";
16762 /** @constant */
16763 net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED = "u81";
16764 /** @constant */
16765 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT = "u82";
16766 /** @constant */
16767 net.user1.orbiter.UPC.SESSION_TERMINATED = "u84";
16768 /** @constant */
16769 net.user1.orbiter.UPC.SESSION_NOT_FOUND = "u85";
16770 /** @constant */
16771 net.user1.orbiter.UPC.LOGOFF_RESULT = "u87";
16772 /** @constant */
16773 net.user1.orbiter.UPC.LOGGED_IN = "u88";
16774 /** @constant */
16775 net.user1.orbiter.UPC.LOGGED_OFF = "u89";
16776 /** @constant */
16777 net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED = "u90";
16778 /** @constant */
16779 net.user1.orbiter.UPC.CLIENTLIST_SNAPSHOT = "u101";
16780 /** @constant */
16781 net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER = "u102";
16782 /** @constant */
16783 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER = "u103";
16784 /** @constant */
16785 net.user1.orbiter.UPC.CLIENT_SNAPSHOT = "u104";
16786 /** @constant */
16787 net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT = "u105";
16788 /** @constant */
16789 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT = "u106";
16790 /** @constant */
16791 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT = "u107";
16792 /** @constant */
16793 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT = "u108";
16794 /** @constant */
16795 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT = "u109";
16796 /** @constant */
16797 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "u110";
16798 /** @constant */
16799 net.user1.orbiter.UPC.ACCOUNT_ADDED = "u111";
16800 /** @constant */
16801 net.user1.orbiter.UPC.ACCOUNT_REMOVED = "u112";
16802 /** @constant */
16803 net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT = "u113";
16804 /** @constant */
16805 net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT = "u114";
16806 /** @constant */
16807 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT = "u115";
16808 /** @constant */
16809 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT = "u116";
16810 /** @constant */
16811 net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT = "u117";
16812 /** @constant */
16813 net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT = "u118";
16814 /** @constant */
16815 net.user1.orbiter.UPC.CLIENT_OBSERVED = "u119";
16816 /** @constant */
16817 net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT = "u120";
16818 /** @constant */
16819 net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT = "u123";
16820 /** @constant */
16821 net.user1.orbiter.UPC.ACCOUNT_OBSERVED = "u124";
16822 /** @constant */
16823 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT = "u125";
16824 /** @constant */
16825 net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT = "u126";
16826 /** @constant */
16827 net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE = "u127";
16828 /** @constant */
16829 net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE = "u128";
16830 /** @constant */
16831 net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM = "u129";
16832 /** @constant */
16833 net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM = "u130";
16834 /** @constant */
16835 net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE = "u131";
16836 /** @constant */
16837 net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE = "u132";
16838 /** @constant */
16839 net.user1.orbiter.UPC.ADD_ROLE_RESULT = "u134";
16840 /** @constant */
16841 net.user1.orbiter.UPC.REMOVE_ROLE_RESULT = "u136";
16842 /** @constant */
16843 net.user1.orbiter.UPC.BAN_RESULT = "u138";
16844 /** @constant */
16845 net.user1.orbiter.UPC.UNBAN_RESULT = "u140";
16846 /** @constant */
16847 net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT = "u142";
16848 /** @constant */
16849 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT = "u144";
16850 /** @constant */
16851 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "u146";
16852 /** @constant */
16853 net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED = "u147";
16854 /** @constant */
16855 net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED = "u148";
16856 /** @constant */
16857 net.user1.orbiter.UPC.KICK_CLIENT_RESULT = "u150";
16858 /** @constant */
16859 net.user1.orbiter.UPC.SERVERMODULELIST_SNAPSHOT = "u152";
16860 /** @constant */
16861 net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT_RESULT = "u155";
16862 /** @constant */
16863 net.user1.orbiter.UPC.UPC_STATS_SNAPSHOT = "u156";
16864 /** @constant */
16865 net.user1.orbiter.UPC.RESET_UPC_STATS_RESULT = "u158";
16866 /** @constant */
16867 net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS_RESULT = "u160";
16868 /** @constant */
16869 net.user1.orbiter.UPC.PROCESSED_UPC_ADDED = "u161";
16870 /** @constant */
16871 net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT = "u163";
16872 /** @constant */
16873 net.user1.orbiter.UPC.CONNECTION_REFUSED = "u164";
16874 /** @constant */
16875 net.user1.orbiter.UPC.NODELIST_SNAPSHOT = "u166";
16876 /** @constant */
16877 net.user1.orbiter.UPC.GATEWAYS_SNAPSHOT = "u168";
16878 //==============================================================================
16879 // LOADED FLAG
16880 //==============================================================================
16881 /** 
16882  * @constant 
16883  * 
16884  * Indicates that Orbiter has finished loading.
16885  */
16886 net.user1.orbiter.LOADED = true;
16887 
16888 })((typeof window == "undefined") ? this : window);
16889