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,19);
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 /** @constant */
1073 net.user1.orbiter.ConnectionManagerEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
1074   
1075 //==============================================================================
1076 // INSTANCE METHODS
1077 //==============================================================================
1078 
1079 net.user1.orbiter.ConnectionManagerEvent.prototype.getConnection = function () {
1080   return this.connection;
1081 }
1082 
1083 net.user1.orbiter.ConnectionManagerEvent.prototype.getStatus = function () {
1084   return this.status;
1085 }
1086 
1087 net.user1.orbiter.ConnectionManagerEvent.prototype.toString = function () {
1088   return "[object ConnectionManagerEvent]";
1089 }  
1090 
1091 //==============================================================================
1092 // CLASS DECLARATION
1093 //==============================================================================
1094 /** @class
1095 
1096 The ConnectionManager class dispatches the following events:
1097 
1098 <ul class="summary">
1099 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT}</li>
1100 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION}</li>
1101 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE}</li>
1102 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.DISCONNECT}</li>
1103 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT}</li>
1104 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT}</li>
1105 <li class="fixedFont">{@link net.user1.orbiter.ConnectionManagerEvent.READY}</li>
1106 </ul>
1107 
1108 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
1109 
1110     @extends net.user1.events.EventDispatcher
1111 
1112  * @see net.user1.orbiter.Orbiter#connect
1113  */
1114 net.user1.orbiter.ConnectionManager = function (orbiter) {
1115     // Call superconstructor
1116     net.user1.events.EventDispatcher.call(this);
1117     
1118     // Variables
1119     this.orbiter             = orbiter;
1120     this.connectAttemptCount = 0;
1121     this.connectAbortCount   = 0;
1122     this.readyCount          = 0;      
1123     this.connectFailedCount  = 0;
1124     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1125     this.readyTimeout        = 0;
1126     this.connections         = new Array();
1127     this.activeConnection    = null;
1128     this.inProgressConnection = null;
1129     this.currentConnectionIndex = 0;
1130     this.attemptedConnections = null;
1131     this.setReadyTimeout(net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT);
1132     
1133     // Initialization
1134     // Make all Orbiter instances in this VM share the same server affinity 
1135     this.setGlobalAffinity(true);  
1136 };
1137     
1138 //==============================================================================    
1139 // INHERITANCE
1140 //============================================================================== 
1141 net.user1.utils.extend(net.user1.orbiter.ConnectionManager, net.user1.events.EventDispatcher);
1142 
1143 //==============================================================================
1144 // STATIC VARIABLES
1145 //==============================================================================
1146 net.user1.orbiter.ConnectionManager.DEFAULT_READY_TIMEOUT = 10000;
1147 
1148 // =============================================================================
1149 // CONNECT AND DISCONNECT
1150 // =============================================================================
1151 net.user1.orbiter.ConnectionManager.prototype.connect = function () {
1152   if (this.connections.length == 0) {
1153     this.orbiter.getLog().error("[CONNECTION_MANAGER] No connections defined. Connection request ignored.");
1154     return;
1155   }
1156   
1157   this.connectAttemptCount++;
1158   this.attemptedConnections = new Array();
1159 
1160   switch (this.connectionState) {
1161     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1162       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection attempt already in " 
1163                             + "progress. Existing attempt must be aborted before"  
1164                             + " new connection attempt begins...");
1165       this.disconnect();
1166       break;
1167 
1168     case net.user1.orbiter.ConnectionState.READY:
1169       this.orbiter.getLog().info("[CONNECTION_MANAGER] Existing connection to Union" 
1170                             + " must be disconnected before new connection" 
1171                             + " attempt begins.");
1172       this.disconnect();
1173       break;
1174   }
1175   this.setConnectionState(net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS);
1176   
1177   this.orbiter.getLog().debug("[CONNECTION_MANAGER] Searching for most recent valid connection.");
1178   var originalConnectionIndex = this.currentConnectionIndex;
1179   while (!this.getCurrentConnection().isValid()) {
1180     this.advance();
1181     if (this.currentConnectionIndex == originalConnectionIndex) {
1182       // Couldn't find a valid connection, so start the connection with
1183       // the first connection in the connection list
1184       this.orbiter.getLog().debug("[CONNECTION_MANAGER] No valid connection found. Starting connection attempt with first connection.");
1185       this.currentConnectionIndex = 0;
1186       break;
1187     }
1188   }  
1189   
1190   this.dispatchBeginConnect();
1191   this.connectCurrentConnection();
1192 };
1193 
1194 net.user1.orbiter.ConnectionManager.prototype.disconnect = function () {
1195   if (this.connections.length == 0) {
1196     this.dispatchConnectFailure("No connections defined. Disconnection attempt failed.");
1197     return;
1198   }
1199   
1200   switch (this.connectionState) {
1201     // Currently connected
1202     case net.user1.orbiter.ConnectionState.READY:
1203       this.orbiter.getLog().info("[CONNECTION_MANAGER] Closing existing connection: "
1204                             + this.getActiveConnection().toString());
1205       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1206       this.disconnectConnection(this.getActiveConnection());
1207       break;
1208 
1209     // Currently attempting to connect
1210     case net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS:
1211       this.orbiter.getLog().info("[CONNECTION_MANAGER] Aborting existing connection attempt: "
1212                             + this.getInProgressConnection().toString());
1213       this.connectAbortCount++;
1214       this.setConnectionState(net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS);
1215       this.disconnectConnection(this.getInProgressConnection());
1216       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection abort complete.");
1217       break;
1218 
1219     // Currently attempting to disconnect
1220     case net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS:
1221       this.orbiter.getLog().info("[CONNECTION_MANAGER] Disconnection request ignored."
1222                             + " Already disconnecting.");
1223       break;
1224   }
1225 };
1226     
1227 /** @private */
1228 net.user1.orbiter.ConnectionManager.prototype.disconnectConnection = function (connection) {
1229   connection.disconnect();
1230 };
1231     
1232 /** @private */
1233 net.user1.orbiter.ConnectionManager.prototype.connectCurrentConnection = function () {
1234   // If there are no Connections defined, fail immediately 
1235   if (this.connections.length == 0) {
1236     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1237     this.connectFailedCount++;
1238     this.dispatchConnectFailure("No connections defined. Connection attempt failed.");
1239     return;
1240   }
1241   
1242   this.inProgressConnection = this.getCurrentConnection();
1243   
1244   // If the requested connection has already been attempted this round,
1245   // ignore it.
1246   if (net.user1.utils.ArrayUtil.indexOf(this.attemptedConnections, this.inProgressConnection) != -1) {
1247     this.advanceAndConnect();
1248     return;
1249   }
1250   
1251   this.dispatchSelectConnection(this.inProgressConnection);
1252   this.orbiter.getLog().info("[CONNECTION_MANAGER] Attempting connection via "
1253                         + this.inProgressConnection.toString() + ". (Connection "
1254                         + (this.attemptedConnections.length+1) + " of "
1255                         + this.connections.length + ". Attempt " + this.connectAttemptCount +" since last successful connection).");
1256   this.addConnectionListeners(this.inProgressConnection);
1257   this.inProgressConnection.connect();
1258 };
1259 
1260 /** @private */
1261 net.user1.orbiter.ConnectionManager.prototype.advanceAndConnect = function () {
1262   if (!this.connectAttemptComplete()) {
1263     this.advance();
1264     this.connectCurrentConnection();
1265   } else {
1266     // Tried all connections, so give up and dispatch CONNECT_FAILURE
1267     this.connectFailedCount++;
1268     this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1269     this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for all specified hosts and ports.");
1270     this.dispatchConnectFailure("Connection failed for all specified hosts and ports.");
1271   }
1272 };
1273     
1274 /** @private */
1275 net.user1.orbiter.ConnectionManager.prototype.connectAttemptComplete = function () {
1276   return this.attemptedConnections.length == this.connections.length;
1277 };
1278 
1279 /** @private */
1280 net.user1.orbiter.ConnectionManager.prototype.advance = function () {
1281   this.currentConnectionIndex++;
1282   if (this.currentConnectionIndex == this.connections.length) {
1283     this.currentConnectionIndex = 0;
1284   }
1285 };
1286     
1287 // =============================================================================
1288 // CONNECTION OBJECT MANAGEMENT
1289 // =============================================================================
1290 net.user1.orbiter.ConnectionManager.prototype.addConnection = function (connection) {
1291   if (connection != null) {
1292     this.orbiter.getLog().info("[CONNECTION_MANAGER] New connection added. "
1293                           + connection.toString() + ".");
1294     connection.setOrbiter(this.orbiter);
1295     this.connections.push(connection);
1296   }
1297 };
1298     
1299 net.user1.orbiter.ConnectionManager.prototype.removeConnection = function (connection) {
1300   if (connection != null) {
1301     connection.disconnect();
1302     this.removeConnectionListeners(connection);
1303     return net.user1.utils.ArrayUtil.remove(this.connections, connection);
1304   } else {
1305     return false;
1306   }
1307 };
1308 
1309 net.user1.orbiter.ConnectionManager.prototype.removeAllConnections = function () {
1310   if (this.connections.length == 0) {
1311     this.orbiter.getLog().info("[CONNECTION_MANAGER] removeAllConnections() ignored. " +
1312                                " No connections to remove.");
1313     return;
1314   }
1315   
1316   this.orbiter.getLog().info("[CONNECTION_MANAGER] Removing all connections...");
1317   this.disconnect();
1318   while (this.connections.length > 0) {
1319     this.removeConnection(this.connections[0]);
1320   }
1321   this.currentConnectionIndex = 0;
1322   this.orbiter.getLog().info("[CONNECTION_MANAGER] All connections removed.");
1323 };
1324     
1325 // =============================================================================
1326 // CONNECTION ACCESS
1327 // =============================================================================
1328 net.user1.orbiter.ConnectionManager.prototype.getActiveConnection = function () {
1329   return this.activeConnection;
1330 };
1331     
1332 net.user1.orbiter.ConnectionManager.prototype.getInProgressConnection = function () {
1333   return this.inProgressConnection;
1334 };
1335     
1336 net.user1.orbiter.ConnectionManager.prototype.getConnections = function () {
1337   return this.connections.slice();
1338 };
1339 
1340 /** @private */    
1341 net.user1.orbiter.ConnectionManager.prototype.getCurrentConnection = function () {
1342   return this.connections[this.currentConnectionIndex];
1343 };
1344     
1345 // =============================================================================
1346 // CONNECTION LISTENER REGISTRATION
1347 // =============================================================================
1348 /** @private */
1349 net.user1.orbiter.ConnectionManager.prototype.addConnectionListeners = function(connection) {
1350   if (connection != null) {
1351     connection.addEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1352     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1353     connection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1354     connection.addEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1355     connection.addEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1356   }
1357 };
1358     
1359 /** @private */    
1360 net.user1.orbiter.ConnectionManager.prototype.removeConnectionListeners = function (connection) {
1361   if (connection != null) {
1362     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.READY,               this.readyListener, this);
1363     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,     this.connectFailureListener, this);
1364     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,          this.disconnectListener, this);
1365     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT, this.clientKillConnectListener, this);
1366     connection.removeEventListener(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT, this.serverKillConnectListener, this);
1367   }
1368 };
1369     
1370 // =============================================================================
1371 // CONNECTION STATE ACCESS
1372 // =============================================================================
1373 net.user1.orbiter.ConnectionManager.prototype.isReady = function () {
1374   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
1375 }
1376 
1377 net.user1.orbiter.ConnectionManager.prototype.setConnectionState = function (state) {
1378   var changed = false;
1379   if (state != this.connectionState) {
1380     changed = true;
1381   }
1382   this.connectionState = state;
1383   if (changed) {
1384     this.dispatchConnectionStateChange();
1385   }
1386 };
1387 
1388 net.user1.orbiter.ConnectionManager.prototype.getConnectionState = function () {
1389   return this.connectionState;
1390 };
1391     
1392 // =============================================================================
1393 // CONNECTION COUNT MANAGEMENT
1394 // =============================================================================
1395 net.user1.orbiter.ConnectionManager.prototype.getReadyCount = function () {
1396   return this.readyCount;
1397 };
1398   
1399 net.user1.orbiter.ConnectionManager.prototype.getConnectFailedCount = function () {
1400   return this.connectFailedCount;
1401 };
1402   
1403 net.user1.orbiter.ConnectionManager.prototype.getConnectAttemptCount = function () {
1404   return this.connectAttemptCount;
1405 };
1406   
1407 net.user1.orbiter.ConnectionManager.prototype.getConnectAbortCount = function () {
1408   return this.connectAbortCount;
1409 };
1410     
1411 // =============================================================================
1412 // CURRENT CONNECTION LISTENERS
1413 // =============================================================================
1414 /** @private */
1415 net.user1.orbiter.ConnectionManager.prototype.readyListener = function (e) {
1416   this.setConnectionState(net.user1.orbiter.ConnectionState.READY);
1417   this.inProgressConnection = null;
1418   this.activeConnection = e.target;
1419   this.readyCount++;
1420   this.connectFailedCount = 0;
1421   this.connectAttemptCount = 0;
1422   this.connectAbortCount = 0;
1423   this.dispatchReady();
1424 };
1425 
1426 /** @private */
1427 net.user1.orbiter.ConnectionManager.prototype.connectFailureListener = function (e) {
1428   var failedConnection = e.target;
1429   this.orbiter.getLog().warn("[CONNECTION_MANAGER] Connection failed for "
1430                         + failedConnection.toString() 
1431                         + ". Status: [" + e.getStatus() + "]");
1432   
1433   this.removeConnectionListeners(failedConnection);
1434   this.inProgressConnection = null;
1435   
1436   if (this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
1437     this.dispatchConnectFailure("Connection closed by client.");
1438   } else {
1439     if (failedConnection.getHost() != failedConnection.getRequestedHost()) {
1440       this.orbiter.getLog().info("[CONNECTION_MANAGER] Connection failed for affinity address [" + failedConnection.getHost() + "]. Removing affinity.");
1441       this.clearAffinity(failedConnection.getRequestedHost());
1442     }
1443 
1444     this.attemptedConnections.push(failedConnection);
1445     this.advanceAndConnect();
1446   }
1447 };
1448 
1449 /** @private */
1450 net.user1.orbiter.ConnectionManager.prototype.disconnectListener = function (e) {
1451   this.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
1452   this.removeConnectionListeners(e.target);
1453   this.activeConnection = null;
1454   this.dispatchDisconnect(e.target);
1455 };
1456 
1457 /** @private */
1458 net.user1.orbiter.ConnectionManager.prototype.clientKillConnectListener = function (e) {
1459   this.dispatchClientKillConnect(e.target);
1460   // This event is always followed by a DISCONNECT event
1461 };
1462 
1463 /** @private */
1464 net.user1.orbiter.ConnectionManager.prototype.serverKillConnectListener = function (e) {
1465   this.dispatchServerKillConnect(e.target);
1466   // This event is always followed by a DISCONNECT event
1467 };
1468 
1469 // =============================================================================
1470 // READY TIMEOUT MANAGEMENT
1471 // =============================================================================
1472     
1473 net.user1.orbiter.ConnectionManager.prototype.setReadyTimeout = function (milliseconds) {
1474   if (milliseconds > 0) {
1475     this.readyTimeout = milliseconds;
1476     this.orbiter.getLog().info("[CONNECTION_MANAGER] Ready timeout set to " + milliseconds + " ms.");
1477     if (milliseconds < 3000) {
1478       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Current ready timeout (" 
1479                            + milliseconds + ") may not allow sufficient time"
1480                            + " to connect to Union Server over a typical"
1481                            + " internet connection.");
1482     }
1483   } else {
1484     this.orbiter.getLog().warn("[CONNECTION_MANAGER] Invalid ready timeout specified: " 
1485              + milliseconds + ". Duration must be greater than zero.");
1486   }
1487 };
1488     
1489 net.user1.orbiter.ConnectionManager.prototype.getReadyTimeout = function () {
1490   return this.readyTimeout;
1491 };
1492 
1493 // =============================================================================
1494 // SERVER AFFINITY
1495 // =============================================================================
1496 net.user1.orbiter.ConnectionManager.prototype.getAffinity = function (host) {
1497   var address = this.affinityData.read("affinity", host+"address");
1498   var until = parseFloat(this.affinityData.read("affinity", host+"until"));
1499   
1500   if (address != null) {
1501     var now = new Date().getTime();
1502     if (now >= until) {
1503       this.orbiter.getLog().warn("[CONNECTION_MANAGER] Affinity duration expired for address [" 
1504                                  + address + "], host [" + host + "]. Removing affinity.");
1505       this.clearAffinity(host);
1506     } else {
1507       return address;
1508     }
1509   }
1510 
1511   return host;
1512 };
1513 
1514 /**
1515  * @private
1516  */
1517 net.user1.orbiter.ConnectionManager.prototype.setAffinity = function (host, address, duration) {
1518   var until = new Date().getTime() + (duration*60*1000);
1519   // Don't use JSON stringify for affinity values because not all JavaScript
1520   // environments support JSON natively (e.g., non-browser VMs)
1521   this.affinityData.write("affinity", host+"address", address);
1522   this.affinityData.write("affinity", host+"until", until);
1523 
1524   this.orbiter.getLog().info("[CONNECTION_MANAGER] Assigning affinity address [" 
1525     + address + "] for supplied host [" +host + "]. Duration (minutes): "
1526     + duration);
1527 };
1528 
1529 /**
1530  * @private
1531  */
1532 net.user1.orbiter.ConnectionManager.prototype.clearAffinity = function (host) {
1533   this.affinityData.remove("affinity", host+"address");
1534   this.affinityData.remove("affinity", host+"until");
1535 };
1536     
1537 net.user1.orbiter.ConnectionManager.prototype.setGlobalAffinity = function (enabled) {
1538   if (enabled) {
1539     this.orbiter.getLog().info("[CONNECTION_MANAGER] Global server affinity selected."
1540      + " Using current environment's shared server affinity."); 
1541     this.affinityData = net.user1.utils.LocalData;
1542   } else {
1543     this.orbiter.getLog().info("[CONNECTION_MANAGER] Local server affinity selected."
1544      + " The current client will maintain its own, individual server affinity."); 
1545     this.affinityData = new net.user1.utils.MemoryStore();
1546   }
1547 };
1548 
1549 // =============================================================================
1550 // EVENT DISPATCHING
1551 // =============================================================================
1552     
1553 /** @private */
1554 net.user1.orbiter.ConnectionManager.prototype.dispatchBeginConnect = function () {
1555   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.BEGIN_CONNECT));
1556 };
1557     
1558 /** @private */
1559 net.user1.orbiter.ConnectionManager.prototype.dispatchSelectConnection = function (connection) {
1560   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
1561       connection));
1562 };
1563     
1564 /** @private */
1565 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectFailure = function (status) {
1566   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE,
1567       null, status));
1568 };
1569     
1570 /** @private */
1571 net.user1.orbiter.ConnectionManager.prototype.dispatchDisconnect = function (connection) {
1572   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT,
1573       connection));
1574 };
1575     
1576 /** @private */
1577 net.user1.orbiter.ConnectionManager.prototype.dispatchServerKillConnect = function (connection) {
1578   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT,
1579       connection));
1580 };
1581     
1582 /** @private */
1583 net.user1.orbiter.ConnectionManager.prototype.dispatchClientKillConnect = function (connection) {
1584   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT,
1585       connection));
1586 };
1587     
1588 /** @private */
1589 net.user1.orbiter.ConnectionManager.prototype.dispatchReady = function () {
1590   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.READY));
1591 };
1592 
1593 /** @private */
1594 net.user1.orbiter.ConnectionManager.prototype.dispatchConnectionStateChange = function () {
1595   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.CONNECTION_STATE_CHANGE));
1596 };
1597 
1598 /** @private */
1599 net.user1.orbiter.ConnectionManager.prototype.dispatchSessionTerminated = function () {
1600   this.dispatchEvent(new net.user1.orbiter.ConnectionManagerEvent(net.user1.orbiter.ConnectionManagerEvent.SESSION_TERMINATED));
1601 };
1602 
1603 // =============================================================================
1604 // DISPOSAL
1605 // =============================================================================    
1606 net.user1.orbiter.ConnectionManager.prototype.dispose = function () {
1607   this.removeAllConnections();
1608   this.attemptedConnections = null;
1609   this.activeConnection = null;
1610   this.inProgressConnection = null;
1611   this.connections = null;
1612 };
1613 
1614 
1615 
1616 
1617 
1618 
1619 
1620 
1621 
1622 
1623 
1624 
1625 
1626 
1627 
1628 
1629 
1630 
1631 
1632 
1633 
1634 
1635 //==============================================================================
1636 // CLASS DECLARATION
1637 //==============================================================================
1638 /** @class */
1639 net.user1.orbiter.ConnectionMonitor = function (orbiter) {
1640   // Instance variables
1641   this.connectionTimeout = 0;
1642   this.heartbeatIntervalID = -1;
1643   this.heartbeatCounter = 0;
1644   this.heartbeatEnabled = true;
1645   this.heartbeats = new net.user1.utils.UDictionary();
1646   
1647   this.oldestHeartbeat = 0;
1648   this.heartBeatFrequency = -1;
1649   
1650   this.sharedPing = false;
1651 
1652   this.autoReconnectMinMS = 0;
1653   this.autoReconnectMaxMS = 0;
1654   this.autoReconnectFrequency = -1;
1655   this.autoReconnectDelayFirstAttempt = false;
1656   this.autoReconnectTimeoutID = -1;
1657   this.autoReconnectAttemptLimit = -1;
1658   
1659   this.orbiter = orbiter;
1660   this.msgManager = orbiter.getMessageManager();
1661   this.log = orbiter.getLog();
1662   
1663   this.disposed = false;
1664   
1665   // Initialization
1666   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
1667   this.orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
1668   this.disableHeartbeatLogging();
1669 };
1670 
1671 //==============================================================================
1672 // STATIC VARIABLES
1673 //==============================================================================
1674 net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY = 10000;
1675 net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY = 20;
1676 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY = -1;
1677 net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT = -1;
1678 net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT = 60000;
1679 
1680 //==============================================================================
1681 // CONNECTION MONITORING
1682 //==============================================================================
1683 /** @private */
1684 net.user1.orbiter.ConnectionMonitor.prototype.connectReadyListener = function (e) {
1685   this.msgManager.addMessageListener(net.user1.orbiter.Messages.CLIENT_HEARTBEAT, this.heartbeatMessageListener, this);
1686   this.startHeartbeat();
1687   this.stopReconnect();
1688 }
1689 
1690 /** @private */
1691 net.user1.orbiter.ConnectionMonitor.prototype.connectCloseListener = function (e) {
1692   this.stopHeartbeat();
1693 
1694   var numAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1695   if (numAttempts == 0) {
1696     this.selectReconnectFrequency();
1697   }
1698 
1699   if (this.autoReconnectFrequency > -1) {
1700     if (this.autoReconnectTimeoutID != -1) {
1701       return;
1702     } else {
1703       // Defer reconnection until after all other listeners have processed the
1704       // CLOSE event
1705       var self = this;
1706       setTimeout(function () {
1707         // If another listener disposed of Orbiter, or disabled autoreconnect, quit
1708         if (!self.disposed && self.autoReconnectFrequency != -1) {
1709           self.log.warn("[CONNECTION_MONITOR] Disconnection detected.");
1710           if (self.autoReconnectDelayFirstAttempt
1711               && (
1712                   (numAttempts == 0)
1713                   ||
1714                   (numAttempts == 1 && self.orbiter.getConnectionManager().getReadyCount() == 0)
1715                  )
1716              ) {
1717             self.log.info("[CONNECTION_MONITOR] Delaying reconnection attempt"
1718               + " by " + self.autoReconnectFrequency + " ms...");
1719             self.scheduleReconnect(self.autoReconnectFrequency);
1720           } else {
1721             self.doReconnect();
1722           }
1723         }
1724       }, 1);
1725     }
1726   }
1727 }
1728     
1729 //==============================================================================
1730 // HEARTBEAT
1731 //==============================================================================
1732 
1733 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeat = function () {
1734   this.log.info("[CONNECTION_MONITOR] Heartbeat enabled.");
1735   this.heartbeatEnabled = true;
1736   this.startHeartbeat();
1737 }
1738 
1739 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeat = function () {
1740   this.log.info("[CONNECTION_MONITOR] Heartbeat disabled.");
1741   this.heartbeatEnabled = false;
1742   this.stopHeartbeat();
1743 }
1744 
1745 /** @private */
1746 net.user1.orbiter.ConnectionMonitor.prototype.startHeartbeat = function () {
1747   if (!this.heartbeatEnabled) {
1748     this.log.info("[CONNECTION_MONITOR] Heartbeat is currently disabled. Ignoring start request.");
1749     return;
1750   }
1751   
1752   this.stopHeartbeat();
1753   
1754   this.heartbeats = new net.user1.utils.UDictionary();
1755   
1756   var currentObj = this;
1757   var callback   = this.heartbeatTimerListener;
1758   this.heartbeatIntervalID = setInterval(function () {
1759     callback.call(currentObj);
1760   }, this.heartBeatFrequency);
1761   
1762 }
1763 
1764 /** @private */
1765 net.user1.orbiter.ConnectionMonitor.prototype.stopHeartbeat = function () {
1766   clearInterval(this.heartbeatIntervalID);
1767   this.heartbeats = null;
1768 }
1769 
1770 /** @private */
1771 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatTimerListener = function () {
1772   if (!this.orbiter.isReady()) {
1773     this.log.info("[CONNECTION_MONITOR] Orbiter is not connected. Stopping heartbeat.");
1774     this.stopHeartbeat();
1775     return;
1776   }
1777 
1778   var timeSinceOldestHeartbeat;
1779   var now = new Date().getTime();
1780   
1781   this.heartbeats[this.heartbeatCounter] = now;
1782   this.orbiter.getMessageManager().sendUPC("u2",
1783                                  net.user1.orbiter.Messages.CLIENT_HEARTBEAT, 
1784                                  this.orbiter.getClientID(),
1785                                  "",
1786                                  this.heartbeatCounter);
1787   this.heartbeatCounter++;
1788   
1789   // Assign the oldest heartbeat
1790   if (net.user1.utils.ObjectUtil.length(this.heartbeats) == 1) {
1791     this.oldestHeartbeat = now;
1792   } else { 
1793     this.oldestHeartbeat = Number.MAX_VALUE;
1794     for (var p in this.heartbeats) {
1795       if (this.heartbeats[p] < this.oldestHeartbeat) {
1796         this.oldestHeartbeat = this.heartbeats[p];
1797       }
1798     }
1799   }
1800   // Close connection if too much time has passed since the last response
1801   timeSinceOldestHeartbeat = now - this.oldestHeartbeat;
1802   if (timeSinceOldestHeartbeat > this.connectionTimeout) {
1803     this.log.warn("[CONNECTION_MONITOR] No response from server in " + 
1804                   timeSinceOldestHeartbeat + "ms. Starting automatic disconnect.");
1805     this.orbiter.disconnect();
1806   }
1807 }
1808 
1809 /** @private */
1810 net.user1.orbiter.ConnectionMonitor.prototype.heartbeatMessageListener = function (fromClientID, id) {
1811   var ping = new Date().getTime() - this.heartbeats[parseInt(id)];
1812   if (typeof this.orbiter.self().setAttribute === "undefined") {
1813     // OrbiterMicro
1814     this.orbiter.self().ping = ping;
1815     this.orbiter.getMessageManager().sendUPC("u3",
1816                                              this.orbiter.getClientID(),
1817                                              "",
1818                                              "_PING",
1819                                              ping.toString(),
1820                                              "",
1821                                              this.sharedPing ? "4" : "0");
1822   } else {
1823     // Orbiter
1824     this.orbiter.self().setAttribute("_PING",
1825                                      ping.toString(),
1826                                      null,
1827                                      this.sharedPing);
1828   }
1829   delete this.heartbeats[parseInt(id)];
1830 }
1831 
1832 //==============================================================================
1833 // RECONNECTION
1834 //==============================================================================
1835 /** @private */
1836 net.user1.orbiter.ConnectionMonitor.prototype.reconnectTimerListener = function (e) {
1837   this.stopReconnect();
1838   if (this.orbiter.getConnectionManager().connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
1839     this.doReconnect();
1840   }
1841 }
1842 
1843 /** @private */
1844 net.user1.orbiter.ConnectionMonitor.prototype.stopReconnect = function () {
1845   clearTimeout(this.autoReconnectTimeoutID);
1846   this.autoReconnectTimeoutID = -1
1847 }
1848 
1849 /** @private */
1850 net.user1.orbiter.ConnectionMonitor.prototype.scheduleReconnect = function (milliseconds) {
1851   // Reset the timer
1852   this.stopReconnect();
1853   var currentObj = this;
1854   var callback   = this.reconnectTimerListener;
1855   this.autoReconnectTimeoutID = setTimeout(function () {
1856     currentObj.autoReconnectTimeoutID = -1;
1857     callback.call(currentObj);
1858   }, milliseconds);
1859 };
1860 
1861 /** @private */
1862 net.user1.orbiter.ConnectionMonitor.prototype.selectReconnectFrequency = function () {
1863   if (this.autoReconnectMinMS == -1) {
1864     this.autoReconnectFrequency = -1;
1865   } else if (this.autoReconnectMinMS == this.autoReconnectMaxMS) {
1866     this.autoReconnectFrequency = this.autoReconnectMinMS;
1867   } else {
1868     this.autoReconnectFrequency = getRandInt(this.autoReconnectMinMS, this.autoReconnectMaxMS);
1869     this.log.info("[CONNECTION_MONITOR] Random auto-reconnect frequency selected: [" +
1870                   this.autoReconnectFrequency + "] ms.");
1871   }
1872 
1873   function getRandInt (min, max) {
1874     return min + Math.floor(Math.random()*(max+1 - min));
1875   }
1876 };
1877 
1878 /** @private */
1879 net.user1.orbiter.ConnectionMonitor.prototype.doReconnect = function () {
1880   var numActualAttempts = this.orbiter.getConnectionManager().getConnectAttemptCount();
1881   var numReconnectAttempts;
1882 
1883   if (this.orbiter.getConnectionManager().getReadyCount() == 0) {
1884     numReconnectAttempts = numActualAttempts - 1;
1885   } else {
1886     numReconnectAttempts = numActualAttempts;
1887   }
1888 
1889   if (this.autoReconnectAttemptLimit != -1
1890       && numReconnectAttempts > 0
1891       && numReconnectAttempts % (this.autoReconnectAttemptLimit) == 0) {
1892     this.log.warn("[CONNECTION_MONITOR] Automatic reconnect attempt limit reached."
1893                   + " No further automatic connection attempts will be made until"
1894                   + " the next manual connection attempt.");
1895     return;
1896   }
1897 
1898   this.scheduleReconnect(this.autoReconnectFrequency);
1899 
1900   this.log.warn("[CONNECTION_MONITOR] Attempting automatic reconnect. (Next attempt in "
1901                 + this.autoReconnectFrequency + "ms.)");
1902   this.orbiter.connect();
1903 }
1904 
1905 //==============================================================================
1906 // CONFIGURATION
1907 //==============================================================================
1908 
1909 net.user1.orbiter.ConnectionMonitor.prototype.restoreDefaults = function () {
1910   this.setAutoReconnectFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_FREQUENCY);
1911   this.setAutoReconnectAttemptLimit(net.user1.orbiter.ConnectionMonitor.DEFAULT_AUTORECONNECT_ATTEMPT_LIMIT);
1912   this.setConnectionTimeout(net.user1.orbiter.ConnectionMonitor.DEFAULT_CONNECTION_TIMEOUT);
1913   this.setHeartbeatFrequency(net.user1.orbiter.ConnectionMonitor.DEFAULT_HEARTBEAT_FREQUENCY);
1914 }
1915 
1916 net.user1.orbiter.ConnectionMonitor.prototype.setHeartbeatFrequency = function (milliseconds) {
1917   if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY) {
1918     this.heartBeatFrequency = milliseconds;
1919     this.log.info("[CONNECTION_MONITOR] Heartbeat frequency set to " 
1920                   + milliseconds + " ms.");
1921     // Log a warning for low heartbeat frequencies...
1922     if (milliseconds >= net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY && milliseconds < 1000) {
1923       this.log.info("[CONNECTION_MONITOR] HEARTBEAT FREQUENCY WARNING: " 
1924                + milliseconds + " ms. Current frequency will generate "
1925                + (Math.floor((1000/milliseconds)*10)/10) 
1926                + " messages per second per connected client.");
1927     }
1928     
1929     // If the connection is ready, then restart
1930     // the heartbeat when the heartbeat frequency changes.
1931     if (this.orbiter.isReady()) {
1932       this.startHeartbeat();
1933     }
1934   } else {
1935     this.log.warn("[CONNECTION_MONITOR] Invalid heartbeat frequency specified: " 
1936              + milliseconds + ". Frequency must be "
1937              + net.user1.orbiter.ConnectionMonitor.MIN_HEARTBEAT_FREQUENCY + " or greater.");
1938   }
1939 }
1940 
1941 net.user1.orbiter.ConnectionMonitor.prototype.getHeartbeatFrequency = function () {
1942   return this.heartBeatFrequency;
1943 }
1944 
1945 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectFrequency = function (minMS, maxMS, delayFirstAttempt) {
1946   maxMS = (typeof maxMS == "undefined") ? -1 : maxMS;
1947   delayFirstAttempt = (typeof delayFirstAttempt == "undefined") ? false : delayFirstAttempt;
1948 
1949   if (minMS == 0 || minMS < -1) {
1950     this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect minMS specified: ["
1951       + minMS + "]. Value must not be zero or less than -1. Value adjusted"
1952       + " to [-1] (no reconnect).");
1953     minMS = -1;
1954   }
1955   if (minMS == -1) {
1956     this.stopReconnect();
1957   } else {
1958     if (maxMS == -1) {
1959       maxMS = minMS;
1960     }
1961     if (maxMS < minMS) {
1962       this.log.warn("[CONNECTION_MONITOR] Invalid auto-reconnect maxMS specified: ["
1963                     + maxMS + "]." + " Value of maxMS must be greater than or equal "
1964                     + "to minMS. Value adjusted to [" + minMS + "].");
1965       maxMS = minMS;
1966     }
1967   }
1968 
1969   this.autoReconnectDelayFirstAttempt = delayFirstAttempt;
1970   this.autoReconnectMinMS = minMS;
1971   this.autoReconnectMaxMS = maxMS;
1972 
1973   this.log.info("[CONNECTION_MONITOR] Assigning auto-reconnect frequency settings: [minMS: "
1974                 + minMS + ", maxMS: " + maxMS + ", delayFirstAttempt: "
1975                 + delayFirstAttempt.toString() + "].");
1976   if (minMS > 0 && minMS < 1000) {
1977     this.log.info("[CONNECTION_MONITOR] RECONNECT FREQUENCY WARNING: "
1978                   + minMS + " minMS specified. Current frequency will cause "
1979                   + (Math.floor((1000/minMS)*10)/10).toString()
1980                   + " reconnection attempts per second.");
1981   }
1982   this.selectReconnectFrequency();
1983 }
1984 
1985 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectFrequency = function () {
1986   return this.autoReconnectFrequency;
1987 }
1988 
1989 net.user1.orbiter.ConnectionMonitor.prototype.setAutoReconnectAttemptLimit = function (attempts) {
1990   if (attempts < -1 || attempts == 0) {
1991     this.log.warn("[CONNECTION_MONITOR] Invalid Auto-reconnect attempt limit specified: " 
1992              + attempts + ". Limit must -1 or greater than 1.");
1993     return;
1994   }
1995     
1996   this.autoReconnectAttemptLimit = attempts;
1997   
1998   if (attempts == -1) {
1999     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to none."); 
2000   } else {
2001     this.log.info("[CONNECTION_MONITOR] Auto-reconnect attempt limit set to " 
2002                   + attempts + " attempt(s).");
2003   }
2004 };
2005     
2006 net.user1.orbiter.ConnectionMonitor.prototype.getAutoReconnectAttemptLimit = function () {
2007   return this.autoReconnectAttemptLimit;
2008 }
2009 
2010 net.user1.orbiter.ConnectionMonitor.prototype.setConnectionTimeout = function (milliseconds) {
2011   if (milliseconds > 0) {
2012     this.connectionTimeout = milliseconds;
2013     this.log.info("[CONNECTION_MONITOR] Connection timeout set to " 
2014                   + milliseconds + " ms.");
2015   } else {
2016     this.log.warn("[CONNECTION_MONITOR] Invalid connection timeout specified: " 
2017                              + milliseconds + ". Frequency must be greater " 
2018                              + "than zero.");
2019   }
2020 }
2021 
2022 net.user1.orbiter.ConnectionMonitor.prototype.getConnectionTimeout = function () {
2023   return this.connectionTimeout;
2024 }
2025 
2026 net.user1.orbiter.ConnectionMonitor.prototype.sharePing = function (share) {
2027   this.sharedPing = share;
2028 }
2029 
2030 net.user1.orbiter.ConnectionMonitor.prototype.isPingShared = function () {
2031   return this.sharedPing;
2032 }
2033 
2034 net.user1.orbiter.ConnectionMonitor.prototype.disableHeartbeatLogging = function () {
2035   this.log.addSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2036   this.log.addSuppressionTerm("<A>_PING</A>");
2037   this.log.addSuppressionTerm("[_PING]");
2038   this.log.addSuppressionTerm("<![CDATA[_PING]]>");
2039 }
2040 
2041 net.user1.orbiter.ConnectionMonitor.prototype.enableHeartbeatLogging = function () {
2042   this.log.removeSuppressionTerm("<A>CLIENT_HEARTBEAT</A>");
2043   this.log.removeSuppressionTerm("<A>_PING</A>");
2044   this.log.removeSuppressionTerm("[_PING]");
2045   this.log.removeSuppressionTerm("<![CDATA[_PING]]>");
2046 }
2047 
2048 // =============================================================================
2049 // DISPOSAL
2050 // =============================================================================
2051 
2052 net.user1.orbiter.ConnectionMonitor.prototype.dispose = function () {
2053   this.disposed = true;
2054   
2055   this.stopHeartbeat();
2056   this.stopReconnect();
2057 
2058   this.heartbeats = null;
2059   
2060   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.READY, this.connectReadyListener, this);
2061   this.orbiter.removeEventListener(net.user1.orbiter.OrbiterEvent.CLOSE, this.connectCloseListener, this);
2062   this.orbiter = null;
2063   this.msgManager.removeMessageListener("u7", this.u7);
2064   this.msgManager(null);
2065   this.log = null;
2066 };
2067 
2068 
2069 
2070 
2071 
2072 
2073 
2074 
2075 
2076 
2077 
2078 
2079 
2080 
2081 
2082 
2083 
2084 //==============================================================================
2085 // CLASS DECLARATION
2086 //==============================================================================
2087 /** @class
2088     @extends net.user1.events.Event
2089 */
2090 net.user1.orbiter.OrbiterEvent = function (type, 
2091                                            serverUPCVersion,
2092                                            connectionRefusal) {
2093   net.user1.events.Event.call(this, type);
2094 
2095   this.serverUPCVersion = serverUPCVersion;
2096   this.connectionRefusal = connectionRefusal;
2097 };
2098 
2099 //==============================================================================
2100 // INHERITANCE
2101 //==============================================================================
2102 net.user1.utils.extend(net.user1.orbiter.OrbiterEvent, net.user1.events.Event);
2103  
2104 //==============================================================================
2105 // STATIC VARIABLES
2106 //==============================================================================
2107 /** @constant */
2108 net.user1.orbiter.OrbiterEvent.READY = "READY";
2109 /** @constant */
2110 net.user1.orbiter.OrbiterEvent.CLOSE = "CLOSE";
2111 /** @constant */
2112 net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE = "PROTOCOL_INCOMPATIBLE";
2113 /** @constant */
2114 net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED = "CONNECT_REFUSED";
2115 
2116 //==============================================================================
2117 // INSTANCE METHODS
2118 //==============================================================================  
2119 net.user1.orbiter.OrbiterEvent.prototype.getServerUPCVersion = function () {
2120   return this.serverUPCVersion;
2121 }
2122 
2123 net.user1.orbiter.OrbiterEvent.prototype.getConnectionRefusal = function () {
2124   return this.connectionRefusal;
2125 }
2126 
2127 net.user1.orbiter.OrbiterEvent.prototype.toString = function () {
2128   return "[object OrbiterEvent]";
2129 }  
2130 
2131 //==============================================================================
2132 // CLASS DECLARATION
2133 //==============================================================================
2134 /** @class
2135 
2136 The Snapshot class dispatches the following events:
2137 
2138 <ul class="summary">
2139 <li class="fixedFont">{@link net.user1.orbiter.SnapshotEvent.LOAD}</li>
2140 <li class="fixedFont">{@link net.user1.orbiter.SnapshotEvent.STATUS}</li>
2141 </ul>
2142 
2143 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2144 
2145     @extends net.user1.events.EventDispatcher
2146 */
2147 net.user1.orbiter.snapshot.Snapshot = function () {
2148   // Call superconstructor
2149   net.user1.events.EventDispatcher.call(this);
2150   this.method;
2151   this.args = new Array();
2152   this.hasStatus;
2153   this.statusReceived;
2154   this.loaded;
2155   this._updateInProgress;
2156   this._status;
2157   this.onLoad = function () {};
2158 };
2159 
2160 //==============================================================================    
2161 // INHERITANCE
2162 //============================================================================== 
2163 net.user1.utils.extend(net.user1.orbiter.snapshot.Snapshot, net.user1.events.EventDispatcher);
2164 
2165 //==============================================================================
2166 // INSTANCE METHODS
2167 //==============================================================================   
2168 net.user1.orbiter.snapshot.Snapshot.prototype.updateInProgress = function () {
2169   return this._updateInProgress;
2170 };
2171     
2172 /**
2173  * @private
2174  */    
2175 net.user1.orbiter.snapshot.Snapshot.prototype.setUpdateInProgress = function (value) {
2176   this._updateInProgress = value;
2177 };
2178     
2179 /**
2180  * @private
2181  */    
2182 net.user1.orbiter.snapshot.Snapshot.prototype.dispatchLoaded = function () {
2183   this.dispatchEvent(new net.user1.orbiter.snapshot.SnapshotEvent(net.user1.orbiter.snapshot.SnapshotEvent.LOAD, this));
2184 };
2185     
2186 /**
2187  * @private
2188  */    
2189 net.user1.orbiter.snapshot.Snapshot.prototype.dispatchStatus = function () {
2190   this.dispatchEvent(new net.user1.orbiter.snapshot.SnapshotEvent(net.user1.orbiter.snapshot.SnapshotEvent.STATUS, this));
2191 };
2192 
2193 net.user1.orbiter.snapshot.Snapshot.prototype.getStatus = function () {
2194   return this._status;
2195 };
2196 
2197 /**
2198  * @private
2199  */    
2200 net.user1.orbiter.snapshot.Snapshot.prototype.setStatus = function (value) {
2201   this._status = value;
2202 };
2203 //==============================================================================
2204 // CLASS DECLARATION
2205 //==============================================================================
2206 /** 
2207  * @class
2208  * @extends net.user1.events.Event
2209  */
2210 net.user1.orbiter.snapshot.SnapshotEvent = function (type,
2211                                             snapshot) {
2212   net.user1.events.Event.call(this, type);
2213   this.snapshot = snapshot;
2214 };
2215   
2216 //==============================================================================
2217 // INHERITANCE
2218 //==============================================================================
2219 net.user1.utils.extend(net.user1.orbiter.snapshot.SnapshotEvent, net.user1.events.Event);
2220 
2221 //==============================================================================
2222 // STATIC VARIABLES
2223 //==============================================================================
2224 
2225 /** @constant */
2226 net.user1.orbiter.snapshot.SnapshotEvent.LOAD = "LOAD";
2227 /** @constant */
2228 net.user1.orbiter.snapshot.SnapshotEvent.STATUS = "STATUS";
2229 
2230 net.user1.orbiter.snapshot.SnapshotEvent.prototype.toString = function () {
2231   return "[object SnapshotEvent]";
2232 };
2233 //==============================================================================
2234 // CLASS DECLARATION
2235 //==============================================================================
2236   /**
2237    * @private 
2238    */  
2239 net.user1.orbiter.SnapshotManager = function (messageManager) {
2240   this.messageManager = messageManager;
2241   this.pendingSnapshots = new Object();
2242   this.requestIDCounter = 0;
2243 };
2244     
2245 //==============================================================================
2246 // UPDATE SNAPSHOT
2247 //==============================================================================    
2248     
2249 net.user1.orbiter.SnapshotManager.prototype.updateSnapshot = function (snapshot) {
2250   var args;
2251   if (snapshot != null) {
2252     if (!snapshot.updateInProgress()) {
2253       this.requestIDCounter++;
2254       snapshot.setUpdateInProgress(true);
2255       snapshot.loaded = false;
2256       snapshot.statusReceived = false;
2257       snapshot.setStatus(null);
2258       this.pendingSnapshots[this.requestIDCounter.toString()] = snapshot;
2259       args = snapshot.args.slice(0);
2260       args.unshift(this.requestIDCounter);
2261       args.unshift(snapshot.method);
2262       this.messageManager.sendUPC.apply(this.messageManager, args);
2263     }
2264   }
2265 };
2266 
2267 //==============================================================================
2268 // RECEIVE SNAPSHOT RESULT
2269 //==============================================================================    
2270 
2271 net.user1.orbiter.SnapshotManager.prototype.receiveSnapshotResult = function (requestID, status) {
2272   var snapshot = this.pendingSnapshots[requestID];
2273   if (snapshot == null) {
2274     throw new Error("[SNAPSHOT_MANAGER] Received snapshot result for unknown "
2275                     + "request ID: [" + requestID + "]");
2276   }
2277   snapshot.setStatus(status);
2278   this.setStatusReceived(snapshot, requestID);
2279 };
2280 
2281 //==============================================================================
2282 // RECEIVE CLIENTCOUNT SNAPSHOT
2283 //==============================================================================    
2284 
2285 net.user1.orbiter.SnapshotManager.prototype.receiveClientCountSnapshot =  function (requestID,
2286                                                                                     numClients) {
2287   var snapshot = this.pendingSnapshots[requestID];
2288   if (snapshot == null) {
2289     throw new Error("[SNAPSHOT_MANAGER] Received client-count snapshot for unknown "
2290                     + "request ID: [" + requestID + "]");
2291   }
2292   snapshot.setCount(numClients);
2293   this.setLoaded(snapshot, requestID);
2294 };
2295 
2296 //==============================================================================
2297 // RECEIVE CLIENT SNAPSHOT
2298 //==============================================================================    
2299 
2300 net.user1.orbiter.SnapshotManager.prototype.receiveClientSnapshot = function (requestID, manifest) {
2301   var snapshot = this.pendingSnapshots[requestID];
2302   if (snapshot == null) {
2303     throw new Error("[SNAPSHOT_MANAGER] Received client snapshot for unknown "
2304                     + "request ID: [" + requestID + "]");
2305   }
2306   snapshot.setManifest(manifest);
2307   this.setLoaded(snapshot, requestID);
2308 };
2309 
2310 //==============================================================================
2311 // RECEIVE ACCOUNT SNAPSHOT
2312 //==============================================================================    
2313 
2314 net.user1.orbiter.SnapshotManager.prototype.receiveAccountSnapshot = function (requestID, manifest) {
2315   var snapshot = this.pendingSnapshots[requestID];
2316   if (snapshot == null) {
2317     throw new Error("[SNAPSHOT_MANAGER] Received account snapshot for unknown "
2318                     + "request ID: [" + requestID + "]");
2319   }
2320   snapshot.setManifest(manifest);
2321   this.setLoaded(snapshot, requestID);
2322 }
2323 
2324 //==============================================================================
2325 // RECEIVE ROOMLIST SNAPSHOT
2326 //==============================================================================    
2327 
2328 net.user1.orbiter.SnapshotManager.prototype.receiveRoomListSnapshot = function (requestID,
2329                                                                                 roomList, 
2330                                                                                 qualifier,
2331                                                                                 recursive) {
2332   var snapshot = this.pendingSnapshots[requestID];
2333   if (snapshot == null) {
2334     throw new Error("[SNAPSHOT_MANAGER] Received roomlist snapshot for unknown "
2335                     + "request ID: [" + requestID + "]");
2336   }
2337   snapshot.setRoomList(roomList);
2338   snapshot.setQualifier(qualifier == "" ? null : qualifier);
2339   snapshot.setRecursive(recursive);
2340   this.setLoaded(snapshot, requestID);
2341 };
2342 
2343 //==============================================================================
2344 // RECEIVE ROOM SNAPSHOT
2345 //==============================================================================    
2346 
2347 net.user1.orbiter.SnapshotManager.prototype.receiveRoomSnapshot = function (requestID, manifest) {
2348   var snapshot = this.pendingSnapshots[requestID];
2349   if (snapshot == null) {
2350     throw new Error("[SNAPSHOT_MANAGER] Received room snapshot for unknown "
2351                     + "request ID: [" + requestID + "]");
2352   }
2353   snapshot.setManifest(manifest);
2354   this.setLoaded(snapshot, requestID);
2355 };
2356 
2357 //==============================================================================
2358 // RECEIVE CLIENTLIST SNAPSHOT
2359 //==============================================================================    
2360 
2361 net.user1.orbiter.SnapshotManager.prototype.receiveClientListSnapshot = function (requestID, clientList) {
2362   var snapshot = this.pendingSnapshots[requestID];
2363   if (snapshot == null) {
2364     throw new Error("[SNAPSHOT_MANAGER] Received clientlist snapshot for unknown "
2365                     + "request ID: [" + requestID + "]");
2366   }
2367   snapshot.setClientList(clientList);
2368   this.setLoaded(snapshot, requestID);
2369 };
2370 
2371 //==============================================================================
2372 // RECEIVE ACCOUNTLIST SNAPSHOT
2373 //==============================================================================    
2374 
2375 net.user1.orbiter.SnapshotManager.prototype.receiveAccountListSnapshot = function (requestID, accountList) {
2376   var snapshot = this.pendingSnapshots[requestID];
2377   if (snapshot == null) {
2378     throw new Error("[SNAPSHOT_MANAGER] Received accountlist snapshot for unknown "
2379                     + "request ID: [" + requestID + "]");
2380   }
2381   snapshot.setAccountList(accountList);
2382   this.setLoaded(snapshot, requestID);
2383 };
2384 
2385 //==============================================================================
2386 // RECEIVE BANNEDLIST SNAPSHOT
2387 //==============================================================================    
2388 
2389 net.user1.orbiter.SnapshotManager.prototype.receiveBannedListSnapshot = function (requestID, bannedList) {
2390   var snapshot = this.pendingSnapshots[requestID];
2391   if (snapshot == null) {
2392     throw new Error("[SNAPSHOT_MANAGER] Received bannedlist snapshot for unknown "
2393                     + "request ID: [" + requestID + "]");
2394   }
2395   snapshot.setBannedList(bannedList);
2396   this.setLoaded(snapshot, requestID);
2397 };
2398 
2399 //==============================================================================
2400 // RECEIVE SERVERMODULELIST SNAPSHOT
2401 //==============================================================================    
2402 
2403 net.user1.orbiter.SnapshotManager.prototype.receiveServerModuleListSnapshot = function (requestID, moduleList) {
2404   var snapshot = this.pendingSnapshots[requestID];
2405   if (snapshot == null) {
2406     throw new Error("[SNAPSHOT_MANAGER] Received server module list snapshot for unknown "
2407                     + "request ID: [" + requestID + "]");
2408   }
2409   snapshot.setModuleList(moduleList);
2410   this.setLoaded(snapshot, requestID);
2411 };
2412 
2413 //==============================================================================
2414 // RECEIVE UPCSTATS SNAPSHOT
2415 //==============================================================================    
2416 
2417 net.user1.orbiter.SnapshotManager.prototype.receiveUPCStatsSnapshot = function (requestID, 
2418                                                                                 totalUPCsProcessed,
2419                                                                                 numUPCsInQueue,
2420                                                                                 lastQueueWaitTime,
2421                                                                                 longestUPCProcesses) {
2422   var snapshot = this.pendingSnapshots[requestID];
2423   if (snapshot == null) {
2424     throw new Error("[SNAPSHOT_MANAGER] Received UPC stats snapshot for unknown "
2425                     + "request ID: [" + requestID + "]");
2426   }
2427   snapshot.setTotalUPCsProcessed(totalUPCsProcessed);
2428   snapshot.setNumUPCsInQueue(numUPCsInQueue);
2429   snapshot.setLastQueueWaitTime(lastQueueWaitTime);
2430   snapshot.setLongestUPCProcesses(longestUPCProcesses);
2431   this.setLoaded(snapshot, requestID);
2432 };
2433 
2434 //==============================================================================
2435 // RECEIVE NODELIST SNAPSHOT
2436 //==============================================================================    
2437 
2438 net.user1.orbiter.SnapshotManager.prototype.receiveNodeListSnapshot = function (requestID, nodeList) {
2439   var snapshot = this.pendingSnapshots[requestID];
2440   if (snapshot == null) {
2441     throw new Error("[SNAPSHOT_MANAGER] Received server node list snapshot for unknown "
2442                     + "request ID: [" + requestID + "]");
2443   }
2444   snapshot.setNodeList(nodeList);
2445   this.setLoaded(snapshot, requestID);
2446 };
2447 
2448 
2449 //==============================================================================
2450 // RECEIVE GATEWAYS SNAPSHOT
2451 //==============================================================================    
2452 
2453 net.user1.orbiter.SnapshotManager.prototype.receiveGatewaysSnapshot = function (requestID, gateways) {
2454   var snapshot = this.pendingSnapshots[requestID];
2455   if (snapshot == null) {
2456     throw new Error("[SNAPSHOT_MANAGER] Received gateways snapshot for unknown "
2457                     + "request ID: [" + requestID + "]");
2458   }
2459   snapshot.setGateways(gateways);
2460   this.setLoaded(snapshot, requestID);
2461 };
2462 
2463 //==============================================================================
2464 // LOADED AND STATUS ASSIGNMENT
2465 //==============================================================================    
2466 
2467 net.user1.orbiter.SnapshotManager.prototype.setLoaded = function (snapshot, requestID) {
2468   snapshot.loaded = true;
2469   if (snapshot.hasStatus == false
2470       || (snapshot.hasStatus == true && snapshot.statusReceived)) {
2471     snapshot.setUpdateInProgress(false);
2472     delete this.pendingSnapshots[requestID];
2473   }
2474   
2475   if (snapshot.hasOwnProperty("onLoad")) {
2476     snapshot["onLoad"]();
2477   }
2478   snapshot.dispatchLoaded();
2479 };
2480     
2481 net.user1.orbiter.SnapshotManager.prototype.setStatusReceived = function (snapshot, requestID) {
2482   if (snapshot.loaded) {
2483     snapshot.setUpdateInProgress(false);
2484     delete this.pendingSnapshots[requestID];
2485   }
2486   snapshot.dispatchStatus();
2487 };
2488 
2489 
2490 
2491 
2492 
2493 
2494 
2495 
2496 
2497 
2498 
2499 
2500 
2501 
2502 
2503 
2504 
2505 
2506 //==============================================================================
2507 // BOOLEAN GROUP TYPE CONSTANTS
2508 //==============================================================================
2509 /** @class */
2510 net.user1.orbiter.filters.BooleanGroupType = new Object();
2511 /** @constant */
2512 net.user1.orbiter.filters.BooleanGroupType.AND = "AND";
2513 /** @constant */
2514 net.user1.orbiter.filters.BooleanGroupType.OR = "OR";
2515 //==============================================================================
2516 // CLASS DECLARATION
2517 //==============================================================================
2518 /** 
2519  * @class
2520  */
2521 net.user1.orbiter.filters.BooleanGroup = function (type) {
2522   this.type = type;
2523   this.comparisons = new Array();
2524 };
2525 
2526 net.user1.orbiter.filters.BooleanGroup.prototype.addComparison = function (comparison) {
2527   if (comparison == null) return;
2528   this.comparisons.push(comparison);
2529 };
2530 
2531 net.user1.orbiter.filters.BooleanGroup.prototype.toXMLString = function () {
2532   var s = type == net.user1.orbiter.filters.BooleanGroupType.AND ? "<and>\n" : "<or>\n";
2533   
2534   var comparison;
2535   for (var i = 0; i < this.comparisons.length; i++) {
2536     comparison = this.comparisons[i];
2537     s += comparison.toXMLString() + "\n";
2538   }
2539   s += this.type == net.user1.orbiter.filters.BooleanGroupType.AND ? "</and>" : "</or>";
2540   return s;
2541 }
2542 //==============================================================================
2543 // CLASS DECLARATION
2544 //==============================================================================
2545 /** 
2546  * @class
2547  * @extends net.user1.events.Event
2548  */
2549 net.user1.orbiter.AccountEvent = function (type, 
2550                                            status,
2551                                            userID,
2552                                            clientID,
2553                                            role) {
2554   net.user1.events.Event.call(this, type);
2555   
2556   this.status = status;
2557   this.userID = userID;
2558   this.clientID = clientID;
2559   this.role = role;
2560   this.account = null;
2561 };
2562 
2563 //==============================================================================
2564 // INHERITANCE
2565 //==============================================================================
2566 net.user1.utils.extend(net.user1.orbiter.AccountEvent, net.user1.events.Event);
2567 
2568 //==============================================================================
2569 // STATIC VARIABLES
2570 //==============================================================================
2571 
2572 /** @constant */
2573 net.user1.orbiter.AccountEvent.LOGIN_RESULT = "LOGIN_RESULT";
2574 /** @constant */
2575 net.user1.orbiter.AccountEvent.LOGIN  = "LOGIN";
2576 /** @constant */
2577 net.user1.orbiter.AccountEvent.LOGOFF_RESULT = "LOGOFF_RESULT";
2578 /** @constant */
2579 net.user1.orbiter.AccountEvent.LOGOFF = "LOGOFF";
2580 /** @constant */
2581 net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT = "CHANGE_PASSWORD_RESULT";
2582 /** @constant */
2583 net.user1.orbiter.AccountEvent.CHANGE_PASSWORD = "CHANGE_PASSWORD";
2584 /** @constant */
2585 net.user1.orbiter.AccountEvent.OBSERVE = "OBSERVE";
2586 /** @constant */
2587 net.user1.orbiter.AccountEvent.STOP_OBSERVING = "STOP_OBSERVING";
2588 /** @constant */
2589 net.user1.orbiter.AccountEvent.OBSERVE_RESULT = "OBSERVE_RESULT";
2590 /** @constant */
2591 net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";    
2592 /** @constant */
2593 net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT = "ADD_ROLE_RESULT";
2594 /** @constant */
2595 net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT = "REMOVE_ROLE_RESULT";
2596 /** @constant */
2597 net.user1.orbiter.AccountEvent.SYNCHRONIZE = "SYNCHRONIZE";
2598 
2599   
2600 //==============================================================================
2601 // INSTANCE METHODS
2602 //==============================================================================    
2603 net.user1.orbiter.AccountEvent.prototype.getAccount = function () {
2604   if (this.target instanceof net.user1.orbiter.AccountManager) {
2605     return this.account;
2606   } else if (this.target instanceof net.user1.orbiter.UserAccount) {
2607     return this.target;
2608   } else {
2609     throw new Error("[AccountEvent] Unexpected target type: " + this.target);
2610   }
2611 };
2612 
2613 /**
2614  * @private
2615  */    
2616 net.user1.orbiter.AccountEvent.prototype.setAccount = function (value) {
2617   this.account = value;
2618 };
2619 
2620 net.user1.orbiter.AccountEvent.prototype.getUserID = function () {
2621   return this.userID;
2622 };
2623 
2624 net.user1.orbiter.AccountEvent.prototype.getRole = function () {
2625   return this.role;
2626 };
2627 
2628 net.user1.orbiter.AccountEvent.prototype.getClientID = function () {
2629   return this.clientID;
2630 };
2631 
2632 net.user1.orbiter.AccountEvent.prototype.getStatus = function () {
2633   return this.status;
2634 };
2635 
2636 net.user1.orbiter.AccountEvent.prototype.toString = function () {
2637   return "[object AccountEvent]";
2638 };
2639 //==============================================================================
2640 // CLASS DECLARATION
2641 //==============================================================================
2642 /** 
2643  * @class
2644  * @extends net.user1.orbiter.snapshot.Snapshot
2645  */
2646 net.user1.orbiter.snapshot.AccountListSnapshot = function () {
2647   // Call superconstructor
2648   net.user1.orbiter.snapshot.Snapshot.call(this);
2649   this.accountList = null;
2650   this.method = net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT;
2651 };
2652 
2653 //==============================================================================
2654 // INHERITANCE
2655 //==============================================================================
2656 net.user1.utils.extend(net.user1.orbiter.snapshot.AccountListSnapshot, net.user1.orbiter.snapshot.Snapshot);
2657     
2658 //==============================================================================
2659 // INSTANCE METHODS
2660 //==============================================================================
2661 /**
2662  * @private
2663  */    
2664 net.user1.orbiter.snapshot.AccountListSnapshot.prototype.setAccountList = function (value) {
2665   this.accountList = value;
2666 }
2667 
2668 net.user1.orbiter.snapshot.AccountListSnapshot.prototype.getAccountList = function () {
2669   if (!this.accountList) {
2670     return null;
2671   }
2672   return this.accountList.slice();
2673 }
2674 //==============================================================================
2675 // CLASS DECLARATION
2676 //==============================================================================
2677 /** @class
2678 
2679 The AccountManager class dispatches the following events:
2680 
2681 <ul class="summary">
2682 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT}</li>
2683 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT}</li>
2684 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT}</li>
2685 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED}</li>
2686 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED}</li>
2687 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF_RESULT}</li>
2688 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>
2689 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN_RESULT}</li>
2690 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>
2691 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD}</li>
2692 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE}</li>
2693 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING}</li>
2694 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT}</li>
2695 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT}</li>
2696 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE_RESULT}</li>
2697 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT}</li>
2698 <li class="fixedFont">{@link net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE}</li>
2699 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT}</li>
2700 <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT}</li>
2701 </ul>
2702 
2703 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
2704 
2705     @extends net.user1.events.EventDispatcher
2706 */
2707 net.user1.orbiter.AccountManager = function (log) {
2708   // Call superconstructor
2709   net.user1.events.EventDispatcher.call(this);
2710   
2711   this.watchedAccounts   = new net.user1.orbiter.AccountSet();
2712   this.observedAccounts  = new net.user1.orbiter.AccountSet();
2713   this.accountCache      = new net.user1.utils.LRUCache(10000);
2714   this.log               = log;
2715   this._isWatchingForAccounts = false;
2716   this.accountCache;
2717   this.messageManager;
2718   this.clientManager;
2719   this.roomManager;
2720 };
2721 
2722 //==============================================================================    
2723 // INHERITANCE
2724 //============================================================================== 
2725 net.user1.utils.extend(net.user1.orbiter.AccountManager, net.user1.events.EventDispatcher);
2726 
2727 // =============================================================================
2728 // DEPENDENCIES
2729 // =============================================================================
2730     /**
2731      * @private
2732      */    
2733 net.user1.orbiter.AccountManager.prototype.setMessageManager = function (value) {
2734   this.messageManager = value;
2735 }
2736 
2737 /**
2738  * @private
2739  */    
2740 net.user1.orbiter.AccountManager.prototype.setClientManager = function (value) {
2741   this.clientManager = value;
2742 }
2743 
2744 /**
2745  * @private
2746  */    
2747 net.user1.orbiter.AccountManager.prototype.setRoomManager = function (value) {
2748   this.roomManager = value;
2749 }
2750     
2751 // =============================================================================
2752 // REMOTE ACCOUNT CREATION/REMOVAL
2753 // =============================================================================
2754     
2755 net.user1.orbiter.AccountManager.prototype.createAccount = function (userID, password) {
2756   if (userID == null || userID == "") {
2757     this.log.warn("[ACCOUNT_MANAGER] Create account failed. No userID supplied.");
2758   } else if (password == null) {
2759     this.log.warn("[ACCOUNT_MANAGER] Create account failed. No password supplied.");
2760   } else {
2761     this.messageManager.sendUPC(net.user1.orbiter.UPC.CREATE_ACCOUNT, userID, password);
2762   }
2763 };
2764     
2765 net.user1.orbiter.AccountManager.prototype.removeAccount = function (userID, password) {
2766   if (userID == null || userID == "") {
2767     this.log.warn("[ACCOUNT_MANAGER] Remove account failed. No userID supplied.");
2768   } else {
2769     if (password == null) {
2770       this.log.warn("[ACCOUNT_MANAGER] Remove account: no password supplied." +
2771                             " Removal will fail unless sender is an administrator.");
2772     } 
2773     this.messageManager.sendUPC(net.user1.orbiter.UPC.REMOVE_ACCOUNT, userID, password);
2774   }
2775 }  
2776     
2777 // =============================================================================
2778 // CHANGE PASSWORD
2779 // =============================================================================
2780     
2781 net.user1.orbiter.AccountManager.prototype.changePassword = function (userID, newPassword, oldPassword) {
2782   if (userID == null || userID == "") {
2783     this.log.warn("[ACCOUNT_MANAGER] Change password failed. No userID supplied.");
2784   } else if (newPassword == null || newPassword == "") {
2785     this.log.warn("[ACCOUNT_MANAGER] Change password failed for account [" 
2786                           + userID + "]. No new password supplied.");
2787   } else {
2788     if (oldPassword == null || oldPassword == "") {
2789       this.log.warn("[ACCOUNT_MANAGER] Change account password for account ["
2790                             + userID + "]: no old password supplied."
2791                             + " Operation will fail unless sender is an administrator.");
2792       oldPassword = "";
2793     }
2794     this.messageManager.sendUPC(net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD, userID, oldPassword, newPassword);
2795   }
2796 };
2797     
2798 // =============================================================================
2799 // ADD/REMOVE ROLE
2800 // =============================================================================
2801     
2802 net.user1.orbiter.AccountManager.prototype.addRole = function (userID, role) {
2803   if (userID == null || userID == "") {
2804     this.log.warn("[ACCOUNT_MANAGER] Add role failed. No userID supplied.");
2805   } else if (role == null || role == "") {
2806     this.log.warn("[ACCOUNT_MANAGER] Add role failed for account [" 
2807                           + userID + "]. No role supplied.");
2808   } else {
2809     this.messageManager.sendUPC(net.user1.orbiter.UPC.ADD_ROLE, userID, role);
2810   }
2811 };
2812     
2813 net.user1.orbiter.AccountManager.prototype.removeRole = function (userID, role) {
2814   if (userID == null || userID == "") {
2815     this.log.warn("[ACCOUNT_MANAGER] Remove role failed. No userID supplied.");
2816   } else if (role == null || role == "") {
2817     this.log.warn("[ACCOUNT_MANAGER] Remove role failed for account [" 
2818                           + userID + "]. No role supplied.");
2819   } else {
2820     this.messageManager.sendUPC(net.user1.orbiter.UPC.REMOVE_ROLE, userID, role);
2821   }
2822 };
2823     
2824 // =============================================================================
2825 // LOCAL ACCOUNT CREATION/REMOVAL
2826 // =============================================================================
2827     
2828 /**
2829  * @private
2830  */        
2831 net.user1.orbiter.AccountManager.prototype.requestAccount = function (userID) {
2832   var account;
2833   
2834   if (userID == null || userID == "") {
2835     return null;
2836   } else {
2837     account = this.getAccount(userID);
2838     if (account == null) {
2839       account = new net.user1.orbiter.UserAccount(userID, this.log, this, this.clientManager, this.roomManager);
2840       account.setAttributeManager(new net.user1.orbiter.AttributeManager(account, this.messageManager, this.log));
2841       this.accountCache.put(userID, account);
2842     }
2843     return account;
2844   }
2845 };
2846     
2847 /**
2848  * @private
2849  */            
2850 net.user1.orbiter.AccountManager.prototype.deserializeWatchedAccounts = function (ids) {
2851   var idList = ids.split(Tokens.RS);
2852   var idHash = new net.user1.utils.UDictionary();
2853   var len = idList.length;
2854   
2855   // Generate a hash of clientID keys to dummy values for quick lookup
2856   for (var i = len; --i >= 0;) {
2857     idHash[idList[i]] = 1; 
2858   }
2859   
2860   // Remove all local accounts that are not in the new list from the server
2861   var accountStillExists;
2862   for (var accountID in watchedAccounts.getAll()) {
2863     if (!idHash.hasOwnProperty(accountID)) {
2864       removeWatchedAccount(accountID);
2865     }
2866   }
2867       
2868   // Add accounts from the new list that are not known locally. Do not add
2869   // clients for the accounts because "watch for accounts" does not
2870   // include client knowledge.
2871   if (ids != "") {  // Empty string means no accounts are on the server
2872     for (accountID in idHash) {
2873       if (accountID != "") {
2874         if (!this.watchedAccounts.containsUserID(accountID)) { 
2875           this.addWatchedAccount(this.requestAccount(accountID));
2876         }
2877       } else {
2878         throw new Error("[CORE_MESSAGE_LISTENER] Received empty account id in user list (u127).");
2879       }
2880     }
2881   }
2882   
2883   this.fireSynchronize();
2884 };
2885     
2886 // =============================================================================
2887 // OBSERVED ACCOUNTS
2888 // =============================================================================
2889     
2890 net.user1.orbiter.AccountManager.prototype.observeAccount = function (userID) {
2891   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_ACCOUNT, userID);
2892 };      
2893     
2894 // This method is internal because the developer is expected to access
2895 // stopObserving() through the UserAccount directly. AccountManager's 
2896 // observeAccount() exists only to allow developers to observe a
2897 // user that is currently unknown.
2898 /**
2899  * @private
2900  */        
2901 net.user1.orbiter.AccountManager.prototype.stopObservingAccount = function (userID) {
2902   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT, userID);
2903 };      
2904     
2905 /**
2906  * @private
2907  */        
2908 net.user1.orbiter.AccountManager.prototype.addObservedAccount = function (account) {
2909   this.observedAccounts.add(account);
2910   this.fireObserveAccount(account.getUserID());
2911 }
2912     
2913 /**
2914  * @private
2915  */        
2916 net.user1.orbiter.AccountManager.prototype.removeObservedAccount = function (userID) {
2917   var account = this.observedAccounts.removeByUserID(userID);
2918   this.fireStopObservingAccount(userID);
2919   return account;
2920 }
2921     
2922 /**
2923  * @private
2924  */        
2925 net.user1.orbiter.AccountManager.prototype.removeAllObservedAccounts = function () {
2926   this.observedAccounts.removeAll();
2927 }
2928 
2929 net.user1.orbiter.AccountManager.prototype.isObservingAccount = function (userID) {
2930   return this.observedAccounts.containsUserID(userID);
2931 }
2932     
2933 //==============================================================================
2934 // WATCH FOR ACCOUNTS
2935 //==============================================================================
2936 
2937 net.user1.orbiter.AccountManager.prototype.watchForAccounts = function () {
2938   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS);
2939 }      
2940 
2941 net.user1.orbiter.AccountManager.prototype.stopWatchingForAccounts = function () {
2942   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT);
2943 }      
2944 
2945 net.user1.orbiter.AccountManager.prototype.isWatchingForAccounts = function () {
2946   return this._isWatchingForAccounts;
2947 }
2948     
2949 /**
2950  * @private
2951  */        
2952 net.user1.orbiter.AccountManager.prototype.setIsWatchingForAccounts = function (value) {
2953   this._isWatchingForAccounts = value;
2954 }
2955 
2956 /**
2957  * @private
2958  */        
2959 net.user1.orbiter.AccountManager.prototype.addWatchedAccount = function (account) {
2960   this.watchedAccounts.add(account);
2961   this.fireAccountAdded(account.getUserID(), account);
2962 }
2963 
2964 /**
2965  * @private
2966  */        
2967 net.user1.orbiter.AccountManager.prototype.removeWatchedAccount = function (userID) {
2968   return this.watchedAccounts.removeByUserID(userID);
2969 }
2970 
2971 /**
2972  * @private
2973  */        
2974 net.user1.orbiter.AccountManager.prototype.removeAllWatchedAccounts = function () {
2975   this.watchedAccounts.removeAll();
2976 }
2977 
2978 net.user1.orbiter.AccountManager.prototype.hasWatchedAccount = function (userID) {
2979   return this.watchedAccounts.containsUserID(userID);
2980 }
2981     
2982 // =============================================================================
2983 // CLIENT ACCESS
2984 // =============================================================================
2985         
2986 /**
2987  * @private
2988  */        
2989 net.user1.orbiter.AccountManager.prototype.getClientsForObservedAccounts = function () {
2990   var clients = new Object();
2991   var client;
2992   
2993   var accounts = this.observedAccounts.getAll();
2994   var account;
2995   for (var userID in accounts) {
2996     account = accounts[userID];
2997     client = account.getInternalClient();
2998     if (client != null) {
2999       clients[client.getClientID()] = client;
3000     }
3001   }
3002   
3003   return clients;
3004 }
3005     
3006 // =============================================================================
3007 // LOCAL ACCOUNT ACCESS
3008 // =============================================================================
3009     
3010 net.user1.orbiter.AccountManager.prototype.getAccount = function (userID) {
3011   // Look in account cache
3012   var account = this.accountCache.get(userID);
3013   if (account) {
3014     return account;
3015   }
3016   
3017   // Look in observed accounts
3018   account = this.observedAccounts.getByUserID(userID);
3019   if (account) {
3020     return account;
3021   }
3022   
3023   // Look in watched accounts
3024   account = this.watchedAccounts.getByUserID(userID);
3025   if (account) {
3026     return account;
3027   }
3028 
3029   // Look in connected accounts
3030   var connectedAccounts = new Object();
3031   var clients = this.clientManager.getInternalClients();
3032   var client;
3033   for (var clientID in clients) {
3034     account = clients[clientID].getAccount();
3035     if (account != null && account.getUserID() == userID) {
3036       return account;
3037     }
3038   }
3039   
3040   return null;
3041 };
3042     
3043 net.user1.orbiter.AccountManager.prototype.selfAccount = function () {
3044   return this.clientManager.self().getAccount();
3045 };
3046     
3047 net.user1.orbiter.AccountManager.prototype.getAccounts = function () {
3048   var connectedAccounts = new Object();
3049   var account;
3050   
3051   var clients = this.clientManager.getInternalClients();
3052   var client;
3053   for (var clientID in clients) {
3054     account = client.getAccount();
3055     if (account != null) {
3056       connectedAccounts[account.getUserID()] = account;
3057     }
3058   }
3059   
3060   return net.user1.utils.ObjectUtil.combine(connectedAccounts, this.observedAccounts.getAll(), this.watchedAccounts.getAll());
3061 };
3062     
3063 net.user1.orbiter.AccountManager.prototype.accountIsKnown = function (userID) {
3064   for (var knownUserID in this.getAccounts()) {
3065     if (knownUserID == userID) {
3066       return true;
3067     }
3068   }
3069   return false;
3070 };
3071     
3072 net.user1.orbiter.AccountManager.prototype.getNumAccounts = function () {
3073   return this.getAccounts().length;
3074 };
3075 
3076 net.user1.orbiter.AccountManager.prototype.getNumAccountsOnServer = function () {
3077   return this.watchedAccounts.length();
3078 };
3079     
3080 net.user1.orbiter.AccountManager.prototype.getNumLoggedInAccounts = function () {
3081   var count; 
3082   var account;
3083   var accounts = this.getAccounts();
3084   for (var userID in accounts) {
3085     account = accounts[userID];
3086     if (account.isLoggedIn()) {
3087       count++;
3088     }
3089   }
3090   return count;
3091 };
3092     
3093 // =============================================================================
3094 // LOGIN/LOGOFF
3095 // =============================================================================
3096 
3097 net.user1.orbiter.AccountManager.prototype.login = function (userID, password) {
3098   if (this.clientManager.self().getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN) {
3099     this.log.warn("[ACCOUNT_MANAGER] User [" + userID + "]: Login attempt" 
3100              + " ignored. Already logged in. Current client must logoff before"
3101              + " logging in again.");
3102     this.fireLoginResult(userID, net.user1.orbiter.Status.ERROR);
3103   } else if (userID == null) { 
3104     this.log.warn("[ACCOUNT_MANAGER] Login attempt" 
3105                   + " failed. No userID supplied.");
3106   } else if (password == null) {
3107     this.log.warn("[ACCOUNT_MANAGER] Login attempt failed for user " 
3108                           + "[" + userID + "] failed. No password supplied.");
3109   } else {
3110     this.messageManager.sendUPC(net.user1.orbiter.UPC.LOGIN, userID, password);
3111   }
3112 };
3113     
3114 net.user1.orbiter.AccountManager.prototype.logoff = function (userID, password) {
3115   if (userID == null) {
3116     // Current client
3117     if (this.clientManager.self().getConnectionState() != net.user1.orbiter.ConnectionState.LOGGED_IN) {
3118       this.log.warn("[ACCOUNT_MANAGER] Logoff failed. The current user is not logged in.");
3119     } else {
3120       this.clientManager.self().getAccount().logoff();
3121     }
3122   } else if (userID == "") {
3123     // Invalid client
3124     this.log.warn("[ACCOUNT_MANAGER] Logoff failed. Supplied userID must not be the empty string.");
3125   } else {
3126     // UserID supplied
3127     if (password == null || password == "") {
3128       if (this.clientManager.self().getConnectionState() != net.user1.orbiter.ConnectionState.LOGGED_IN) {
3129         this.log.warn("[ACCOUNT_MANAGER] Logoff: no password supplied." +
3130                       " Operation will fail unless sender is an administrator.");
3131       }
3132       password = "";
3133     }
3134     this.messageManager.sendUPC(net.user1.orbiter.UPC.LOGOFF, userID, password);
3135   }
3136 }  
3137     
3138 //==============================================================================
3139 // EVENT DISPATCHING
3140 //==============================================================================
3141     
3142 /**
3143  * @private
3144  */
3145 net.user1.orbiter.AccountManager.prototype.fireCreateAccountResult = function (userID, status) {
3146   var e = new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT, 
3147                                   userID, this.getAccount(userID), status);
3148   this.dispatchEvent(e);
3149 };
3150 
3151 /**
3152  * @private
3153  */
3154 net.user1.orbiter.AccountManager.prototype.fireRemoveAccountResult = function (userID, status) {
3155   var e = new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT, 
3156                                   userID, this.getAccount(userID), status);
3157   this.dispatchEvent(e);
3158 };
3159 
3160 /**
3161  * @private
3162  */
3163 net.user1.orbiter.AccountManager.prototype.fireChangePasswordResult = function (userID, status) {
3164   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT, 
3165                            status, userID);
3166   e.setAccount(this.getAccount(userID));
3167   this.dispatchEvent(e);
3168 };
3169 
3170 /**
3171  * @private
3172  */
3173 net.user1.orbiter.AccountManager.prototype.fireAccountAdded = function (userID, account) {
3174   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED, 
3175                                        userID, account));
3176 };   
3177 
3178 /**
3179  * @private
3180  */
3181 net.user1.orbiter.AccountManager.prototype.fireAccountRemoved = function (userID, account) {
3182   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED, 
3183                          userID, account));
3184 };
3185 
3186 /**
3187  * @private
3188  */
3189 net.user1.orbiter.AccountManager.prototype.fireLogoffResult = function (userID, status) {
3190   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF_RESULT, 
3191                            status, userID);
3192   e.setAccount(this.getAccount(userID));
3193   this.dispatchEvent(e);
3194 };  
3195 
3196 /**
3197  * @private
3198  */
3199 net.user1.orbiter.AccountManager.prototype.fireLogoff = function (account, clientID) {
3200   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
3201                            net.user1.orbiter.Status.SUCCESS, account.getUserID(), clientID);
3202   e.setAccount(account);
3203   this.dispatchEvent(e);
3204 };
3205 
3206 /**
3207  * @private
3208  */
3209 net.user1.orbiter.AccountManager.prototype.fireLoginResult = function (userID, status) {
3210   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN_RESULT,
3211                            status, userID);
3212   e.setAccount(this.getAccount(userID));
3213   this.dispatchEvent(e);
3214 };
3215 
3216 /**
3217  * @private
3218  */
3219 net.user1.orbiter.AccountManager.prototype.fireLogin = function (account, clientID) {
3220   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
3221                                              net.user1.orbiter.Status.SUCCESS, account.getUserID(), clientID);
3222   e.setAccount(account);
3223   this.dispatchEvent(e);
3224 };
3225 
3226 /**
3227  * @private
3228  */
3229 net.user1.orbiter.AccountManager.prototype.fireChangePassword = function (userID) {
3230   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
3231                                              net.user1.orbiter.Status.SUCCESS, userID);
3232   e.setAccount(this.getAccount(userID));
3233   this.dispatchEvent(e);
3234 };
3235 
3236 /**
3237  * @private
3238  */
3239 net.user1.orbiter.AccountManager.prototype.fireObserveAccount = function (userID) {
3240   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE, 
3241                            null, userID);
3242   e.setAccount(this.getAccount(userID));
3243   this.dispatchEvent(e);
3244 };
3245 
3246 /**
3247  * @private
3248  */
3249 net.user1.orbiter.AccountManager.prototype.fireStopObservingAccount = function (userID) {
3250   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING, 
3251                            null, userID);
3252   e.setAccount(this.getAccount(userID));
3253   this.dispatchEvent(e);
3254 };
3255 
3256 /**
3257  * @private
3258  */
3259 net.user1.orbiter.AccountManager.prototype.fireStopWatchingForAccountsResult = function (status) {
3260   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT, 
3261                      null, null, status));
3262 }    
3263 
3264 /**
3265  * @private
3266  */
3267 net.user1.orbiter.AccountManager.prototype.fireWatchForAccountsResult = function (status) {
3268   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT, 
3269                      null, null, status));
3270 };
3271 
3272 /**
3273  * @private
3274  */
3275 net.user1.orbiter.AccountManager.prototype.fireObserveAccountResult = function (userID, status) {
3276   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE_RESULT, 
3277                            status, userID);
3278   e.setAccount(this.getAccount(userID));
3279   this.dispatchEvent(e);
3280 };
3281 
3282 /**
3283  * @private
3284  */
3285 net.user1.orbiter.AccountManager.prototype.fireStopObservingAccountResult = function (userID, status) {
3286   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT, 
3287                            status, userID);
3288   e.setAccount(this.getAccount(userID));
3289   this.dispatchEvent(e);
3290 };
3291 
3292 /**
3293  * @private
3294  */
3295 net.user1.orbiter.AccountManager.prototype.fireAddRoleResult = function (userID, role, status) {
3296   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT, 
3297                            status, userID, null, role);
3298   e.setAccount(this.getAccount(userID));
3299   this.dispatchEvent(e);
3300 };
3301 
3302 /**
3303  * @private
3304  */
3305 net.user1.orbiter.AccountManager.prototype.fireRemoveRoleResult  = function (userID, role, status) {
3306   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT, 
3307                            status, userID, null, role);
3308   e.setAccount(this.getAccount(userID));
3309   this.dispatchEvent(e);
3310 };
3311 
3312 /**
3313  * @private
3314  */
3315 net.user1.orbiter.AccountManager.prototype.fireSynchronize = function () {
3316   this.dispatchEvent(new net.user1.orbiter.AccountManagerEvent(net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE));
3317 }    
3318     
3319 //==============================================================================
3320 // CLEANUP AND DISPOSAL
3321 //==============================================================================
3322 
3323 /**
3324  * @private
3325  */    
3326 net.user1.orbiter.AccountManager.prototype.cleanup = function () {
3327   this.log.info("[ACCOUNT_MANAGER] Cleaning resources.");
3328   this.removeAllObservedAccounts();
3329   this.removeAllWatchedAccounts();
3330   this.setIsWatchingForAccounts(false);
3331 };
3332     
3333 //==============================================================================
3334 // CLASS DECLARATION
3335 //==============================================================================
3336 /** @class
3337     @extends net.user1.events.Event
3338 */
3339 net.user1.orbiter.AccountManagerEvent = function (type, 
3340                                                   userID,
3341                                                   account,
3342                                                   status) {
3343   net.user1.events.Event.call(this, type);
3344   
3345   this.account = account;
3346   this.userID = userID;
3347   this.status = status;
3348 };
3349 
3350 //==============================================================================
3351 // INHERITANCE
3352 //==============================================================================
3353 net.user1.utils.extend(net.user1.orbiter.AccountManagerEvent, net.user1.events.Event);
3354 
3355 //==============================================================================
3356 // STATIC VARIABLES
3357 //==============================================================================
3358 
3359 /** @constant */
3360 net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT = "CREATE_ACCOUNT_RESULT";
3361 /** @constant */
3362 net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT = "REMOVE_ACCOUNT_RESULT";
3363 /** @constant */
3364 net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED = "ACCOUNT_ADDED";
3365 /** @constant */
3366 net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED = "ACCOUNT_REMOVED";    
3367 /** @constant */
3368 net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT = "WATCH_FOR_ACCOUNTS_RESULT";
3369 /** @constant */
3370 net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "STOP_WATCHING_FOR_ACCOUNTS_RESULT";
3371 /** @constant */
3372 net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE = "SYNCHRONIZE";    
3373 
3374 //==============================================================================
3375 // INSTANCE METHODS
3376 //==============================================================================   
3377 net.user1.orbiter.AccountManagerEvent.prototype.getStatus = function () {
3378   return this.status;
3379 };
3380    
3381 net.user1.orbiter.AccountManagerEvent.prototype.getUserID = function () {
3382   return this.userID;
3383 };
3384 
3385 net.user1.orbiter.AccountManagerEvent.prototype.getAccount = function () {
3386   return this.account;
3387 };
3388 
3389 net.user1.orbiter.AccountManagerEvent.prototype.toString = function () {
3390   return "[object AccountManagerEvent]";
3391 };
3392 //==============================================================================
3393 // CLASS DECLARATION
3394 //==============================================================================
3395 /**
3396  * @private
3397  */
3398 net.user1.orbiter.AccountSet = function () {
3399   this.accounts = new net.user1.utils.UDictionary();
3400 };
3401     
3402 net.user1.orbiter.AccountSet.prototype.add = function (account) {
3403   this.accounts[account.getUserID()] = account;
3404 };
3405 
3406 net.user1.orbiter.AccountSet.prototype.remove = function (account) {
3407   var account = this.accounts[account.getUserID()]; 
3408   delete this.accounts[account.getUserID()];
3409   return account;
3410 }
3411 
3412 net.user1.orbiter.AccountSet.prototype.removeAll = function () {
3413   this.accounts = new net.user1.utils.UDictionary();
3414 }
3415 
3416 net.user1.orbiter.AccountSet.prototype.removeByUserID = function (userID) {
3417   var account = this.accounts[userID]; 
3418   delete this.accounts[userID];
3419   return account;
3420 }
3421 
3422 net.user1.orbiter.AccountSet.prototype.contains = function (account) {
3423   return this.accounts[account.getUserID()] != null;
3424 }
3425 
3426 net.user1.orbiter.AccountSet.prototype.containsUserID = function (userID) {
3427   if (userID == "" || userID == null) {
3428     return false;
3429   }
3430   return this.getByUserID(userID) != null;
3431 }
3432 
3433 net.user1.orbiter.AccountSet.prototype.getByUserID = function (userID) {
3434   return this.accounts[userID];
3435 }
3436 
3437 net.user1.orbiter.AccountSet.prototype.getByClient = function (client) {
3438   var account;
3439   for (var userID in this.accounts) {
3440     account = this.accounts[userID];
3441     if (account.getInternalClient() == client) {
3442       return account;
3443     }
3444   }
3445   return null;
3446 }
3447 
3448 net.user1.orbiter.AccountSet.prototype.getAll = function () {
3449   return this.accounts;
3450 }
3451 
3452 net.user1.orbiter.AccountSet.prototype.length = function () {
3453   var count;
3454   for (var userID in this.accounts) {
3455     count++;
3456   }
3457   return count;
3458 }
3459 //==============================================================================
3460 // CLASS DECLARATION
3461 //==============================================================================
3462 /** 
3463  * @class
3464  * @extends net.user1.orbiter.snapshot.Snapshot
3465  */
3466 net.user1.orbiter.snapshot.AccountSnapshot = function (userID) {
3467   // Call superconstructor
3468   net.user1.orbiter.snapshot.Snapshot.call(this);
3469   this.manifest = null;
3470   this.method = net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT;
3471   this.args   = [userID];
3472   this.hasStatus = true;
3473 };
3474 
3475 //==============================================================================
3476 // INHERITANCE
3477 //==============================================================================
3478 net.user1.utils.extend(net.user1.orbiter.snapshot.AccountSnapshot, net.user1.orbiter.snapshot.Snapshot);
3479 
3480 //==============================================================================
3481 // INSTANCE METHODS
3482 //==============================================================================
3483 /**
3484  * @private
3485  */    
3486 net.user1.orbiter.snapshot.AccountSnapshot.prototype.setManifest = function (value) {
3487   this.manifest = value;
3488 };
3489 
3490 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getAttribute = function (name, scope) {
3491   if (!this.manifest) {
3492     return null;
3493   }
3494   return this.manifest.persistentAttributes.getAttribute(name, scope);
3495 };
3496 
3497 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getAttributes = function () {
3498   if (!this.manifest) {
3499     return null;
3500   }
3501   return this.manifest.persistentAttributes.getAll();
3502 };
3503 
3504 net.user1.orbiter.snapshot.AccountSnapshot.prototype.getUserID = function () {
3505   if (!this.manifest) {
3506     return null;
3507   }
3508   return this.manifest.userID;
3509 };
3510 //==============================================================================
3511 // CLASS DECLARATION
3512 //==============================================================================
3513 /** 
3514  * @class
3515  * @extends net.user1.orbiter.filters.BooleanGroup
3516  */
3517 net.user1.orbiter.filters.AndGroup = function () {
3518   net.user1.orbiter.filters.BooleanGroup.call(this, net.user1.orbiter.filters.BooleanGroupType.AND);
3519 };
3520 
3521 //==============================================================================
3522 // INHERITANCE
3523 //==============================================================================
3524 net.user1.utils.extend(net.user1.orbiter.filters.AndGroup, net.user1.orbiter.filters.BooleanGroup);
3525 //==============================================================================
3526 // CLASS DECLARATION
3527 //==============================================================================
3528 /** 
3529  * @class
3530  * @extends net.user1.orbiter.filters.AndGroup
3531  */
3532 net.user1.orbiter.filters.Filter = function (filterType) {
3533   net.user1.orbiter.filters.AndGroup.call(this);
3534   this.filterType = filterType;
3535 };
3536 
3537 //==============================================================================
3538 // INHERITANCE
3539 //==============================================================================
3540 net.user1.utils.extend(net.user1.orbiter.filters.Filter, net.user1.orbiter.filters.AndGroup);
3541 
3542 net.user1.orbiter.filters.Filter.prototype.toXMLString = function () {
3543   var s = '<f t="' + this.filterType + '">\n';
3544 
3545   var comparison;
3546   for (var i = 0; i < this.comparisons.length; i++) {
3547     comparison = this.comparisons[i];
3548     s += comparison.toXMLString() + "\n";
3549   }
3550   s += '</f>';
3551   return s;      
3552 };
3553 net.user1.utils.ArrayUtil.combine = function () {
3554   var source = arguments.length == 1 ? arguments[0] : arguments;
3555   var master = [];
3556   
3557   var array;
3558   var element;
3559   for (var i = 0; i < source.length; i++) {
3560     array = source[i];
3561     if (net.user1.utils.ArrayUtil.isArray(array)) {
3562       for (var j = 0; j < array.length; j++) {
3563         element = array[j];
3564         if (net.user1.utils.ArrayUtil.indexOf(master, element) == -1) {
3565           master.push(element);
3566         }
3567       }
3568     }
3569   }
3570   return master;
3571 };
3572 //==============================================================================
3573 // CLASS DECLARATION
3574 //==============================================================================
3575 /** 
3576  * @class
3577  */
3578 net.user1.orbiter.Attribute = function (name,
3579                                         value, 
3580                                         oldValue,
3581                                         scope,
3582                                         byClient) {
3583       /**
3584        * @field
3585        */
3586       this.name = name;
3587       /**
3588        * @field
3589        */
3590       this.value = value;
3591       /**
3592        * @field
3593        */
3594       this.oldValue = oldValue;
3595       /**
3596        * @field
3597        */
3598       this.scope = (scope == net.user1.orbiter.Tokens.GLOBAL_ATTR) || (scope == null) ? null : scope;
3599       /**
3600        * @field
3601        */
3602       this.byClient = byClient;
3603     }
3604 
3605 net.user1.orbiter.Attribute.prototype.toString = function () {
3606   return "Attribute: " + (this.scope == null ? "" : this.scope + ".") + this.name + " = " + this.value + "." + " Old value: " + this.oldValue;
3607 };
3608 //==============================================================================
3609 // CLASS DECLARATION
3610 //==============================================================================
3611 /** @class
3612 
3613 The AttributeCollection class dispatches the following events:
3614 
3615 <ul class="summary">
3616 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li>
3617 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li>
3618 </ul>
3619 
3620 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
3621 
3622     @extends net.user1.events.EventDispatcher
3623 */
3624 net.user1.orbiter.AttributeCollection = function () {
3625   // Call superconstructor
3626   net.user1.events.EventDispatcher.call(this);
3627   
3628   this.attributes = new Object();
3629 };
3630 
3631 //==============================================================================    
3632 // INHERITANCE
3633 //============================================================================== 
3634 net.user1.utils.extend(net.user1.orbiter.AttributeCollection, net.user1.events.EventDispatcher);
3635 
3636 // =============================================================================
3637 // ATTRIBUTE ASSIGNMENT
3638 // =============================================================================
3639 /**
3640  * @private
3641  */    
3642 net.user1.orbiter.AttributeCollection.prototype.setAttribute = function (name, value, scope, byClient) {
3643   var scopeExists
3644   var attrExists;
3645   var oldVal;
3646  
3647   // null scope means global scope
3648   scope = scope == null ? net.user1.orbiter.Tokens.GLOBAL_ATTR : scope;
3649   // Check if the scope and attr exist already
3650   scopeExists =  this.attributes.hasOwnProperty(scope);
3651   attrExists  = scopeExists ? this.attributes[scope].hasOwnProperty(name) : false;
3652   
3653   // Find old value, if any
3654   if (attrExists) {
3655     oldVal = this.attributes[scope][name];
3656     if (oldVal == value) {
3657       // Attribute value is unchanged, so abort
3658       return false;
3659     }
3660   }
3661 
3662   // Make the scope record if necessary
3663   if (!scopeExists) {
3664     this.attributes[scope] = new Object();
3665   }
3666   
3667   // Set the attribute value
3668   this.attributes[scope][name] = value;
3669   
3670   // Notify listeners
3671   this.fireUpdateAttribute(name, value, scope, oldVal, byClient);
3672   
3673   return true;
3674 };
3675 
3676 // =============================================================================
3677 // ATTRIBUTE DELETION
3678 // =============================================================================
3679 /**
3680  * @private
3681  */
3682 net.user1.orbiter.AttributeCollection.prototype.deleteAttribute = function (name, scope, byClient) {
3683   var lastAttr = true;
3684   var value;
3685 
3686   // If the attribute exists...
3687   if (this.attributes.hasOwnProperty(scope) 
3688       && this.attributes[scope].hasOwnProperty(name)) {
3689     value = this.attributes[scope][name];
3690     delete this.attributes[scope][name];
3691     // Check if this is the last attribute. If it is, remove the room scope object.
3692     for (var p in this.attributes[scope]) {
3693       lastAttr = false;
3694       break;
3695     }
3696     if (lastAttr) {
3697       delete this.attributes[scope];
3698     }
3699     
3700     // Notify listeners
3701     this.fireDeleteAttribute(name, value, scope, byClient);
3702     return true;
3703   }
3704   return false;
3705 };
3706 
3707 /**
3708  * @private
3709  */
3710 net.user1.orbiter.AttributeCollection.prototype.clear = function () {
3711   this.attributes = new Object();
3712 };
3713 
3714 // =============================================================================
3715 // ATTRIBUTE RETRIEVAL
3716 // =============================================================================
3717 
3718 net.user1.orbiter.AttributeCollection.prototype.getByScope = function (scope) {
3719   var obj = new Object();
3720 
3721   if (scope == null) {
3722     for (var attrscope in this.attributes) {
3723       obj[attrscope] = new Object();
3724       for (var attrname in this.attributes[attrscope]) {
3725         obj[attrscope][attrname] = this.attributes[attrscope][attrname];
3726       }
3727     }
3728   } else {
3729     for (var name in this.attributes[scope]) {
3730       obj[name] = this.attributes[scope][name];
3731     }
3732   }
3733 
3734   return obj;
3735 };
3736 
3737 net.user1.orbiter.AttributeCollection.prototype.getAttributesNamesForScope = function (scope) {
3738   var names = new Array();
3739   for (var name in this.attributes[scope]) {
3740     names.push(name);
3741   }
3742   return names;
3743 };
3744 
3745 net.user1.orbiter.AttributeCollection.prototype.getAll = function () {
3746   var attrs = new Object();
3747   for (var attrScope in this.attributes) {
3748     for (var attrName in this.attributes[attrScope]) {
3749       attrs[attrScope == net.user1.orbiter.Tokens.GLOBAL_ATTR ? attrName : (attrScope + "." + attrName)] = this.attributes[attrScope][attrName];
3750     }
3751   }
3752   return attrs;
3753 }  
3754 
3755 net.user1.orbiter.AttributeCollection.prototype.getAttribute = function (attrName, attrScope) {
3756   // Use the global scope when no scope is specified
3757   if (attrScope == null) {
3758     attrScope = net.user1.orbiter.Tokens.GLOBAL_ATTR;
3759   }
3760   
3761   // Find and return the attribute.
3762   if (this.attributes.hasOwnProperty(attrScope) 
3763       && this.attributes[attrScope].hasOwnProperty(attrName)) {
3764     return this.attributes[attrScope][attrName];
3765   } else {
3766     // No attribute was found, so quit.
3767     return null;
3768   }
3769 };
3770 
3771 net.user1.orbiter.AttributeCollection.prototype.getScopes = function () {
3772   var scopes = new Array();
3773   for (var scope in this.attributes) {
3774     scopes.push(scope);
3775   }
3776   return scopes;
3777 };
3778 
3779 // =============================================================================
3780 // COLLECTION INSPECTION
3781 // =============================================================================
3782 
3783 net.user1.orbiter.AttributeCollection.prototype.contains = function (name, scope) {
3784   return this.attributes.hasOwnProperty(scope) ? this.attributes[scope].hasOwnProperty(name) : false;
3785 };
3786 
3787 // =============================================================================
3788 // MERGING
3789 // =============================================================================
3790 
3791 /**
3792  * @private
3793  */    
3794 net.user1.orbiter.AttributeCollection.prototype.add = function (collection) {
3795   var scopes = collection.getScopes();
3796   var scope;
3797   
3798   var names;
3799   var name;
3800   
3801   for (var i = 0; i <= scopes.length; i++) {
3802     scope = scopes[i];
3803     names = collection.getAttributesNamesForScope(scope);
3804     for (var j = 0; j < names.length; j++) {
3805       name = names[j];
3806       this.setAttribute(name, collection.getAttribute(name, scope), scope);
3807     }
3808   }
3809 };
3810 
3811 /**
3812  * @private
3813  */    
3814 net.user1.orbiter.AttributeCollection.prototype.synchronizeScope = function (scope,
3815                                                                              collection) {
3816   // Delete all existing attributes that are not in the new collection
3817   var names = this.getAttributesNamesForScope(scope);
3818   var name;
3819   
3820   for (var i = 0; i < names.length; i++) {
3821     name = names[i];
3822     if (!collection.contains(name, scope)) {
3823       this.deleteAttribute(name, scope);
3824     }
3825   }
3826   
3827   // Set all new attributes (unchanged attributes are ignored)
3828   var names = collection.getAttributesNamesForScope(scope);
3829   for (i = 0; i < names.length; i++) {
3830     name = names[i];
3831     this.setAttribute(name, collection.getAttribute(name, scope), scope);
3832   } 
3833 };
3834 
3835 // =============================================================================
3836 // EVENT DISPATCHING
3837 // =============================================================================
3838 /**
3839  * @private
3840  */
3841 net.user1.orbiter.AttributeCollection.prototype.fireUpdateAttribute = function (attrName, 
3842                                                                                 attrVal, 
3843                                                                                 attrScope,
3844                                                                                 oldVal,
3845                                                                                 byClient) {
3846   var changedAttr = new net.user1.orbiter.Attribute(attrName, attrVal, oldVal, attrScope, byClient);
3847   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.UPDATE,
3848                                                changedAttr);
3849   this.dispatchEvent(e);
3850 };
3851 
3852 /**
3853  * @private
3854  */
3855 net.user1.orbiter.AttributeCollection.prototype.fireDeleteAttribute = function (attrName,
3856                                                                                 attrValue,
3857                                                                                 attrScope,
3858                                                                                 byClient) {
3859   var changedAttr = new net.user1.orbiter.Attribute(attrName, null, attrValue, attrScope, byClient);
3860   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.DELETE,
3861                                                changedAttr);
3862   this.dispatchEvent(e);
3863 };
3864 //==============================================================================
3865 // CLASS DECLARATION
3866 //==============================================================================
3867 /** 
3868  * @class
3869  */
3870 net.user1.orbiter.filters.AttributeComparison = function (name,
3871                                                           value,
3872                                                           compareType) {
3873   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
3874     throw new Error("Invalid attribute name specified for AttributeComparison: "
3875                     + name);
3876   }                                           
3877   this.name = name;
3878   this.value = value;
3879   this.compareType = compareType;
3880 };
3881     
3882 net.user1.orbiter.filters.AttributeComparison.prototype.toXMLString = function () {
3883   return '<a c="' + this.compareType + '"><n><![CDATA[' + this.name + ']]></n><v><![CDATA[' + this.value.toString() + ']]></v></a>';
3884 };
3885 //==============================================================================
3886 // CLASS DECLARATION
3887 //==============================================================================
3888 /** @class
3889     @extends net.user1.events.Event
3890 */
3891 net.user1.orbiter.AttributeEvent = function (type, 
3892                                              changedAttr,
3893                                              status) {
3894   net.user1.events.Event.call(this, type);
3895   
3896   this.changedAttr = changedAttr;
3897   this.status = status;
3898 };
3899 
3900 //==============================================================================
3901 // INHERITANCE
3902 //==============================================================================
3903 net.user1.utils.extend(net.user1.orbiter.AttributeEvent, net.user1.events.Event);
3904 
3905 //==============================================================================
3906 // STATIC VARIABLES
3907 //==============================================================================
3908 
3909 /** @constant */
3910 net.user1.orbiter.AttributeEvent.UPDATE = "UPDATE";
3911 /** @constant */
3912 net.user1.orbiter.AttributeEvent.DELETE = "DELETE";
3913 /** @constant */
3914 net.user1.orbiter.AttributeEvent.DELETE_RESULT = "DELETE_RESULT";
3915 /** @constant */
3916 net.user1.orbiter.AttributeEvent.SET_RESULT = "SET_RESULT";
3917 
3918 //==============================================================================
3919 // INSTANCE METHODS
3920 //==============================================================================   
3921 net.user1.orbiter.AttributeEvent.prototype.getChangedAttr = function () {
3922   return this.changedAttr;
3923 }
3924 
3925 net.user1.orbiter.AttributeEvent.prototype.getStatus = function () {
3926   return this.status;
3927 }
3928 
3929 net.user1.orbiter.AttributeEvent.prototype.toString = function () {
3930   return "[object AttributeEvent]";
3931 }  
3932 //==============================================================================
3933 // CLASS DECLARATION
3934 //==============================================================================
3935 /** 
3936  * @class
3937  * @extends net.user1.orbiter.filters.Filter
3938  */
3939 net.user1.orbiter.filters.AttributeFilter = function () {
3940   net.user1.orbiter.filters.Filter.call(this, "A");
3941 };
3942 
3943 
3944 //==============================================================================
3945 // INHERITANCE
3946 //==============================================================================
3947 net.user1.utils.extend(net.user1.orbiter.filters.AttributeFilter, net.user1.orbiter.filters.Filter);
3948 //==============================================================================
3949 // CLASS DECLARATION
3950 //==============================================================================
3951 /**
3952 * @private
3953 */
3954 net.user1.orbiter.AttributeManager = function (owner,
3955                                                messageManager,
3956                                                log) {
3957   // Call superconstructor
3958   net.user1.events.EventDispatcher.call(this);
3959   
3960   this.attributes = null;
3961   this.owner = owner;
3962   this.messageManager = messageManager;
3963   this.log = log;
3964   this.setAttributeCollection(new net.user1.orbiter.AttributeCollection());
3965 };
3966 
3967 //==============================================================================    
3968 // INHERITANCE
3969 //============================================================================== 
3970 net.user1.utils.extend(net.user1.orbiter.AttributeManager, net.user1.events.EventDispatcher);
3971 
3972 //==============================================================================
3973 // DEPENDENCIES
3974 //==============================================================================
3975 
3976 net.user1.orbiter.AttributeManager.prototype.getAttributeCollection = function () {
3977   return this.attributes;
3978 };
3979 
3980 net.user1.orbiter.AttributeManager.prototype.setAttributeCollection = function (value) {
3981   this.unregisterAttributeListeners();
3982   this.attributes = value;
3983   this.registerAttributeListeners();
3984 };
3985 
3986 //==============================================================================
3987 // SERVER-SIDE ASSIGNMENT
3988 //==============================================================================
3989 
3990 net.user1.orbiter.AttributeManager.prototype.setAttribute = function (setRequest) {
3991   this.messageManager.sendUPCObject(setRequest);
3992 }
3993 
3994 //==============================================================================
3995 // SERVER-SIDE DELETION
3996 //==============================================================================
3997 
3998 net.user1.orbiter.AttributeManager.prototype.deleteAttribute = function (deleteRequest) {
3999   this.messageManager.sendUPCObject(deleteRequest);
4000 }
4001 
4002 //==============================================================================
4003 // LOCAL RETRIEVAL
4004 //==============================================================================
4005 
4006 net.user1.orbiter.AttributeManager.prototype.getAttribute = function (attrName, attrScope) {
4007   // Quit if there are no attrbutes.
4008   if (this.attributes == null) {
4009     return null;
4010   } else {
4011     return this.attributes.getAttribute(attrName, attrScope);
4012   }
4013 };
4014 
4015 net.user1.orbiter.AttributeManager.prototype.getAttributes = function () {
4016   return this.attributes.getAll();
4017 }  
4018 
4019 net.user1.orbiter.AttributeManager.prototype.getAttributesByScope = function (scope) {
4020   return this.attributes.getByScope(scope);
4021 };
4022 
4023 //==============================================================================
4024 // LOCAL ASSIGNMENT
4025 //==============================================================================
4026 
4027 /**
4028  * @private
4029  */        
4030 net.user1.orbiter.AttributeManager.prototype.setAttributeLocal = function (attrName, 
4031                                                                            attrVal,
4032                                                                            attrScope,
4033                                                                            byClient) {
4034   var changed = this.attributes.setAttribute(attrName, attrVal, attrScope, byClient);
4035   if (!changed) {
4036     this.log.info(this.owner + " New attribute value for [" + attrName + "] matches old value. Not changed.");
4037   }
4038 };
4039 
4040 //==============================================================================
4041 // LOCAL REMOVAL
4042 //==============================================================================
4043   
4044 /**
4045  * @private
4046  */        
4047 net.user1.orbiter.AttributeManager.prototype.removeAttributeLocal = function (attrName,
4048                                                                               attrScope,
4049                                                                               byClient) {
4050   var deleted = this.attributes.deleteAttribute(attrName, attrScope, byClient);
4051   if (!deleted) {
4052     this.log.info(owner + " Delete attribute failed for [" + attrName + "]. No such attribute.");
4053   }
4054 };
4055   
4056 /**
4057  * @private
4058  */        
4059 net.user1.orbiter.AttributeManager.prototype.removeAll = function () {
4060   this.attributes.clear();
4061 }
4062 
4063 //==============================================================================
4064 // EVENT REGISTRATION
4065 //==============================================================================
4066 
4067 net.user1.orbiter.AttributeManager.prototype.registerAttributeListeners = function () {
4068   if (this.attributes != null) {
4069     // Can't use migrateListeners() here because we need to specify the listener priority (int.MAX_VALUE)
4070     this.attributes.addEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateAttributeListener, this, net.user1.utils.integer.MAX_VALUE);
4071     this.attributes.addEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteAttributeListener, this, net.user1.utils.integer.MAX_VALUE);
4072   }
4073 };
4074 
4075 net.user1.orbiter.AttributeManager.prototype.unregisterAttributeListeners = function () {
4076   if (this.attributes != null) {
4077     this.attributes.removeEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateAttributeListener, this);
4078     this.attributes.removeEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteAttributeListener, this);
4079   }
4080 }
4081 
4082 //==============================================================================
4083 // EVENT LISTENERS
4084 //==============================================================================
4085 
4086 net.user1.orbiter.AttributeManager.prototype.updateAttributeListener = function (e) {
4087   var attr = e.getChangedAttr();
4088   
4089   this.log.info(this.owner + " Setting attribute [" 
4090                 + ((attr.scope == null) ? "" : attr.scope + ".") 
4091                 + attr.name + "]. New value: [" + attr.value + "]. Old value: [" 
4092                 + attr.oldValue + "].");
4093   this.owner.dispatchEvent(e);
4094 };
4095 
4096 net.user1.orbiter.AttributeManager.prototype.deleteAttributeListener = function (e) {
4097   this.owner.dispatchEvent(e);
4098 }
4099 
4100 //==============================================================================
4101 // EVENT DISPATCHING
4102 //==============================================================================
4103 
4104 /**
4105  * @private
4106  */        
4107 net.user1.orbiter.AttributeManager.prototype.fireSetAttributeResult = function (attrName,
4108                                                                                 attrScope,
4109                                                                                 status) {
4110   var attr = new net.user1.orbiter.Attribute(attrName, null, null, attrScope);
4111 
4112   // Trigger event on listeners.
4113   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.SET_RESULT,
4114                                                attr, status);
4115   this.owner.dispatchEvent(e);
4116 };
4117 
4118 /**
4119  * @private
4120  */
4121 net.user1.orbiter.AttributeManager.prototype.fireDeleteAttributeResult = function (attrName,
4122                                                                                    attrScope, 
4123                                                                                    status) {
4124   var attr = new net.user1.orbiter.Attribute(attrName, null, null, attrScope);
4125   
4126   // Trigger event on listeners.
4127   var e = new net.user1.orbiter.AttributeEvent(net.user1.orbiter.AttributeEvent.DELETE_RESULT,
4128                                                attr, status);
4129   this.owner.dispatchEvent(e);
4130 };
4131 
4132 // =============================================================================
4133 // DISPOSAL
4134 // =============================================================================
4135 
4136 /**
4137  * @private
4138  */
4139 net.user1.orbiter.AttributeManager.prototype.dispose = function () {
4140   this.messageManager = null;  
4141   this.attributes = null;
4142   this.owner = null;
4143   this.log = null;
4144 };
4145 //==============================================================================
4146 //  ATTRIBUTE_OPTIONS CONSTANTS
4147 //==============================================================================
4148 /** @class
4149     @private */
4150 net.user1.orbiter.AttributeOptions = new Object();
4151 
4152 /** @private */
4153 net.user1.orbiter.AttributeOptions.FLAG_SHARED     = 1 << 2;
4154 /** @private */
4155 net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT = 1 << 3;
4156 /** @private */
4157 net.user1.orbiter.AttributeOptions.FLAG_IMMUTABLE  = 1 << 5;
4158 /** @private */
4159 net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   = 1 << 8;
4160 //==============================================================================
4161 // CLASS DECLARATION
4162 //==============================================================================
4163 /** 
4164  * @class
4165  * @extends net.user1.orbiter.snapshot.Snapshot
4166  */
4167 net.user1.orbiter.snapshot.BannedListSnapshot = function () {
4168   // Call superconstructor
4169   net.user1.orbiter.snapshot.Snapshot.call(this);
4170   this.bannedList = null;
4171   this.method = net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT;
4172 }
4173 
4174 //==============================================================================
4175 // INHERITANCE
4176 //==============================================================================
4177 net.user1.utils.extend(net.user1.orbiter.snapshot.BannedListSnapshot, net.user1.orbiter.snapshot.Snapshot);
4178 
4179 //==============================================================================
4180 // INSTANCE METHODS
4181 //==============================================================================
4182 /**
4183  * @private
4184  */    
4185 net.user1.orbiter.snapshot.BannedListSnapshot.prototype.setBannedList = function (value) {
4186   this.bannedList = value;
4187 };
4188 
4189 net.user1.orbiter.snapshot.BannedListSnapshot.prototype.getBannedList = function () {
4190   if (!this.bannedList) {
4191     return null;
4192   }
4193   return this.bannedList.slice();
4194 };
4195 //==============================================================================
4196 // CLASS DECLARATION
4197 //==============================================================================
4198 /** 
4199  * @class
4200  */
4201 net.user1.utils.CacheNode = function () {
4202   /** @field */
4203   this.next;
4204   /** @field */
4205   this.prev;
4206   /** @field */
4207   this.key;
4208   /** @field */
4209   this.value;
4210 };
4211 //==============================================================================
4212 // CLASS DECLARATION
4213 //==============================================================================
4214 /** @class
4215 
4216 The Client class dispatches the following events:
4217 
4218 <ul class="summary">
4219   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.JOIN_ROOM}</li> 
4220   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.LEAVE_ROOM}</li> 
4221   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_ROOM}</li> 
4222   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM}</li> 
4223   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE}</li> 
4224   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING}</li> 
4225   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_RESULT}</li> 
4226   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT}</li> 
4227   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>    
4228   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>    
4229   <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.SYNCHRONIZE}</li> 
4230   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li> 
4231   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li> 
4232   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.SET_RESULT}</li> 
4233   <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE_RESULT}</li> 
4234 </ul>
4235 
4236 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
4237 
4238     @extends net.user1.events.EventDispatcher
4239 */
4240 net.user1.orbiter.Client = function (clientID, 
4241                                      clientManager,
4242                                      messageManager,
4243                                      roomManager,
4244                                      connectionManager,
4245                                      server,
4246                                      log) {
4247   // Call superconstructor
4248   net.user1.events.EventDispatcher.call(this);
4249 
4250   this.clientID = "";
4251   this._isSelf = false;
4252   this.account = null;
4253   this.disposed = false;
4254   
4255   this.messageManager    = messageManager;
4256   this.clientManager     = clientManager;
4257   this.roomManager       = roomManager;
4258   this.connectionManager = connectionManager;
4259   this.server            = server;
4260   this.log               = log;
4261   this.occupiedRoomIDs   = new Array();
4262   this.observedRoomIDs   = new Array();
4263   this.customClients     = new Object();
4264   this.attributeManager  = new net.user1.orbiter.AttributeManager(this, this.messageManager, this.log);
4265   this.connectionState   = net.user1.orbiter.ConnectionState.UNKNOWN;
4266 
4267   this.setClientID(clientID);
4268 };
4269 
4270 //==============================================================================    
4271 // INHERITANCE
4272 //============================================================================== 
4273 net.user1.utils.extend(net.user1.orbiter.Client, net.user1.events.EventDispatcher);
4274     
4275 //==============================================================================
4276 // STATIC VARIABLES
4277 //==============================================================================
4278 
4279 /** @private */    
4280 net.user1.orbiter.Client.FLAG_ADMIN = 1 << 2;
4281     
4282 // =============================================================================
4283 // CLIENT ID
4284 // =============================================================================
4285 net.user1.orbiter.Client.prototype.getClientID = function () {
4286   return this.clientID;
4287 };
4288 
4289 /**
4290  * @private
4291  */
4292 net.user1.orbiter.Client.prototype.setClientID = function (id) {
4293   if (this.clientID != id) {
4294     this.clientID = id;
4295   }
4296 };
4297 
4298 net.user1.orbiter.Client.prototype.isSelf = function () {
4299   return this._isSelf;
4300 };
4301 
4302 /**
4303  * @private
4304  */
4305 net.user1.orbiter.Client.prototype.setIsSelf = function () {
4306   this._isSelf = true;
4307 };
4308 
4309 // =============================================================================
4310 // CONNECTION STATUS
4311 // =============================================================================
4312 
4313 net.user1.orbiter.Client.prototype.getConnectionState = function () {
4314   if (this.isSelf()) {
4315     if (this.disposed
4316         || this.clientManager.getInternalClient(this.getClientID()) == null) {
4317       return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
4318     } else {
4319       return this.account != null ? this.account.getConnectionState() : this.connectionManager.getConnectionState();
4320     }
4321   } else {
4322     if (this.connectionState != net.user1.orbiter.ConnectionState.UNKNOWN) {
4323       return this.connectionState;
4324     } else if (this.disposed
4325                || this.clientManager.getInternalClient(this.getClientID()) == null) {
4326       return net.user1.orbiter.ConnectionState.UNKNOWN;
4327     } else {
4328       return this.account != null ? this.account.getConnectionState() : net.user1.orbiter.ConnectionState.READY;
4329     }
4330   }
4331 };
4332 
4333 // Normally, this client's connection state is not assigned directly; it 
4334 // it is deduced within getConnectionState(). But when Union
4335 // sends a u103, we know that this client has definitely disconnected from 
4336 // the server, and this client object will never be reused, so CoreMessageListener
4337 // permanently assigns its connection state to NOT_CONNECTED.
4338 net.user1.orbiter.Client.prototype.setConnectionState = function (newState) {
4339   this.connectionState = newState;
4340 };
4341 
4342 // =============================================================================
4343 // ROLES
4344 // =============================================================================
4345 net.user1.orbiter.Client.prototype.isAdmin = function () {
4346   var rolesAttr = this.getAttribute(Tokens.ROLES_ATTR);
4347   var roles;
4348   if (rolesAttr != null) {
4349     return parseInt(rolesAttr) & net.user1.orbiter.Client.FLAG_ADMIN;
4350   } else {
4351     this.log.warn("[" + this.toString() + "] Could not determine admin status because the client is not synchronized.");
4352     return false;
4353   }
4354 };
4355 
4356 // =============================================================================
4357 // OBSERVATION
4358 // =============================================================================
4359 
4360 net.user1.orbiter.Client.prototype.observe = function () {
4361   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_CLIENT, this.clientID);
4362 };
4363 
4364 net.user1.orbiter.Client.prototype.stopObserving = function () {
4365   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT, this.clientID);
4366 };
4367 
4368 // =============================================================================
4369 // KICK / BAN
4370 // =============================================================================
4371 
4372 net.user1.orbiter.Client.prototype.kick = function () {
4373   if (this.getClientID() == null) {
4374     this.log.warn(this + " Kick attempt failed. Client not currently connected.");
4375   }
4376   this.messageManager.sendUPC(net.user1.orbiter.UPC.KICK_CLIENT, getClientID());
4377 };
4378 
4379 net.user1.orbiter.Client.prototype.ban = function (duration, reason) {
4380   if (this.getClientID() == null) {
4381     this.log.warn(this + " Ban attempt failed. Client not currently connected.");
4382   }
4383   this.messageManager.sendUPC(net.user1.orbiter.UPC.BAN, null, getClientID(), duration.toString(), reason);
4384 };
4385 
4386 // =============================================================================
4387 // CUSTOM CLASS MANAGEMENT
4388 // =============================================================================
4389 
4390 net.user1.orbiter.Client.prototype.setClientClass = function (scope, 
4391                                                               clientClass) {
4392   var fallbackClasses = Array.prototype.slice.call(arguments).slice(2);
4393   if (!this.isSelf()) {
4394     throw new Error("Custom client class assignment failed for : "
4395                     + clientClass + ". A custom" 
4396                     + " class can be set for the current client ("
4397                     + " i.e., ClientManager.self()) only.");
4398   }
4399   
4400   fallbackClasses.unshift(clientClass);
4401   var classList = fallbackClasses.join(" ");
4402   setAttribute(Tokens.CUSTOM_CLASS_ATTR, classList, scope);
4403 };
4404 
4405 /**
4406  * @private
4407  */    
4408 net.user1.orbiter.Client.prototype.getCustomClient = function (scope) {
4409   var customClient;
4410 
4411   // If the custom client already exists for the specified scope, return it.
4412   customClient = this.customClients[scope];
4413   if (customClient != null) {
4414     return customClient;
4415   }
4416   
4417   // Look for a custom class for the given scope, and create a custom client
4418   if (scope == null) {
4419     return this.setGlobalCustomClient();
4420   } else {
4421     return this.setCustomClientForScope(scope);
4422   }
4423 };
4424 
4425 /**
4426  * @private
4427  */    
4428 net.user1.orbiter.Client.prototype.setGlobalCustomClient = function () {
4429   var defaultClientClass;
4430   var globalDefaultClientClass;
4431   var customClient;
4432   
4433   // If this client has a default custom client class, use it
4434   defaultClientClass = this.getClientClass(null);
4435   if (defaultClientClass != null) {
4436     return this.createCustomClient(defaultClientClass, null);
4437   }          
4438   
4439   // No global class was set on the client, so check for a system-wide default
4440   globalDefaultClientClass = this.clientManager.getDefaultClientClass();
4441   if (globalDefaultClientClass == null) {
4442     // No global custom client class exists
4443     return null;
4444   } else {
4445     // Global default class exists
4446     return this.createCustomClient(globalDefaultClientClass, null);
4447   }
4448 };
4449 
4450 /**
4451  * @private
4452  */    
4453 net.user1.orbiter.Client.prototype.setCustomClientForScope = function (scope) {
4454   var theRoom;
4455   var clientClass;
4456   var roomDefaultClientClass;
4457   var globalDefaultClientClass;
4458   
4459   // If this client has a default custom client class, use it
4460   clientClass = this.getClientClass(scope);
4461   if (clientClass != null) {
4462     return this.createCustomClient(clientClass, scope);
4463   }          
4464   
4465   // No class was set on the client for the scope, so check for a room default
4466   theRoom = this.roomManager.getRoom(scope);
4467   if (theRoom != null) {
4468     roomDefaultClientClass = theRoom.getDefaultClientClass();
4469     if (roomDefaultClientClass != null) {
4470       return this.createCustomClient(roomDefaultClientClass, scope);
4471     }
4472   }
4473   
4474   // No class was set on the room for the scope, so check for a system-wide default
4475   // If a custom global client already exists, return it.
4476   var customClient = this.customClients[null];
4477   if (customClient != null) {
4478     return customClient;
4479   } else {
4480     globalDefaultClientClass = this.clientManager.getDefaultClientClass();
4481     if (globalDefaultClientClass == null) {
4482       // No global custom client class exists
4483       return null;
4484     } else {
4485       // Global default class exists
4486       return this.createCustomClient(globalDefaultClientClass, null);
4487     }
4488   }
4489 };
4490 
4491 /**
4492  * @private
4493  */    
4494 net.user1.orbiter.Client.prototype.getClientClass = function (scope) {
4495   var clientClassNames = this.getAttribute(net.user1.orbiter.Tokens.CUSTOM_CLASS_ATTR, scope);
4496   var clientClassList;
4497 
4498   // Convert the custom class names to an array for processing
4499   if (clientClassNames != null) {
4500     clientClassList = clientClassNames.split(" ");
4501   }
4502 
4503   // Search for a matching class definition. The first definition that's 
4504   // found is returned.
4505   var className;
4506   if (clientClassList != null) {
4507     for (var i = 0; i < clientClassList.length; i++) {
4508       try {
4509         var theClass = net.user1.utils.resolveMemberExpression(className);
4510         if (!theClass instanceof Function) {
4511           this.log.debug(this.toString() + ": Definition for client class [" + className + "] is not a constructor function.");
4512           continue;
4513         }
4514         return theClass;
4515       } catch (e) {
4516         this.log.debug(this.toString() + ": No definition found for client class [" + className + "]");
4517         continue;
4518       }
4519     }
4520   }
4521   return null;
4522 };
4523 
4524 /**
4525  * @private
4526  */    
4527 net.user1.orbiter.Client.prototype.createCustomClient = function (wrapperClass, scope) {
4528   var customClient;
4529 
4530   // Wrap the client
4531   customClient = new wrapperClass();
4532   this.customClients[scope] = customClient;
4533   
4534   // Do custom client setup
4535   if (customClient instanceof CustomClient) {
4536     customClient.setClient(this);
4537     customClient.init();
4538     return customClient;
4539   } else {
4540     this.log.debug("[CLIENT_MANAGER] Custom client class [" + wrapperClass + "] does not "  
4541            + " extend CustomClient. Assuming specified class will manually "  
4542            + " compose its own Client instance for client ID: " + clientID
4543            + ". See Client.setClientClass()."); 
4544     return customClient;
4545   }
4546 };
4547 
4548 // =============================================================================
4549 // ROOM MANAGEMENT
4550 // =============================================================================
4551   
4552 /**
4553  * @private
4554  */
4555 net.user1.orbiter.Client.prototype.removeOccupiedRoomID = function (roomID) {
4556   if (this.isInRoom(roomID) && roomID != null) {
4557     this.occupiedRoomIDs.splice(net.user1.utils.ArrayUtil.indexOf(this.occupiedRoomIDs, roomID), 1);
4558     return true;
4559   } else {
4560     return false;
4561   }
4562 };
4563 
4564 /**
4565  * @private
4566  */
4567 net.user1.orbiter.Client.prototype.removeObservedRoomID = function (roomID) {
4568   if (this.isObservingRoom(roomID) && roomID != null) {
4569     this.observedRoomIDs.splice(net.user1.utils.ArrayUtil.indexOf(this.observedRoomIDs, roomID), 1);
4570     return true;
4571   } else {
4572     return false;
4573   }
4574 };
4575 
4576 /**
4577  * @private
4578  */
4579 net.user1.orbiter.Client.prototype.addOccupiedRoomID = function (roomID) {
4580   if (!this.isInRoom(roomID) && roomID != null) {
4581     this.log.info(this.toString() + " added occupied room ID [" + roomID + "].");
4582     this.occupiedRoomIDs.push(roomID);
4583   }
4584 };
4585 
4586 /**
4587  * @private
4588  */
4589 net.user1.orbiter.Client.prototype.addObservedRoomID = function (roomID) {
4590   if (!this.isObservingRoom(roomID) && roomID != null) {
4591     this.log.info("Client [" + this.getClientID() + "] added observed room ID [" + roomID + "].");
4592     this.observedRoomIDs.push(roomID);
4593   }
4594 };
4595 
4596 net.user1.orbiter.Client.prototype.isInRoom = function (roomID) {
4597   return net.user1.utils.ArrayUtil.indexOf(this.getOccupiedRoomIDs(), roomID) != -1;
4598 };
4599 
4600 net.user1.orbiter.Client.prototype.isObservingRoom = function (roomID) {
4601   return net.user1.utils.ArrayUtil.indexOf(this.getObservedRoomIDs(), roomID) != -1;
4602 };
4603 
4604 net.user1.orbiter.Client.prototype.getOccupiedRoomIDs = function () {
4605   var ids;
4606   if (this.clientManager.isObservingClient(this.getClientID())) {
4607     // This client is under observation, so its occupiedRoomIDs array is
4608     // 100% accurate.
4609     return this.occupiedRoomIDs == null ? [] : this.occupiedRoomIDs.slice(0);
4610   } else {
4611     // This client is not under observation, so the current client can only
4612     // deduce this client's occupied room list based on its current sphere of awareness.
4613     ids = [];
4614     var knownRooms = this.roomManager.getRooms();
4615     var numKnownRooms = knownRooms.length;
4616     var room;
4617     for (var i = 0; i < numKnownRooms; i++) {
4618       room = knownRooms[i];
4619       if (room.clientIsInRoom(this.getClientID())) {
4620         ids.push(room.getRoomID());
4621       }
4622     }
4623     return ids;
4624   }
4625 };
4626 
4627 net.user1.orbiter.Client.prototype.getObservedRoomIDs = function () {
4628   var ids;
4629   if (this.clientManager.isObservingClient(this.getClientID())) {
4630     // This client is under observation, so its occupiedRoomIDs array is
4631     // 100% accurate.
4632     return this.observedRoomIDs == null ? [] : this.observedRoomIDs.slice(0);
4633   } else {
4634     // This client is not under observation, so the current client can only
4635     // deduce this client's occupied room list based on its current sphere of awareness.
4636     ids = [];
4637     var knownRooms = this.roomManager.getRooms();
4638     var numKnownRooms = knownRooms.length;
4639     var room;
4640     for (var i = 0; i < numKnownRooms; i++) {
4641       room = knownRooms[i];
4642       if (room.clientIsObservingRoom(this.getClientID())) {
4643         ids.push(room.getRoomID());
4644       }
4645     }
4646     return ids;
4647   }
4648 };
4649 
4650 net.user1.orbiter.Client.prototype.getUpdateLevels = function (roomID) {
4651   var levels;
4652   var levelsAttr = this.getAttribute("_UL", roomID);
4653   
4654   if (levelsAttr != null) {
4655     levels = new net.user1.orbiter.UpdateLevels();
4656     levels.fromInt(parseInt(levelsAttr));
4657     return levels;
4658   } else {
4659     return null;
4660   }
4661 };
4662 
4663 // =============================================================================
4664 // BUILT-IN ATTRIBUTE RETRIEVAL
4665 // =============================================================================
4666 
4667 net.user1.orbiter.Client.prototype.getIP = function () {
4668   return this.getAttribute("_IP");
4669 };
4670 
4671 net.user1.orbiter.Client.prototype.getConnectTime = function () {
4672   var ct = this.getAttribute("_CT");
4673   return ct == null ? NaN : parseFloat(ct);
4674 };
4675 
4676 net.user1.orbiter.Client.prototype.getPing = function () {
4677   var ping = this.getAttribute("_PING");
4678   return ping == null ? -1 : parseInt(ping);
4679 };
4680 
4681 net.user1.orbiter.Client.prototype.getTimeOnline = function () {
4682   return this.server == null ? NaN : this.server.getServerTime() - this.getConnectTime();
4683 };
4684 
4685 // =============================================================================
4686 // MESSAGING
4687 // ============================================================================= 
4688 
4689 net.user1.orbiter.Client.prototype.sendMessage = function (messageName) {
4690   if (this.clientManager == null) {
4691     return;
4692   }
4693   // Delegate to ClientManager
4694   var rest = Array.prototype.slice.call(arguments).slice(1);
4695   var args = [messageName, 
4696               [this.getClientID()],
4697               null];
4698   this.clientManager.sendMessage.apply(this.clientManager, args.concat(rest));
4699 };
4700 
4701 // =============================================================================
4702 // ATTRIBUTES: PUBLIC API
4703 // =============================================================================
4704 net.user1.orbiter.Client.prototype.setAttribute = function (attrName, 
4705                                                             attrValue, 
4706                                                             attrScope, 
4707                                                             isShared, 
4708                                                             evaluate) {
4709   attrScope = attrScope == undefined ? null : attrScope;
4710   isShared = isShared == undefined ? true : isShared;
4711   evaluate = evaluate == undefined ? false : evaluate;
4712 
4713   // Create an integer to hold the attribute options.
4714   var attrOptions = (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
4715                     | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
4716   // Make the SetClientAttr UPC first so inputs are validated
4717   var setClientAttr = new net.user1.orbiter.upc.SetClientAttr(attrName, attrValue, attrOptions, attrScope, this.getClientID());
4718   
4719   // Set the attribute locally now, unless:
4720   // -it is another client's attribute
4721   // -it is the current client's attribute, and the value has changed
4722   if (!(!this.isSelf() || evaluate)) {
4723     // Set the attribute locally
4724     this.attributeManager.setAttributeLocal(attrName, attrValue, attrScope, this);
4725   }
4726 
4727   // Set the attribute on the server.
4728   this.messageManager.sendUPCObject(setClientAttr);
4729 };
4730 
4731 net.user1.orbiter.Client.prototype.deleteAttribute = function (attrName, attrScope) {
4732   var deleteRequest = new net.user1.orbiter.upc.RemoveClientAttr(this.getClientID(), null, attrName, attrScope);
4733   this.attributeManager.deleteAttribute(deleteRequest);
4734 };
4735 
4736 net.user1.orbiter.Client.prototype.getAttribute = function (attrName, attrScope) {
4737   return this.attributeManager.getAttribute(attrName, attrScope);
4738 };
4739 
4740 net.user1.orbiter.Client.prototype.getAttributes = function () {
4741   return this.attributeManager.getAttributes();
4742 };
4743 
4744 net.user1.orbiter.Client.prototype.getAttributesByScope = function (scope) {
4745   return this.attributeManager.getAttributesByScope(scope);
4746 };
4747 
4748 // =============================================================================
4749 // SYNCHRONIZATION
4750 // =============================================================================
4751   
4752 /**
4753  * @private
4754  */        
4755 net.user1.orbiter.Client.prototype.synchronize = function (clientManifest) {
4756   var scopes;
4757   this.synchronizeOccupiedRoomIDs(clientManifest.occupiedRoomIDs);
4758   this.synchronizeObservedRoomIDs(clientManifest.observedRoomIDs);
4759   
4760   // Synchronize Client attributes
4761   scopes = clientManifest.transientAttributes.getScopes();
4762   for (var i = scopes.length; --i >= 0;) {
4763     this.attributeManager.getAttributeCollection().synchronizeScope(scopes[i], clientManifest.transientAttributes);
4764   }
4765   // Synchronize UserAccount attributes
4766   if (this.account != null) {
4767     scopes = clientManifest.persistentAttributes.getScopes();
4768     for (i = scopes.length; --i >= 0;) {
4769       this.account.getAttributeManager().getAttributeCollection().synchronizeScope(scopes[i], clientManifest.persistentAttributes);
4770     }
4771   }
4772 };
4773     
4774 /**
4775  * @private
4776  */        
4777 net.user1.orbiter.Client.prototype.synchronizeOccupiedRoomIDs = function (newOccupiedRoomIDs) {
4778   if (newOccupiedRoomIDs == null) {
4779     // Nothing to synchronize
4780     return;
4781   }
4782   
4783   // Remove any rooms that are not in the new list
4784   var roomID;
4785   for (var i = this.occupiedRoomIDs.length; --i >= 0;) {
4786     roomID = this.occupiedRoomIDs[i];
4787     if (net.user1.utils.ArrayUtil.indexOf(newOccupiedRoomIDs, roomID) == -1) {
4788       this.removeOccupiedRoomID(roomID);
4789     }
4790   }
4791   
4792   // Add any rooms that are not in the old list (existing room IDs are ignored)
4793   for (i = newOccupiedRoomIDs.length; --i >= 0;) {
4794     roomID = newOccupiedRoomIDs[i];
4795     this.addOccupiedRoomID(roomID);
4796   }
4797 };
4798     
4799 /**
4800  * @private
4801  */        
4802 net.user1.orbiter.Client.prototype.synchronizeObservedRoomIDs = function (newObservedRoomIDs) {
4803   if (newObservedRoomIDs == null) {
4804     // Nothing to synchronize
4805     return;
4806   }
4807   // Remove any rooms that are not in the new list
4808   var roomID;
4809   for (var i = this.observedRoomIDs.length; --i >= 0;) {
4810     roomID = this.observedRoomIDs[i];
4811     if (net.user1.utils.ArrayUtil.indexOf(newObservedRoomIDs, roomID) == -1) {
4812       this.removeObservedRoomID(roomID);
4813     }
4814   }
4815   
4816   // Add any rooms that are not in the old list (existing room IDs are ignored)
4817   for (i = newObservedRoomIDs.length; --i >= 0;) {
4818     roomID = newObservedRoomIDs[i];
4819     this.addObservedRoomID(roomID);
4820   }
4821 };
4822 
4823 // =============================================================================
4824 // DEPENDENCIES
4825 // =============================================================================
4826 
4827 /**
4828  * @private
4829  */        
4830 net.user1.orbiter.Client.prototype.getAttributeManager = function () {
4831   return this.attributeManager;
4832 };
4833 
4834 net.user1.orbiter.Client.prototype.getClientManager = function () {
4835   return this.clientManager;
4836 };
4837 
4838 net.user1.orbiter.Client.prototype.getAccount = function () {
4839   return this.account;
4840 };
4841 
4842 /**
4843  * @private
4844  */
4845 net.user1.orbiter.Client.prototype.setAccount = function (value) {
4846   if (value == null) {
4847     this.account = null;
4848   } else {
4849     if (this.account != value) {
4850       this.account = value;
4851       this.account.setClient(this);
4852     }
4853   }
4854 };
4855 
4856 // =============================================================================
4857 // TOSTRING
4858 // =============================================================================
4859 
4860 net.user1.orbiter.Client.prototype.toString = function () {
4861   return "[CLIENT clientID: " + this.getClientID() + ", userID: " + (this.account == null ? "" : this.account.getUserID())  + "]";
4862 };
4863 
4864 // =============================================================================
4865 // EVENT DISPATCHING
4866 // =============================================================================
4867 
4868 /**
4869  * @private
4870  */
4871 net.user1.orbiter.Client.prototype.fireJoinRoom = function (room, roomID) {
4872   this.log.debug(this + " triggering ClientEvent.JOIN_ROOM event.");
4873   // Trigger event on listeners.
4874   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.JOIN_ROOM,
4875                                             null, room, roomID, this);
4876   this.dispatchEvent(e);
4877 };
4878 
4879 /**
4880  * @private
4881  */
4882 net.user1.orbiter.Client.prototype.fireLeaveRoom = function (room, roomID) {
4883   this.log.debug(this + " triggering ClientEvent.LEAVE_ROOM event.");
4884   // Trigger event on listeners.
4885   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.LEAVE_ROOM,
4886                                             null, room, roomID, this);
4887   this.dispatchEvent(e);
4888 };
4889 
4890 /**
4891  * @private
4892  */
4893 net.user1.orbiter.Client.prototype.fireObserveRoom = function (room, roomID) {
4894   this.log.debug(this + " triggering ClientEvent.OBSERVE_ROOM event.");
4895   // Trigger event on listeners.
4896   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_ROOM,
4897                                             null, room, roomID, this);
4898   this.dispatchEvent(e);
4899 };
4900 
4901 /**
4902  * @private
4903  */
4904 net.user1.orbiter.Client.prototype.fireStopObservingRoom = function (room, roomID) {
4905   this.log.debug(this + " triggering ClientEvent.STOP_OBSERVING_ROOM event.");
4906   // Trigger event on listeners.
4907   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM,
4908                                             null, room, roomID, this);
4909   this.dispatchEvent(e);
4910 };
4911 
4912 /**
4913  * @private
4914  */
4915 net.user1.orbiter.Client.prototype.fireObserve = function () {
4916   // Trigger event on listeners.
4917   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE, null, null, null, this);
4918   this.dispatchEvent(e);
4919 };
4920 
4921 /**
4922  * @private
4923  */
4924 net.user1.orbiter.Client.prototype.fireStopObserving = function () {
4925   // Trigger event on listeners.
4926   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING, null, null, null, this);
4927   this.dispatchEvent(e);
4928 };
4929 
4930 /**
4931  * @private
4932  */
4933 net.user1.orbiter.Client.prototype.fireObserveResult = function (status) {
4934   // Trigger event on listeners.
4935   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_RESULT,
4936                                             null, null, null, this, status);
4937   this.dispatchEvent(e);
4938 };
4939 
4940 /**
4941  * @private
4942  */
4943 net.user1.orbiter.Client.prototype.fireStopObservingResult = function (status) {
4944   // Trigger event on listeners.
4945   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT,
4946                                             null, null, null, this, status);
4947   this.dispatchEvent(e);
4948 };
4949 
4950 /**
4951  * @private
4952  */
4953 net.user1.orbiter.Client.prototype.fireLogin = function () {
4954   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
4955                                              net.user1.orbiter.Status.SUCCESS, this.getAccount().getUserID(), this.getClientID());
4956   this.dispatchEvent(e);
4957 };
4958 
4959 /**
4960  * @private
4961  */
4962 net.user1.orbiter.Client.prototype.fireLogoff = function (userID) {
4963   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
4964                                              net.user1.orbiter.Status.SUCCESS, userID, this.getClientID());
4965   this.dispatchEvent(e);
4966 };
4967 
4968 /**
4969  * @private
4970  */
4971 net.user1.orbiter.Client.prototype.fireSynchronize = function () {
4972   // Trigger event on listeners.
4973   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.SYNCHRONIZE, null, null, null, this);
4974   this.dispatchEvent(e);
4975 };
4976 
4977 // =============================================================================
4978 // DISPOSAL
4979 // =============================================================================
4980 
4981 net.user1.orbiter.Client.prototype.dispose = function () {
4982   this.occupiedRoomIDs = null;
4983   this.attributeManager.dispose();
4984   this.attributeManager = null;
4985   this.clientID = null;
4986   this.log = null;
4987   this.account = null;
4988   this.customClients = null;
4989   this.messageManager = null;
4990   this.clientManager = null;
4991   this.roomManager = null;
4992   this.server = null;
4993   this.disposed = true;
4994 };
4995 //==============================================================================
4996 // CLASS DECLARATION
4997 //==============================================================================
4998 /** 
4999  * @class
5000  * @extends net.user1.orbiter.snapshot.Snapshot
5001  */
5002 net.user1.orbiter.snapshot.ClientCountSnapshot = function () {
5003   // Call superconstructor
5004   net.user1.orbiter.snapshot.Snapshot.call(this);
5005   this.count = 0;
5006   this.method = net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT;
5007   this.hasStatus = true;
5008 };
5009 
5010 //==============================================================================
5011 // INHERITANCE
5012 //==============================================================================
5013 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientCountSnapshot, net.user1.orbiter.snapshot.Snapshot);
5014 
5015 //==============================================================================
5016 // INSTANCE METHODS
5017 //==============================================================================
5018 /**
5019  * @private
5020  */    
5021 net.user1.orbiter.snapshot.ClientCountSnapshot.prototype.setCount = function (value) {
5022   this.count = value;
5023 };
5024 
5025 net.user1.orbiter.snapshot.ClientCountSnapshot.prototype.getCount = function () {
5026   return this.count;
5027 };
5028 //==============================================================================
5029 // CLASS DECLARATION
5030 //==============================================================================
5031 /** @class
5032     @extends net.user1.events.Event
5033 */
5034 net.user1.orbiter.ClientEvent = function (type,
5035                                           changedAttr,
5036                                           room,
5037                                           roomID,
5038                                           client,
5039                                           status,
5040                                           clientID) {
5041   net.user1.events.Event.call(this, type);
5042   
5043   this.changedAttr = changedAttr;
5044   this.room = room;
5045   this.roomID = roomID;
5046   this.client = client;
5047   this.status = status;
5048   this.clientID = clientID;
5049 };
5050 
5051 //==============================================================================
5052 // INHERITANCE
5053 //==============================================================================
5054 net.user1.utils.extend(net.user1.orbiter.ClientEvent, net.user1.events.Event);
5055 
5056 //==============================================================================
5057 // STATIC VARIABLES
5058 //==============================================================================
5059 /** @constant */
5060 net.user1.orbiter.ClientEvent.JOIN_ROOM = "JOIN_ROOM";
5061 /** @constant */
5062 net.user1.orbiter.ClientEvent.LEAVE_ROOM = "LEAVE_ROOM";
5063 /** @constant */
5064 net.user1.orbiter.ClientEvent.OBSERVE_ROOM = "OBSERVE_ROOM";
5065 /** @constant */
5066 net.user1.orbiter.ClientEvent.STOP_OBSERVING_ROOM = "STOP_OBSERVING_ROOM";
5067 /** @constant */
5068 net.user1.orbiter.ClientEvent.OBSERVE = "OBSERVE";
5069 /** @constant */
5070 net.user1.orbiter.ClientEvent.STOP_OBSERVING = "STOP_OBSERVING";
5071 /** @constant */
5072 net.user1.orbiter.ClientEvent.OBSERVE_RESULT = "OBSERVE_RESULT";
5073 /** @constant */
5074 net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";
5075 /** @constant */
5076 net.user1.orbiter.ClientEvent.SYNCHRONIZE = "SYNCHRONIZE";
5077 
5078 net.user1.orbiter.ClientEvent.prototype.getClient = function () {
5079   return this.client;
5080 };
5081 
5082 net.user1.orbiter.ClientEvent.prototype.getClientID = function () {
5083   if (this.client != null) {
5084     return this.client.getClientID();
5085   } else {
5086     return this.clientID;
5087   }
5088 };
5089 
5090 net.user1.orbiter.ClientEvent.prototype.getRoom = function () {
5091   return this.room;
5092 };
5093 
5094 net.user1.orbiter.ClientEvent.prototype.getRoomID = function () {
5095   return this.roomID;
5096 }
5097 
5098 net.user1.orbiter.ClientEvent.prototype.getStatus = function () {
5099   return this.status;
5100 };
5101 
5102 net.user1.orbiter.ClientEvent.prototype.toString = function () {
5103   return "[object ClientEvent]";
5104 };
5105 //==============================================================================
5106 // CLASS DECLARATION
5107 //==============================================================================
5108 /** 
5109  * @class
5110  * @extends net.user1.orbiter.snapshot.Snapshot
5111  */
5112 net.user1.orbiter.snapshot.ClientListSnapshot = function () {
5113   // Call superconstructor
5114   net.user1.orbiter.snapshot.Snapshot.call(this);
5115   this.clientList;
5116   this.method = net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT;
5117 };
5118 
5119 //==============================================================================
5120 // INHERITANCE
5121 //==============================================================================
5122 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientListSnapshot, net.user1.orbiter.snapshot.Snapshot);
5123 
5124 //==============================================================================
5125 // INSTANCE METHODS
5126 //==============================================================================
5127 /**
5128  * @private
5129  */          
5130 net.user1.orbiter.snapshot.ClientListSnapshot.prototype.setClientList = function (value) {
5131   this.clientList = value;
5132 };
5133 
5134 net.user1.orbiter.snapshot.ClientListSnapshot.prototype.getClientList = function () {
5135   if (!this.clientList) {
5136     return null;
5137   }
5138   return this.clientList.slice();
5139 }
5140 //==============================================================================
5141 // CLASS DECLARATION
5142 //==============================================================================
5143 /** @class
5144 
5145 The ClientManager class dispatches the following events:
5146 
5147 <ul class="summary">
5148 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CREATE_ACCOUNT_RESULT}</li>
5149   
5150 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE}</li>
5151 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING}</li>
5152 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED}</li>
5153 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED}</li>
5154 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT}</li>
5155 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT}</li>
5156 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.OBSERVE_RESULT}</li>
5157 <li class="fixedFont">{@link net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT}</li>
5158 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.KICK_RESULT}</li>
5159 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.BAN_RESULT}</li>
5160 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT}</li>
5161 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT}</li>
5162 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT}</li>
5163 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED}</li>
5164 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED}</li>
5165 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST}</li>
5166 <li class="fixedFont">{@link net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE}</li>
5167 </ul>
5168 
5169 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
5170 
5171     @extends net.user1.events.EventDispatcher
5172 */  
5173 net.user1.orbiter.ClientManager = function (roomManager, 
5174                                             accountManager,
5175                                             connectionManager,
5176                                             messageManager,
5177                                             server,
5178                                             log) {
5179   // Call superconstructor
5180   net.user1.events.EventDispatcher.call(this);
5181   
5182   this.selfReference = null;
5183   this.defaultClientClass = null;
5184   this.lifetimeClientsRequested = 0;
5185   
5186   this._isWatchingForClients;
5187   this._isWatchingForUsers;
5188   this._isWatchingForBannedAddresses;
5189   
5190   this.watchedClients  = new net.user1.orbiter.ClientSet();
5191   this.observedClients = new net.user1.orbiter.ClientSet();
5192   this.bannedAddresses = [];
5193   this.clientCache     = new net.user1.utils.LRUCache(5000);
5194   
5195   this.roomManager       = roomManager;
5196   this.accountManager    = accountManager;
5197   this.connectionManager = connectionManager;
5198   this.messageManager    = messageManager;
5199   this.server            = server;
5200   this.log               = log;
5201 };
5202 
5203 //==============================================================================    
5204 // INHERITANCE
5205 //============================================================================== 
5206 net.user1.utils.extend(net.user1.orbiter.ClientManager, net.user1.events.EventDispatcher);
5207 
5208 //==============================================================================
5209 // CLIENT OBJECT CREATION AND ACCESS
5210 //==============================================================================
5211 
5212 /**
5213  * @private
5214  */
5215 net.user1.orbiter.ClientManager.prototype.requestClient = function (clientID) {
5216   var client;
5217   
5218   if (clientID == null || clientID === "") {
5219     throw new Error("[CLIENT_MANAGER] requestClient() called with empty clientID.");
5220   }
5221   
5222   client = this.getInternalClient(clientID);
5223   
5224   // If the client isn't already known
5225   if (client === null) {
5226     client = new net.user1.orbiter.Client(clientID, this, this.messageManager, this.roomManager, this.connectionManager, this.server, this.log);
5227     this.lifetimeClientsRequested++;
5228     this.clientCache.put(clientID, client);
5229   }
5230 
5231   return client;
5232 }
5233 
5234 net.user1.orbiter.ClientManager.prototype.getClient = function (clientID, scope) {
5235   var theClient;
5236   var theCustomClient;
5237   
5238   if (clientID === "" || clientID == null) {
5239     throw new Error("ClientManager.getClient() failed. Client ID must not be null or the" + 
5240                     " empty string.");
5241   }
5242 
5243   theClient = this.getInternalClient(clientID);
5244   if (theClient === null) {
5245     this.log.debug("[CLIENT_MANAGER] getClient() called for unknown client ID ["
5246               + clientID + "]."); 
5247     return null;
5248   } else {
5249     theCustomClient = theClient.getCustomClient(scope);
5250     return theCustomClient === null ? theClient : theCustomClient;
5251   }
5252 };
5253 
5254 net.user1.orbiter.ClientManager.prototype.getClients = function () {
5255   // Get all internal clients
5256   var clients = this.getInternalClients();
5257   var clientsList  = new Array();
5258   var customClient;
5259   
5260   // Replace internal clients with custom clients where available
5261   var client;
5262   for (var clientID in clients) {
5263     client = clients[clientID];
5264     customClient = client.getCustomClient(null);
5265     if (customClient != null) {
5266       clientsList.push(customClient);
5267     } else {
5268       clientsList.push(client);
5269     }
5270   }
5271   return clientsList;
5272 }
5273 
5274 net.user1.orbiter.ClientManager.prototype.getInternalClients = function () {
5275   var clients = net.user1.utils.ObjectUtil.combine(this.roomManager.getAllClients(),
5276                                                    this.accountManager.getClientsForObservedAccounts(),
5277                                                    this.observedClients.getAll(),
5278                                                    this.watchedClients.getAll());
5279   if (this.selfReference != null) {
5280     clients[this.selfReference.getClientID()] = this.selfReference;
5281   }
5282   return clients;
5283 };
5284 
5285 net.user1.orbiter.ClientManager.prototype.getInternalClient = function (clientID) {
5286   var theClient;
5287   
5288   // Error checking
5289   if (clientID === "" || clientID == null) {
5290     throw new Error("[CLIENT_MANAGER] this.getInternalClient() failed. Client ID must not be null or the" + 
5291                     " empty string.");
5292   }
5293   
5294   theClient = this.clientCache.get(clientID);
5295   
5296   if (theClient != null) {
5297     return theClient;
5298   } else {
5299     // Find the client...
5300     
5301     // Look in rooms
5302     var clients = this.roomManager.getAllClients(); 
5303     theClient = clients[clientID];
5304     if (theClient != null) {
5305       this.clientCache.put(clientID, theClient);
5306       return theClient;
5307     }
5308     
5309     // Look in observed accounts
5310     clients = this.accountManager.getClientsForObservedAccounts();
5311     theClient = clients[clientID];
5312     if (theClient != null) {
5313       this.clientCache.put(clientID, theClient);
5314       return theClient;
5315     }
5316 
5317     // Look in observed clients
5318     theClient = this.observedClients.getByClientID(clientID);
5319     if (theClient != null) {
5320       this.clientCache.put(clientID, theClient);
5321       return theClient;
5322     }
5323 
5324     // Look in watched clients
5325     theClient = this.watchedClients.getByClientID(clientID);
5326     if (theClient != null) {
5327       this.clientCache.put(clientID, theClient);
5328       return theClient;
5329     }
5330   }
5331   
5332   // Client not found
5333   return null;
5334 }
5335 
5336 net.user1.orbiter.ClientManager.prototype.getClientByUserID = function (userID, scope) {
5337   var theClient;
5338   var theCustomClient;
5339   var account;
5340   
5341   if (userID === "" || userID == null) {
5342     throw new Error("ClientManager.getClientByUserID() failed. User ID must not be null or the" + 
5343                     " empty string.");
5344   }
5345 
5346   // Search for the client in all known clients
5347   var client;
5348   var clients = this.getInternalClients();
5349   for (var clientID in clients) {
5350     client = clients[clientID];
5351     account = client.getAccount();
5352     if (account != null && account.getUserID() === userID) {
5353       theClient = client;
5354       break;
5355     }
5356   }
5357   
5358   if (theClient === null) {
5359     this.log.debug("[CLIENT_MANAGER] getClientByUserID() called for unknown user ID ["
5360               + userID + "]."); 
5361     return null;
5362   } else {
5363     theCustomClient = theClient.getCustomClient(scope);
5364     return theCustomClient === null ? theClient : theCustomClient;
5365   }
5366 };
5367 
5368 net.user1.orbiter.ClientManager.prototype.getClientByAttribute  = function (attributeName,
5369                                                                             attributeValue,
5370                                                                             attributeScope,
5371                                                                             roomScope) {
5372   var theCustomClient;
5373   
5374   // Validate
5375   if (attributeName == null || attributeName === "") {
5376     return null;
5377   }
5378   
5379   // Search for the client in all known clients
5380   var client;
5381   var clients = this.getInternalClients();
5382   for (var clientID in clients) {
5383     client = clients[clientID];
5384     if (client.getAttribute(attributeName, attributeScope)
5385         === attributeValue) {
5386       theCustomClient = client.getCustomClient(roomScope);
5387       return theCustomClient === null ? client : theCustomClient;
5388     }
5389   }
5390   return null;
5391 };
5392 
5393 net.user1.orbiter.ClientManager.prototype.clientIsKnown = function (clientID) {
5394   return this.getInternalClients()[clientID] !== null;
5395 };
5396 
5397 // =============================================================================
5398 // WATCHED CLIENTS
5399 // =============================================================================
5400 
5401 net.user1.orbiter.ClientManager.prototype.watchForClients = function () {
5402   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_CLIENTS);
5403 };   
5404 
5405 net.user1.orbiter.ClientManager.prototype.stopWatchingForClients = function () {
5406   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS);
5407 };     
5408 
5409 net.user1.orbiter.ClientManager.prototype.isWatchingForClients = function () {
5410   return this._isWatchingForClients;
5411 };
5412 
5413 net.user1.orbiter.ClientManager.prototype.hasWatchedClient = function (clientID) {
5414   return this.watchedClients.containsClientID(clientID);
5415 };
5416     
5417 /**
5418  * @private
5419  */        
5420 net.user1.orbiter.ClientManager.prototype.setIsWatchingForClients = function (value) {
5421   this._isWatchingForClients = value;
5422 };
5423     
5424 /**
5425  * @private
5426  */        
5427 net.user1.orbiter.ClientManager.prototype.addWatchedClient = function (client) {
5428   var customClient = client.getCustomClient(null);
5429   this.watchedClients.add(client);
5430   this.fireClientConnected(customClient === null ? client : customClient);      
5431 };
5432 
5433 /**
5434  * @private
5435  */        
5436 net.user1.orbiter.ClientManager.prototype.removeWatchedClient = function (clientID) {
5437   this.watchedClients.removeByClientID(clientID);
5438 };
5439 
5440 /**
5441  * @private
5442  */        
5443 net.user1.orbiter.ClientManager.prototype.removeAllWatchedClients = function () {
5444   this.watchedClients.removeAll();
5445 };
5446     
5447 /**
5448  * @private
5449  */        
5450 net.user1.orbiter.ClientManager.prototype.deserializeWatchedClients = function (ids) {
5451   var idList = ids.split(net.user1.orbiter.Tokens.RS);
5452   var idHash = new Object();
5453   var localClients = this.watchedClients.getAll();
5454   var len = idList.length;
5455   var theClient;
5456   var accountID;
5457   
5458   // Client list received, so set isWatchingForClients now, otherwise, code 
5459   // with side-effects may take action against the clients being added
5460    this.setIsWatchingForClients(true);
5461   
5462   // Generate a hash of clientID keys to accountID values
5463   for (var i = len-2; i >= 0; i-=2) {
5464     idHash[idList[i]] = idList[i+1]; 
5465   }
5466   
5467   // Remove all local clients that are not in the new list from the server
5468   var clientStillExists;
5469   for (var clientID in localClients) {
5470     if (!idHash.hasOwnProperty(clientID)) {
5471       // For best performance, use direct access rather than removeByClientID()
5472       delete localClients[clientID];
5473     }
5474   }      
5475   
5476   // Add all new clients that are not in the local set
5477   for (clientID in idHash) {
5478     if (clientID != "") {
5479       if (!this.watchedClients.containsClientID(clientID)) {
5480         theClient = this.requestClient(clientID);
5481         accountID = idHash[clientID]; 
5482         if (accountID != "") {
5483           theClient.setAccount(this.accountManager.requestAccount(accountID));
5484         }
5485         this.addWatchedClient(theClient);
5486       }
5487     } else {
5488       throw new Error("[CLIENT_MANAGER] Received empty client id in client list (u101).");
5489     }
5490   }
5491   
5492   this.fireSynchronize();
5493 };
5494 
5495 // =============================================================================
5496 // OBSERVED CLIENTS
5497 // =============================================================================
5498 
5499 net.user1.orbiter.ClientManager.prototype.observeClient = function (clientID) {
5500   this.messageManager.sendUPC(net.user1.orbiter.UPC.OBSERVE_CLIENT, clientID);
5501 };      
5502 
5503 net.user1.orbiter.ClientManager.prototype.isObservingClient = function (clientID) {
5504   return this.observedClients.containsClientID(clientID);
5505 }
5506 
5507 /**
5508  * @private
5509  */        
5510 net.user1.orbiter.ClientManager.prototype.addObservedClient = function (client) {
5511   var customClient = client.getCustomClient(null);
5512   this.observedClients.add(client);
5513   this.fireObserveClient(customClient === null ? client : customClient);
5514 };
5515 
5516 /**
5517  * @private
5518  */        
5519 net.user1.orbiter.ClientManager.prototype.removeObservedClient = function (clientID) {
5520   var client = this.observedClients.removeByClientID(clientID);
5521   var customClient;
5522   if (client != null) {
5523     customClient = client.getCustomClient(null);
5524     this.fireStopObservingClient(customClient === null ? client : customClient);
5525   }
5526 };
5527 
5528 /**
5529  * @private
5530  */        
5531 net.user1.orbiter.ClientManager.prototype.removeAllObservedClients = function () {
5532   this.observedClients.removeAll();
5533 };
5534 
5535 //==============================================================================
5536 // CLIENT ATTRIBUTE ACCESS
5537 //==============================================================================
5538 
5539 net.user1.orbiter.ClientManager.prototype.getAttributeForClients = function (clientIDs,
5540                                                                              attrName, 
5541                                                                              attrScope) {
5542   var clientAttributes = new Array();
5543   var thisClient;
5544   
5545   for (var i = 0; i < clientIDs.length; i++) {
5546     thisClient = this.getInternalClient(clientIDs[i]);
5547     if (thisClient != null) {
5548       clientAttributes.push({clientID: clientIDs[i],
5549           value: thisClient.getAttribute(attrName, attrScope)});
5550     } else {
5551       this.log.debug("[CLIENT_MANAGER] Attribute retrieval failed during "
5552                 + " getAttributeForClients(). Unknown client ID [" + clientIDs[i] + "]");
5553     }
5554   }
5555   return clientAttributes;
5556 };
5557 
5558 //==============================================================================
5559 // CUSTOM CLIENT MANAGEMENT
5560 //==============================================================================
5561 
5562 net.user1.orbiter.ClientManager.prototype.setDefaultClientClass = function (defaultClass) {
5563   this.defaultClientClass = defaultClass;
5564 };
5565 
5566 net.user1.orbiter.ClientManager.prototype.getDefaultClientClass = function () {
5567   return this.defaultClientClass;
5568 };
5569 
5570 //==============================================================================
5571 // CURRENT CLIENT ASSIGNMENT AND ACCESS
5572 //==============================================================================
5573 
5574 /**
5575  * @private
5576  */
5577 net.user1.orbiter.ClientManager.prototype.self = function () {
5578   return this.selfReference;
5579 }
5580 
5581 /**
5582  * @private
5583  */
5584 net.user1.orbiter.ClientManager.prototype.setSelf = function (client) {
5585   this.selfReference = client;
5586   client.setIsSelf();
5587 }
5588 
5589 //==============================================================================
5590 // CLIENT MESSAGING
5591 //==============================================================================
5592 
5593 net.user1.orbiter.ClientManager.prototype.sendMessage = function (messageName, 
5594                                                                   clientIDs,
5595                                                                   filters) {
5596   var rest = Array.prototype.slice.call(arguments).slice(3);
5597   
5598   // An array of arguments to send to the server.
5599   var args;
5600 
5601   // Can't continue without a valid methodName.
5602   if (messageName == null || messageName == "") {
5603     this.log.warn("[CLIENT_MANAGER] sendMessage() failed. No messageName supplied.");
5604     return;
5605   }
5606   
5607   // Send the UPC.
5608   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS, 
5609           messageName, 
5610           clientIDs.join(net.user1.orbiter.Tokens.RS),
5611           filters != null ? filters.toXMLString() : ""];
5612   this.messageManager.sendUPC.apply(this.messageManager, args.concat(rest));
5613 };
5614 
5615 // =============================================================================
5616 // BAN / UNBAN / KICK
5617 // =============================================================================
5618 
5619 net.user1.orbiter.ClientManager.prototype.ban = function (address, duration, reason) {
5620   this.messageManager.sendUPC(net.user1.orbiter.UPC.BAN, address, null, duration.toString(), reason);
5621 };
5622 
5623 net.user1.orbiter.ClientManager.prototype.unban = function (address) {
5624   this.messageManager.sendUPC(net.user1.orbiter.UPC.UNBAN, address);
5625 };
5626 
5627 net.user1.orbiter.ClientManager.prototype.kickClient = function (clientID) {
5628   if (clientID == null || clientID == "") {
5629     this.log.warn("[CLIENT_MANAGER] Kick attempt failed. No clientID supplied.");
5630   }
5631   this.messageManager.sendUPC(net.user1.orbiter.UPC.KICK_CLIENT, clientID);
5632 }
5633 
5634 // =============================================================================
5635 // WATCH BANNED ADDRESSES
5636 // =============================================================================
5637 
5638 net.user1.orbiter.ClientManager.prototype.watchForBannedAddresses = function () {
5639   this.messageManager.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES);
5640 };
5641 
5642 net.user1.orbiter.ClientManager.prototype.stopWatchingForBannedAddresses = function () {
5643   this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES);
5644 };
5645 
5646 /**
5647  * @private
5648  */  
5649 net.user1.orbiter.ClientManager.prototype.setWatchedBannedAddresses = function (bannedList) {
5650   this.bannedAddresses = bannedList;
5651   this.fireSynchronizeBanlist();
5652 };
5653 
5654 /**
5655  * @private
5656  */  
5657 net.user1.orbiter.ClientManager.prototype.addWatchedBannedAddress = function (address) {
5658   this.bannedAddresses.push(address);
5659   this.fireAddressBanned(address);
5660 };
5661 
5662 /**
5663  * @private
5664  */  
5665 net.user1.orbiter.ClientManager.prototype.removeWatchedBannedAddress = function (address) {
5666   var idx = net.user1.util.ArrayUtil.indexOf(bannedAddresses, address);
5667   if (idx === -1) {
5668     this.log.warn("[CLIENT_MANAGER] Request to remove watched banned address failed."
5669              + " Address not found.");
5670   }
5671   this.bannedAddresses.splice(idx, 1);
5672   this.fireAddressUnbanned(address);
5673 }
5674 
5675 /**
5676  * @private
5677  */        
5678 net.user1.orbiter.ClientManager.prototype.setIsWatchingForBannedAddresses = function (value) {
5679   this._isWatchingForBannedAddresses = value;
5680 };
5681 
5682 net.user1.orbiter.ClientManager.prototype.isWatchingForBannedAddresses = function () {
5683   return this._isWatchingForBannedAddresses;
5684 };
5685 
5686 net.user1.orbiter.ClientManager.prototype.getBannedAddresses = function () {
5687   return this.bannedAddresses.slice(0);
5688 };
5689 
5690 //==============================================================================
5691 // STATISTICS
5692 //==============================================================================
5693 
5694 net.user1.orbiter.ClientManager.prototype.getLifetimeNumClientsKnown = function () {
5695   // -1 for each "ready" state the connection has achieved because we don't
5696   // count the current client ("self")
5697   return this.lifetimeClientsRequested-this.connectionManager.getReadyCount();
5698 };
5699 
5700 net.user1.orbiter.ClientManager.prototype.getNumClients = function () {
5701   return net.user1.utils.ObjectUtil.length(this.getInternalClients());
5702 };
5703 
5704 net.user1.orbiter.ClientManager.prototype.getNumClientsOnServer = function () {
5705   return this.watchedClients.length();
5706 }
5707 
5708 //==============================================================================
5709 // EVENT DISPATCHING
5710 //==============================================================================
5711 
5712 /**
5713  * @private
5714  */
5715 net.user1.orbiter.ClientManager.prototype.fireObserveClient = function (client) {
5716   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE, null, null, null, client);
5717   this.dispatchEvent(e);
5718 };
5719 
5720 /**
5721  * @private
5722  */
5723 net.user1.orbiter.ClientManager.prototype.fireStopObservingClient = function (client) {
5724   var e = new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING, null, null, null, client);
5725   this.dispatchEvent(e);
5726 }; 
5727 
5728 /**
5729  * @private
5730  */
5731 net.user1.orbiter.ClientManager.prototype.fireClientConnected = function (client) {
5732   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED, 
5733                                                               client.getClientID(), client));
5734 };    
5735 
5736 /**
5737  * @private
5738  */
5739 net.user1.orbiter.ClientManager.prototype.fireClientDisconnected = function (client) {
5740   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED, 
5741                                                               client.getClientID(), client));
5742 };   
5743 
5744 /**
5745  * @private
5746  */
5747 net.user1.orbiter.ClientManager.prototype.fireStopWatchingForClientsResult = function (status) {
5748   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT, 
5749                                                               null, null, null, status));
5750 };   
5751 
5752 /**
5753  * @private
5754  */
5755 net.user1.orbiter.ClientManager.prototype.fireWatchForClientsResult = function (status) {
5756   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT, 
5757                                                               null, null, null, status));
5758 };   
5759 
5760 /**
5761  * @private
5762  */
5763 net.user1.orbiter.ClientManager.prototype.fireObserveClientResult = function (clientID, status) {
5764   this.dispatchEvent(new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.OBSERVE_RESULT, 
5765                                        null, null, null, this.getClient(clientID), status, clientID));
5766 };    
5767 
5768 /**
5769  * @private
5770  */
5771 net.user1.orbiter.ClientManager.prototype.fireStopObservingClientResult = function (clientID, status) {
5772   this.dispatchEvent(new net.user1.orbiter.ClientEvent(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT, 
5773                                                        null, null, null, this.getClient(clientID), status, clientID));
5774 };    
5775 
5776 /**
5777  * @private
5778  */
5779 net.user1.orbiter.ClientManager.prototype.fireKickClientResult = function (clientID, status) {
5780   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.KICK_RESULT, 
5781                                                               clientID, null, null, status));
5782 };    
5783 
5784 /**
5785  * @private
5786  */
5787 net.user1.orbiter.ClientManager.prototype.fireBanClientResult = function (address, clientID, status) {
5788   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.BAN_RESULT, 
5789                                                               clientID, null, address, status));
5790 };    
5791 
5792 /**
5793  * @private
5794  */
5795 net.user1.orbiter.ClientManager.prototype.fireUnbanClientResult = function (address, status) {
5796   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT, 
5797                                                               null, null, address, status));
5798 };    
5799 
5800 /**
5801  * @private
5802  */
5803 net.user1.orbiter.ClientManager.prototype.fireWatchForBannedAddressesResult = function (status) {
5804   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT, 
5805                                                               null, null, null, status));
5806 };    
5807 
5808 /**
5809  * @private
5810  */
5811 net.user1.orbiter.ClientManager.prototype.fireStopWatchingForBannedAddressesResult = function (status) {
5812   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT, 
5813                                                               null, null, null, status));
5814 };    
5815 
5816 /**
5817  * @private
5818  */
5819 net.user1.orbiter.ClientManager.prototype.fireAddressBanned = function (address) {
5820   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED, 
5821                                                               null, null, address));
5822 }; 
5823 
5824 /**
5825  * @private
5826  */
5827 net.user1.orbiter.ClientManager.prototype.fireAddressUnbanned = function (address) {
5828   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED, 
5829                                                               null, null, address));
5830 }; 
5831 
5832 /**
5833  * @private
5834  */
5835 net.user1.orbiter.ClientManager.prototype.fireSynchronizeBanlist = function () {
5836   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST));
5837 };        
5838 
5839 /**
5840  * @private
5841  */
5842 net.user1.orbiter.ClientManager.prototype.fireSynchronize = function () {
5843   this.dispatchEvent(new net.user1.orbiter.ClientManagerEvent(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE));
5844 };        
5845 
5846 //==============================================================================
5847 // CLEANUP AND DISPOSAL
5848 //==============================================================================
5849 
5850 /**
5851  * @private
5852  */    
5853 net.user1.orbiter.ClientManager.prototype.cleanup = function () {
5854   this.log.info("[CLIENT_MANAGER] Cleaning resources.");
5855   this.selfReference = null;
5856   this.removeAllObservedClients();
5857   this.removeAllWatchedClients();
5858   this.setIsWatchingForClients(false);
5859 };   
5860 
5861 net.user1.orbiter.ClientManager.prototype.dispose = function () {
5862   this.log.info("[CLIENT_MANAGER] Disposing resources.");
5863   this.watchedClients = null;
5864   this.observedClients = null;
5865   this.defaultClientClass = null;
5866 };
5867 //==============================================================================
5868 // CLASS DECLARATION
5869 //==============================================================================
5870 /** @class
5871     @extends net.user1.events.Event
5872 */
5873 net.user1.orbiter.ClientManagerEvent = function (type,
5874                                                  clientID,
5875                                                  client,
5876                                                  address,
5877                                                  status) {
5878   net.user1.events.Event.call(this, type);
5879   
5880   this.clientID = clientID;
5881   this.client   = client;
5882   this.address  = address;
5883   this.status   = status;
5884 };
5885 
5886 //==============================================================================
5887 // INHERITANCE
5888 //==============================================================================
5889 net.user1.utils.extend(net.user1.orbiter.ClientManagerEvent, net.user1.events.Event);
5890 
5891 //==============================================================================
5892 // STATIC VARIABLES
5893 //==============================================================================
5894 /** @constant */
5895 net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT = "WATCH_FOR_CLIENTS_RESULT";
5896 /** @constant */
5897 net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT = "STOP_WATCHING_FOR_CLIENTS_RESULT";
5898 /** @constant */
5899 net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED = "CLIENT_DISCONNECTED";
5900 /** @constant */
5901 net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED = "CLIENT_CONNECTED";
5902 /** @constant */
5903 net.user1.orbiter.ClientManagerEvent.KICK_RESULT = "KICK_RESULT";
5904 /** @constant */
5905 net.user1.orbiter.ClientManagerEvent.BAN_RESULT = "BAN_RESULT";
5906 /** @constant */
5907 net.user1.orbiter.ClientManagerEvent.UNBAN_RESULT = "UNBAN_RESULT";
5908 /** @constant */
5909 net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT = "WATCH_FOR_BANNED_ADDRESSES_RESULT";
5910 /** @constant */
5911 net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT";
5912 /** @constant */
5913 net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED = "ADDRESS_BANNED";
5914 /** @constant */
5915 net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED = "ADDRESS_UNBANNED";
5916 /** @constant */
5917 net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST = "SYNCHRONIZE_BANLIST"; 
5918 /** @constant */
5919 net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE = "SYNCHRONIZE"; 
5920     
5921 net.user1.orbiter.ClientManagerEvent.prototype.getClientID = function () {
5922   return this.clientID;
5923 };
5924 
5925 net.user1.orbiter.ClientManagerEvent.prototype.getClient = function () {
5926   return this.client;
5927 };
5928 
5929 net.user1.orbiter.ClientManagerEvent.prototype.getAddress = function () {
5930   return this.address;
5931 };
5932 
5933 net.user1.orbiter.ClientManagerEvent.prototype.getStatus = function () {
5934   return this.status;
5935 };
5936 
5937 net.user1.orbiter.ClientManagerEvent.prototype.toString = function () {
5938   return "[object ClientManagerEvent]";
5939 };
5940 //==============================================================================
5941 // CLASS DECLARATION
5942 //==============================================================================
5943 net.user1.orbiter.ClientManifest = function () {
5944   this.clientID = null;
5945   this.userID = null;
5946   this.persistentAttributes = new net.user1.orbiter.AttributeCollection();
5947   this.transientAttributes = new net.user1.orbiter.AttributeCollection();
5948   this.occupiedRoomIDs = null;
5949   this.observedRoomIDs = null;
5950 };
5951 
5952 /**
5953  * @private
5954  */        
5955 net.user1.orbiter.ClientManifest.prototype.deserialize = function (clientID,
5956                                                                    userID,
5957                                                                    serializedOccupiedRoomIDs,
5958                                                                    serializedObservedRoomIDs,
5959                                                                    globalAttrs,
5960                                                                    roomAttrs) {
5961   this.clientID = clientID == "" ? null : clientID;
5962   this.userID   = userID == "" ? null : userID;
5963   
5964   // Room ids
5965   this.deserializeOccupiedRoomIDs(serializedOccupiedRoomIDs);
5966   this.deserializeObservedRoomIDs(serializedObservedRoomIDs);
5967   
5968   // Global attrs
5969   this.deserializeAttributesByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR, globalAttrs);
5970   
5971   // Room attrs
5972   for (var i = 0; i < roomAttrs.length; i += 2) {
5973     this.deserializeAttributesByScope(roomAttrs[i], roomAttrs[i+1]);
5974   }
5975 };
5976     
5977 /**
5978  * @private
5979  */        
5980 net.user1.orbiter.ClientManifest.prototype.deserializeOccupiedRoomIDs = function (roomIDs) {
5981   // No rooms included in the manifest
5982   if (roomIDs == null) {
5983     return;
5984   }
5985   // Client is in no rooms
5986   if (roomIDs == "") {
5987     this.occupiedRoomIDs = [];
5988     return;
5989   }
5990   // Client is in one or more room
5991   this.occupiedRoomIDs = roomIDs.split(net.user1.orbiter.Tokens.RS);
5992 };
5993     
5994 /**
5995  * @private
5996  */        
5997 net.user1.orbiter.ClientManifest.prototype.deserializeObservedRoomIDs = function (roomIDs) {
5998   if (roomIDs == null) {
5999     return;
6000   }
6001   if (roomIDs == "") {
6002     this.observedRoomIDs = [];
6003     return;
6004   }
6005   this.observedRoomIDs = roomIDs.split(net.user1.orbiter.Tokens.RS);
6006 };
6007      
6008 /**
6009  * @private
6010  */         
6011 net.user1.orbiter.ClientManifest.prototype.deserializeAttributesByScope = function (scope,
6012                                                                                     serializedAttributes) {
6013   var attrList;
6014   if (serializedAttributes == null || serializedAttributes == "") {
6015     return;
6016   }
6017   attrList = serializedAttributes.split(net.user1.orbiter.Tokens.RS);
6018   for (var i = attrList.length-3; i >= 0; i -=3) {
6019     if (parseInt(attrList[i+2]) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
6020       // Persistent
6021       this.persistentAttributes.setAttribute(attrList[i], attrList[i+1], scope);
6022     } else {
6023       // Non-persistent
6024       this.transientAttributes.setAttribute(attrList[i], attrList[i+1], scope);
6025     }
6026   }
6027 };
6028 //==============================================================================
6029 // CLASS DECLARATION
6030 //==============================================================================
6031 /**
6032  * @private
6033  */  
6034 net.user1.orbiter.ClientSet = function () {
6035   this.clients = new net.user1.utils.UDictionary();
6036 };
6037 
6038 net.user1.orbiter.ClientSet.prototype.add = function (client) {
6039   this.clients[client.getClientID()] = client;
6040 };
6041 
6042 net.user1.orbiter.ClientSet.prototype.remove = function (client) {
6043   var client = clients[client.getClientID()];
6044   delete this.clients[client.getClientID()];
6045   return client;
6046 };
6047 
6048 net.user1.orbiter.ClientSet.prototype.removeAll = function () {
6049   this.clients = new net.user1.utils.UDictionary();
6050 }
6051 
6052 net.user1.orbiter.ClientSet.prototype.removeByClientID = function (clientID) {
6053   var client = this.clients[clientID];
6054   delete this.clients[clientID];
6055   return client;
6056 };
6057 
6058 net.user1.orbiter.ClientSet.prototype.contains = function (client) {
6059   return this.clients[client.getClientID()] != null;
6060 };
6061 
6062 net.user1.orbiter.ClientSet.prototype.containsClientID = function (clientID) {
6063   if (clientID == "" || clientID == null) {
6064     return false;
6065   }
6066   return this.getByClientID(clientID) != null;
6067 };
6068 
6069 net.user1.orbiter.ClientSet.prototype.getByClientID = function (clientID) {
6070   return this.clients[clientID];
6071 };
6072 
6073 net.user1.orbiter.ClientSet.prototype.getByUserID = function (userID) {
6074   var account;
6075   
6076   var client;
6077   for (var clientID in this.clients) {
6078     client = this.clients[clientID];
6079     account = client.getAccount();
6080     if (account != null && account.getUserID() == userID) {
6081       return client;
6082     }
6083   }
6084   return null;
6085 };
6086 
6087 net.user1.orbiter.ClientSet.prototype.getAll = function () {
6088   return this.clients;
6089 }
6090 
6091 net.user1.orbiter.ClientSet.prototype.getAllIDs = function () {
6092   var ids = [];
6093   for (var clientID in this.clients) {
6094     ids.push(clientID);
6095   }
6096   return ids;
6097 };
6098 
6099 net.user1.orbiter.ClientSet.prototype.length = function () {
6100   return net.user1.utils.ObjectUtil.length(this.clients);
6101 };
6102 //==============================================================================
6103 // CLASS DECLARATION
6104 //==============================================================================
6105 /** 
6106  * @class
6107  * @extends net.user1.orbiter.snapshot.Snapshot
6108  */
6109 net.user1.orbiter.snapshot.ClientSnapshot = function (clientID) {
6110   // Call superconstructor
6111   net.user1.orbiter.snapshot.Snapshot.call(this);
6112   this.manifest = null;
6113   this.method = net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT;
6114   this.args   = [clientID];
6115   this.hasStatus = true;
6116 };
6117 
6118 //==============================================================================
6119 // INHERITANCE
6120 //==============================================================================
6121 net.user1.utils.extend(net.user1.orbiter.snapshot.ClientSnapshot, net.user1.orbiter.snapshot.Snapshot);
6122 
6123 //==============================================================================
6124 // INSTANCE METHODS
6125 //==============================================================================        
6126 /**
6127  * @private
6128  */    
6129 net.user1.orbiter.snapshot.ClientSnapshot.prototype.setManifest = function (value) {
6130   this.manifest = value;
6131 };
6132 
6133 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getAttribute = function (name, scope) {
6134   if (!this.manifest) {
6135     return null;
6136   }
6137   return this.manifest.transientAttributes.getAttribute(name, scope);
6138 };
6139 
6140 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getAttributes = function () {
6141   if (!this.manifest) {
6142     return null;
6143   }
6144   return this.manifest.transientAttributes.getAll();
6145 };
6146 
6147 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getClientID = function () {
6148   if (!this.manifest) {
6149     return null;
6150   }
6151   return this.manifest.clientID;
6152 };
6153 
6154 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getUserID = function () {
6155   if (!this.manifest) {
6156     return null;
6157   }
6158   return this.manifest.userID;
6159 };
6160 
6161 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getOccupiedRoomIDs = function () {
6162   if (!this.manifest) {
6163     return null;
6164   }
6165   return this.manifest.occupiedRoomIDs.slice();
6166 };
6167 
6168 net.user1.orbiter.snapshot.ClientSnapshot.prototype.getObservedRoomIDs = function () {
6169   if (!this.manifest) {
6170     return null;
6171   }
6172   return this.manifest.observedRoomIDs.slice();
6173 }
6174 //==============================================================================
6175 // CLASS DECLARATION
6176 //==============================================================================
6177 /** 
6178  * @private
6179  */
6180 net.user1.orbiter.CollectionEvent = function (type, item) {
6181   net.user1.events.Event.call(this, type);
6182   
6183   this.item = item;
6184 };
6185 
6186 //==============================================================================
6187 // INHERITANCE
6188 //==============================================================================
6189 net.user1.utils.extend(net.user1.orbiter.CollectionEvent, net.user1.events.Event);
6190 
6191 //==============================================================================
6192 // STATIC VARIABLES
6193 //==============================================================================
6194 
6195 /** @constant */
6196 net.user1.orbiter.CollectionEvent.REMOVE_ITEM = "REMOVE_ITEM";
6197 /** @constant */
6198 net.user1.orbiter.CollectionEvent.ADD_ITEM = "ADD_ITEM";
6199     
6200 net.user1.orbiter.CollectionEvent.prototype.getItem = function () {
6201   return this.item;
6202 };
6203 
6204 net.user1.orbiter.CollectionEvent.prototype.toString = function () {
6205   return "[object CollectionEvent]";
6206 };
6207 //==============================================================================
6208 // COMPARE TYPE CONSTANTS
6209 //==============================================================================
6210 /** @class */
6211 net.user1.orbiter.filters.CompareType = new Object();
6212 /** @constant */
6213 net.user1.orbiter.filters.CompareType.EQUAL = "eq";
6214 /** @constant */
6215 net.user1.orbiter.filters.CompareType.NOT_EQUAL = "ne";
6216 /** @constant */
6217 net.user1.orbiter.filters.CompareType.GREATER_THAN = "gt";
6218 /** @constant */
6219 net.user1.orbiter.filters.CompareType.GREATER_THAN_OR_EQUAL = "ge";
6220 /** @constant */
6221 net.user1.orbiter.filters.CompareType.LESS_THAN = "lt";
6222 /** @constant */
6223 net.user1.orbiter.filters.CompareType.LESS_THAN_OR_EQUAL = "le";
6224 //==============================================================================
6225 // CLASS DECLARATION
6226 //==============================================================================
6227 /**
6228 * @private
6229 */
6230 net.user1.orbiter.CoreEventLogger = function (log,
6231                                               connectionMan,
6232                                               roomMan,
6233                                               accountMan,
6234                                               server,
6235                                               clientMan,
6236                                               orbiter) {
6237   this.log = log;
6238 
6239   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
6240                            this.stopWatchingForRoomsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6241   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
6242                            this.watchForRoomsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6243   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT,
6244                            this.createRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6245   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT,
6246                            this.removeRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6247   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_ADDED,
6248                            this.roomAddedListener, this, net.user1.utils.integer.MAX_VALUE);
6249   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED,
6250                            this.roomRemovedListener, this, net.user1.utils.integer.MAX_VALUE);
6251   roomMan.addEventListener(net.user1.orbiter.RoomManagerEvent.ROOM_COUNT,
6252                            this.roomCountListener, this, net.user1.utils.integer.MAX_VALUE);
6253   roomMan.addEventListener(net.user1.orbiter.RoomEvent.JOIN_RESULT,
6254                            this.joinRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6255   roomMan.addEventListener(net.user1.orbiter.RoomEvent.LEAVE_RESULT,
6256                            this.leaveRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6257   roomMan.addEventListener(net.user1.orbiter.RoomEvent.OBSERVE_RESULT,
6258                            this.observeRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6259   roomMan.addEventListener(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT,
6260                            this.stopObservingRoomResultListener, this, net.user1.utils.integer.MAX_VALUE);
6261   
6262   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.CREATE_ACCOUNT_RESULT, 
6263                               this.createAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6264   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.REMOVE_ACCOUNT_RESULT,
6265                               this.removeAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6266   accountMan.addEventListener(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT,
6267                               this.changePasswordResultListener, this, net.user1.utils.integer.MAX_VALUE);
6268   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.ACCOUNT_ADDED,
6269                               this.accountAddedListener, this, net.user1.utils.integer.MAX_VALUE);
6270   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.ACCOUNT_REMOVED,
6271                               this.accountRemovedListener, this, net.user1.utils.integer.MAX_VALUE);
6272   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGOFF_RESULT,
6273                               this.logoffResultListener, this, net.user1.utils.integer.MAX_VALUE);
6274   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGOFF,
6275                               this.logoffListener, this, net.user1.utils.integer.MAX_VALUE);
6276   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGIN_RESULT,
6277                               this.loginResultListener, this, net.user1.utils.integer.MAX_VALUE);
6278   accountMan.addEventListener(net.user1.orbiter.AccountEvent.LOGIN,
6279                               this.loginListener, this, net.user1.utils.integer.MAX_VALUE);
6280   accountMan.addEventListener(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
6281                               this.changePasswordListener, this, net.user1.utils.integer.MAX_VALUE);
6282   accountMan.addEventListener(net.user1.orbiter.AccountEvent.OBSERVE,
6283                               this.observeAccountListener, this, net.user1.utils.integer.MAX_VALUE);
6284   accountMan.addEventListener(net.user1.orbiter.AccountEvent.STOP_OBSERVING,
6285                               this.stopObservingAccountListener, this, net.user1.utils.integer.MAX_VALUE);
6286   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.STOP_WATCHING_FOR_ACCOUNTS_RESULT,
6287                               this.stopWatchingForAccountsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6288   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.WATCH_FOR_ACCOUNTS_RESULT,
6289                               this.watchForAccountsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6290   accountMan.addEventListener(net.user1.orbiter.AccountEvent.OBSERVE_RESULT,
6291                               this.observeAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6292   accountMan.addEventListener(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT,
6293                               this.stopObservingAccountResultListener, this, net.user1.utils.integer.MAX_VALUE);
6294   accountMan.addEventListener(net.user1.orbiter.AccountManagerEvent.SYNCHRONIZE,
6295                               this.synchronizeAccountsListener, this, net.user1.utils.integer.MAX_VALUE);
6296 
6297   server.addEventListener(net.user1.orbiter.ServerEvent.TIME_SYNC, this.timeSyncListener, this, net.user1.utils.integer.MAX_VALUE);
6298 
6299   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE,
6300                                  this.connectFailureListener, this, net.user1.utils.integer.MAX_VALUE);
6301   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CLIENT_KILL_CONNECT,
6302                                  this.clientKillConnectListener, this, net.user1.utils.integer.MAX_VALUE);
6303   connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SERVER_KILL_CONNECT,
6304                                  this.serverKillConnectListener, this, net.user1.utils.integer.MAX_VALUE);
6305   
6306   clientMan.addEventListener(net.user1.orbiter.ClientEvent.OBSERVE,
6307                              this.observeClientListener, this, net.user1.utils.integer.MAX_VALUE);
6308   clientMan.addEventListener(net.user1.orbiter.ClientEvent.STOP_OBSERVING,
6309                              this.stopObservingClientListener, this, net.user1.utils.integer.MAX_VALUE);
6310   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.CLIENT_CONNECTED,
6311                              this.clientConnectedListener, this, net.user1.utils.integer.MAX_VALUE);
6312   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.CLIENT_DISCONNECTED,
6313                              this.clientDisconnectedListener, this, net.user1.utils.integer.MAX_VALUE);
6314   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_CLIENTS_RESULT,
6315                              this.stopWatchingForClientsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6316   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_CLIENTS_RESULT,
6317                              this.watchForClientsResultListener, this, net.user1.utils.integer.MAX_VALUE);
6318   clientMan.addEventListener(net.user1.orbiter.ClientEvent.OBSERVE_RESULT,
6319                              this.observeClientResultListener, this, net.user1.utils.integer.MAX_VALUE);
6320   clientMan.addEventListener(net.user1.orbiter.ClientEvent.STOP_OBSERVING_RESULT,
6321                              this.stopObservingClientResultListener, this, net.user1.utils.integer.MAX_VALUE);
6322   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE,
6323                              this.synchronizeClientsListener, this, net.user1.utils.integer.MAX_VALUE);
6324   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.ADDRESS_BANNED,
6325                              this.addressBannedListener, this, net.user1.utils.integer.MAX_VALUE);
6326   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.ADDRESS_UNBANNED,
6327                              this.addressUnbannedListener, this, net.user1.utils.integer.MAX_VALUE);
6328   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT,
6329                              this.stopWatchingForBannedAddressesResultListener, this, net.user1.utils.integer.MAX_VALUE);
6330   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.WATCH_FOR_BANNED_ADDRESSES_RESULT,
6331                              this.watchForBannedAddressesResultListener, this, net.user1.utils.integer.MAX_VALUE);
6332   clientMan.addEventListener(net.user1.orbiter.ClientManagerEvent.SYNCHRONIZE_BANLIST,
6333                              this.synchronizeBanlistListener, this, net.user1.utils.integer.MAX_VALUE);
6334   
6335                            
6336   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.readyListener, this, net.user1.utils.integer.MAX_VALUE);
6337   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE, this.protocolIncompatibleListener, this, net.user1.utils.integer.MAX_VALUE);
6338   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED, this.connectRefusedListener, this, net.user1.utils.integer.MAX_VALUE);
6339         
6340   this.log.addEventListener(net.user1.logger.LogEvent.LEVEL_CHANGE, this.logLevelChangeListener, this, net.user1.utils.integer.MAX_VALUE);
6341 };    
6342 
6343         
6344 // =============================================================================
6345 // Logger EVENT LISTENERS
6346 // =============================================================================
6347   
6348 /**
6349  * @private
6350  */        
6351 net.user1.orbiter.CoreEventLogger.prototype.logLevelChangeListener = function (e) {
6352   this.log.info("[LOGGER] Log level set to: [" + e.getLevel() + "].");
6353 };
6354 
6355 // =============================================================================
6356 // Orbiter EVENT LISTENERS
6357 // =============================================================================
6358   
6359 /**
6360  * @private
6361  */        
6362 net.user1.orbiter.CoreEventLogger.prototype.readyListener = function (e) {
6363   this.log.info("[ORBITER] Orbiter now connected and ready.");
6364 };
6365   
6366 /**
6367  * @private
6368  */        
6369 net.user1.orbiter.CoreEventLogger.prototype.protocolIncompatibleListener = function (e) {
6370   this.log.warn("[ORBITER] Orbiter UPC protocol incompatibility detected. Client "
6371            + "UPC version: " + e.target.getSystem().getUPCVersion().toString()
6372            + ". Server version: " + e.getServerUPCVersion().toString() + ".");
6373 };
6374 
6375 /**
6376  * @private
6377  */        
6378 net.user1.orbiter.CoreEventLogger.prototype.connectRefusedListener = function (e) {
6379   if (e.getConnectionRefusal().reason == net.user1.orbiter.ConnectionRefusalReason.BANNED) {
6380     this.log.warn("[ORBITER] Union Server refused the connection because the"
6381              + " client address is banned for the following reason: [" 
6382              + e.getConnectionRefusal().banReason + "]. The ban started at: ["
6383              + new Date(e.getConnectionRefusal().bannedAt) + "]. The ban duration is: ["
6384              + net.user1.utils.NumericFormatter.msToElapsedDayHrMinSec(e.getConnectionRefusal().banDuration*1000) + "].");
6385   } else {
6386     this.log.warn("[ORBITER] Union Server refused the connection. Reason: [" 
6387              + e.getConnectionRefusal().reason + "]. Description: ["
6388              + e.getConnectionRefusal().description + "].");
6389   }
6390 }
6391 
6392 // =============================================================================
6393 // Server EVENT LISTENERS
6394 // =============================================================================
6395 
6396 net.user1.orbiter.CoreEventLogger.prototype.timeSyncListener = function (e) {
6397   this.log.info("[SERVER] Server time synchronized with client. Approximate time on " + 
6398       "server is now: " + new Date(e.target.getServerTime()));
6399 };
6400 
6401 // =============================================================================
6402 // AccountManager EVENT LISTENERS 
6403 // =============================================================================
6404 net.user1.orbiter.CoreEventLogger.prototype.createAccountResultListener = function (e) {
6405   this.log.info("[ACCOUNT_MANAGER] Result for createAccount(). Account: " 
6406            + e.getUserID() + ", Status: " + e.getStatus());
6407 };
6408 
6409 net.user1.orbiter.CoreEventLogger.prototype.removeAccountResultListener = function (e) {
6410   this.log.info("[ACCOUNT_MANAGER] Result for removeAccount(). Account: "
6411            + e.getUserID() + ", Status: " + e.getStatus());
6412 };
6413 
6414 net.user1.orbiter.CoreEventLogger.prototype.changePasswordResultListener = function (e) {
6415   this.log.info("[ACCOUNT_MANAGER] Result for changePassword(). Account: "
6416            + e.getUserID() + ", Status: " + e.getStatus());
6417 };
6418 
6419 net.user1.orbiter.CoreEventLogger.prototype.accountAddedListener = function (e) {
6420   this.log.info("[ACCOUNT_MANAGER] Account added: " + e.getAccount());
6421 };
6422 
6423 net.user1.orbiter.CoreEventLogger.prototype.accountRemovedListener = function (e) {
6424   this.log.info("[ACCOUNT_MANAGER] Account removed: " + e.getAccount());
6425 };
6426 
6427 net.user1.orbiter.CoreEventLogger.prototype.logoffResultListener = function (e) {
6428   this.log.info("[ACCOUNT_MANAGER] Result for logoff(). Account: "
6429            + e.getAccount() + ", Status: " + e.getStatus());
6430 };
6431 
6432 net.user1.orbiter.CoreEventLogger.prototype.logoffListener = function (e) {
6433   this.log.info("[ACCOUNT_MANAGER] Account logged off: " + e.getAccount());
6434 };
6435 
6436 net.user1.orbiter.CoreEventLogger.prototype.loginResultListener = function (e) {
6437   this.log.info("[ACCOUNT_MANAGER] Result for login(). Account: "
6438            + e.getAccount() + ", Status: " + e.getStatus());
6439 };
6440 
6441 net.user1.orbiter.CoreEventLogger.prototype.loginListener = function (e) {
6442   this.log.info("[ACCOUNT_MANAGER] Account logged in: " + e.getAccount());
6443 };
6444 
6445 net.user1.orbiter.CoreEventLogger.prototype.changePasswordListener = function (e) {
6446   this.log.info("[ACCOUNT_MANAGER] Password changed for account: " + e.getUserID());
6447 };
6448 
6449 net.user1.orbiter.CoreEventLogger.prototype.observeAccountListener = function (e) {
6450   this.log.info("[ACCOUNT_MANAGER] Account observed: " + e.getAccount());
6451 };
6452 
6453 net.user1.orbiter.CoreEventLogger.prototype.stopObservingAccountListener = function (e) {
6454   this.log.info("[ACCOUNT_MANAGER] Stopped observing account: " + e.getUserID());
6455 };
6456 
6457 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForAccountsResultListener = function (e) {
6458   this.log.info("[SERVER] 'Stop watching for accounts' result: " + e.getStatus());
6459 };
6460 
6461 net.user1.orbiter.CoreEventLogger.prototype.watchForAccountsResultListener = function (e) {
6462   this.log.info("[ACCOUNT_MANAGER] 'Watch for accounts' result: " + e.getStatus());
6463 };
6464 
6465 net.user1.orbiter.CoreEventLogger.prototype.observeAccountResultListener = function (e) {
6466   this.log.info("[ACCOUNT_MANAGER] 'Observe account result' for account: "
6467            + e.getAccount() + ", Status: " + e.getStatus());
6468 };
6469 
6470 net.user1.orbiter.CoreEventLogger.prototype.stopObservingAccountResultListener = function (e) {
6471   this.log.info("[ACCOUNT_MANAGER] 'Stop observing account result' for account: "
6472            + e.getUserID() + ", Status: " + e.getStatus());
6473 };
6474 
6475 net.user1.orbiter.CoreEventLogger.prototype.synchronizeAccountsListener = function (e) {
6476   this.log.info("[ACCOUNT_MANAGER] User account list synchronized with server.");
6477 };
6478 
6479 // =============================================================================
6480 // CONNECTION EVENT LISTENERS
6481 // =============================================================================
6482 
6483 net.user1.orbiter.CoreEventLogger.prototype.connectFailureListener = function (e) {
6484   this.log.info("[CONNECTION_MANAGER] " + e.getStatus());
6485 };
6486 
6487 net.user1.orbiter.CoreEventLogger.prototype.serverKillConnectListener = function (e) {
6488   this.log.info("[CONNECTION_MANAGER] Server closed the connection.");
6489 };
6490 
6491 net.user1.orbiter.CoreEventLogger.prototype.clientKillConnectListener = function (e) {
6492   this.log.info("[CONNECTION_MANAGER] Connection to server closed by client.");
6493 };
6494 
6495 // =============================================================================
6496 // RoomManager EVENT LISTENERS
6497 // =============================================================================
6498 
6499 net.user1.orbiter.CoreEventLogger.prototype.watchForRoomsResultListener = function (e) {
6500   this.log.info("[ROOM_MANAGER] 'Watch for rooms' result for qualifier [" 
6501             + e.getRoomIdQualifier() + "]: " + e.getStatus());
6502 };
6503 
6504 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForRoomsResultListener = function (e) {
6505   this.log.info("[ROOM_MANAGER] 'Stop watching for rooms' result for"
6506             + " qualifier [" + e.getRoomIdQualifier() 
6507             + "]: " + e.getStatus());
6508 };
6509 
6510 net.user1.orbiter.CoreEventLogger.prototype.createRoomResultListener = function (e) {
6511   this.log.info("[ROOM_MANAGER] Room creation result for room [" 
6512             + e.getRoomID() + "]: " + e.getStatus());
6513 };
6514 
6515 net.user1.orbiter.CoreEventLogger.prototype.removeRoomResultListener = function (e) {
6516   this.log.info("[ROOM_MANAGER] Room removal result for room [" 
6517             + e.getRoomID() + "]: " + e.getStatus());
6518 };
6519 
6520 net.user1.orbiter.CoreEventLogger.prototype.roomAddedListener = function (e) {
6521   this.log.info("[ROOM_MANAGER] Room added: " + e.getRoom() + ".");
6522 };
6523 
6524 net.user1.orbiter.CoreEventLogger.prototype.roomRemovedListener = function (e) {
6525   this.log.info("[ROOM_MANAGER] Room removed: " + e.getRoom() + ".");
6526 };
6527 
6528 net.user1.orbiter.CoreEventLogger.prototype.roomCountListener = function (e) {
6529   this.log.info("[ROOM_MANAGER] New room count: " + e.getNumRooms() + ".");
6530 };
6531 
6532 net.user1.orbiter.CoreEventLogger.prototype.joinRoomResultListener = function (e) {
6533   this.log.info("[ROOM_MANAGER] Join result for room [" 
6534             + e.getRoomID() + "]: " + e.getStatus());
6535 };
6536 
6537 net.user1.orbiter.CoreEventLogger.prototype.leaveRoomResultListener = function (e) {
6538   this.log.info("[ROOM_MANAGER] Leave result for room [" 
6539             + e.getRoomID() + "]: " + e.getStatus());
6540 };
6541 
6542 net.user1.orbiter.CoreEventLogger.prototype.observeRoomResultListener = function (e) {
6543   this.log.info("[ROOM_MANAGER] Observe result for room [" 
6544             + e.getRoomID() + "]: " + e.getStatus());
6545 };
6546 
6547 net.user1.orbiter.CoreEventLogger.prototype.stopObservingRoomResultListener = function (e) {
6548   this.log.info("[ROOM_MANAGER] Stop observing result for room [" 
6549             + e.getRoomID() + "]: " + e.getStatus());
6550 };
6551 
6552 // =============================================================================
6553 // ClientManager EVENT LISTENERS
6554 // =============================================================================
6555 
6556 net.user1.orbiter.CoreEventLogger.prototype.observeClientListener = function (e) {
6557   this.log.info("[CLIENT_MANAGER] Client observed: " + e.getClient());
6558 };
6559 
6560 net.user1.orbiter.CoreEventLogger.prototype.stopObservingClientListener = function (e) {
6561   this.log.info("[CLIENT_MANAGER] Stopped observing client: " + e.getClient());
6562 };
6563 
6564 net.user1.orbiter.CoreEventLogger.prototype.clientConnectedListener = function (e) {
6565   this.log.info("[CLIENT_MANAGER] Foreign client connected. ClientID: [" + e.getClientID() 
6566            + "].");
6567 };
6568 
6569 net.user1.orbiter.CoreEventLogger.prototype.clientDisconnectedListener = function (e) {
6570   this.log.info("[CLIENT_MANAGER] Foreign client disconnected. ClientID: [" + e.getClientID() 
6571            + "].");
6572 };
6573 
6574 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForClientsResultListener = function (e) {
6575   this.log.info("[CLIENT_MANAGER] 'Stop watching for clients' result: " + e.getStatus());
6576 };
6577 
6578 net.user1.orbiter.CoreEventLogger.prototype.watchForClientsResultListener = function (e) {
6579   this.log.info("[CLIENT_MANAGER] 'Watch for clients' result: " + e.getStatus());
6580 };
6581 
6582 net.user1.orbiter.CoreEventLogger.prototype.observeClientResultListener = function (e) {
6583   this.log.info("[CLIENT_MANAGER] 'Observe client' result for client: " 
6584            + e.getClient() + ", status: " + e.getStatus());
6585 };
6586 
6587 net.user1.orbiter.CoreEventLogger.prototype.stopObservingClientResultListener = function (e) {
6588   this.log.info("[CLIENT_MANAGER] 'Stop observing client' result for client: " 
6589            + e.getClient() + ", status: " + e.getStatus());
6590 };
6591 
6592 net.user1.orbiter.CoreEventLogger.prototype.synchronizeClientsListener = function (e) {
6593   this.log.info("[CLIENT_MANAGER] Client list synchronized with server.");
6594 };
6595 
6596 net.user1.orbiter.CoreEventLogger.prototype.addressBannedListener = function (e) {
6597   this.log.info("[CLIENT_MANAGER] Client address banned: [" + e.getAddress() 
6598            + "].");
6599 };
6600 
6601 net.user1.orbiter.CoreEventLogger.prototype.addressUnbannedListener = function (e) {
6602   this.log.info("[CLIENT_MANAGER] Client address unbanned. ClientID: [" + e.getAddress() 
6603            + "].");
6604 };
6605 
6606 net.user1.orbiter.CoreEventLogger.prototype.stopWatchingForBannedAddressesResultListener = function (e) {
6607   this.log.info("[CLIENT_MANAGER] 'Stop watching for banned addresses' result: " + e.getStatus());
6608 };
6609 
6610 net.user1.orbiter.CoreEventLogger.prototype.watchForBannedAddressesResultListener = function (e) {
6611   this.log.info("[CLIENT_MANAGER] 'Watch for banned addresses' result: " + e.getStatus());
6612 };
6613 
6614 net.user1.orbiter.CoreEventLogger.prototype.synchronizeBanlistListener = function (e) {
6615   this.log.info("[CLIENT_MANAGER] Banned list synchronized with server.");
6616 };
6617 
6618 
6619 
6620 
6621 
6622 //==============================================================================
6623 // CLASS DECLARATION
6624 //==============================================================================
6625 /** 
6626  * @class
6627  * The CoreMessageListener class is an internal class that responds to the 
6628  * built-in UPC messages sent by the Union Server to the Orbiter. The 
6629  * CoreMessageListener class does not define any public methods or variables.
6630  * 
6631  * @private
6632  */
6633 net.user1.orbiter.CoreMessageListener = function (orbiter) {
6634   /**
6635    * @type net.user1.orbiter.Orbiter
6636    */
6637   this.orbiter = orbiter;
6638   this.log = orbiter.getLog();      
6639   this.registerCoreListeners();
6640   this.orbiter.getConnectionManager().addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
6641                                                        this.selectConnectionListener, this);
6642 
6643   this.roomMan = this.orbiter.getRoomManager();
6644   this.accountMan = this.orbiter.getAccountManager();
6645   this.clientMan = this.orbiter.getClientManager();
6646   this.snapshotMan = this.orbiter.getSnapshotManager();
6647 };
6648 
6649 //==============================================================================
6650 // INSTANCE METHODS
6651 //==============================================================================
6652 net.user1.orbiter.CoreMessageListener.prototype.registerCoreListeners = function () {
6653   var msgMan = this.orbiter.getMessageManager();
6654   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM, this.u6, this);
6655   msgMan.addMessageListener(net.user1.orbiter.UPC.RECEIVE_MESSAGE, this.u7, this);
6656   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE, this.u8, this);
6657   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ATTR_UPDATE, this.u9, this);
6658   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_METADATA, this.u29, this);
6659   msgMan.addMessageListener(net.user1.orbiter.UPC.CREATE_ROOM_RESULT, this.u32, this);
6660   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROOM_RESULT, this.u33, this);
6661   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT, this.u34, this);
6662   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM, this.u36, this);
6663   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM, this.u37, this);
6664   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT, this.u38, this);
6665   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ADDED, this.u39, this);
6666   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_REMOVED, this.u40, this);
6667   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT, this.u42, this);
6668   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT, this.u43, this);
6669   msgMan.addMessageListener(net.user1.orbiter.UPC.LEFT_ROOM, this.u44, this);
6670   msgMan.addMessageListener(net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT, this.u46, this);
6671   msgMan.addMessageListener(net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT, this.u47, this);
6672   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT, this.u48, this);
6673   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGIN_RESULT, this.u49, this);
6674   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_SNAPSHOT, this.u54, this);
6675   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM, this.u59, this);
6676   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT, this.u60, this);
6677   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM, this.u62, this);
6678   msgMan.addMessageListener(net.user1.orbiter.UPC.SERVER_HELLO, this.u66, this);
6679   msgMan.addMessageListener(net.user1.orbiter.UPC.JOIN_ROOM_RESULT, this.u72, this);
6680   msgMan.addMessageListener(net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT, this.u73, this);
6681   msgMan.addMessageListener(net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT, this.u74, this);
6682   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT, this.u75, this);
6683   msgMan.addMessageListener(net.user1.orbiter.UPC.LEAVE_ROOM_RESULT, this.u76, this);
6684   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT, this.u77, this);
6685   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT, this.u78, this);
6686   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_ATTR_REMOVED, this.u79, this);
6687   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT, this.u80, this);
6688   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED, this.u81, this);
6689   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT, this.u82, this);
6690   msgMan.addMessageListener(net.user1.orbiter.UPC.SESSION_TERMINATED, this.u84, this);
6691   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGOFF_RESULT, this.u87, this);
6692   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGGED_IN, this.u88, this);
6693   msgMan.addMessageListener(net.user1.orbiter.UPC.LOGGED_OFF, this.u89, this);
6694   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED, this.u90, this);
6695   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENTLIST_SNAPSHOT, this.u101, this);
6696   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER, this.u102, this);
6697   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER, this.u103, this);
6698   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_SNAPSHOT, this.u104, this);
6699   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT, this.u105, this);
6700   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT, this.u106, this);
6701   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT, this.u107, this);
6702   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT, this.u108, this);
6703   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT, this.u109, this);
6704   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT, this.u110, this);
6705   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_ADDED, this.u111, this);
6706   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_REMOVED, this.u112, this);
6707   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT, this.u113, this);
6708   msgMan.addMessageListener(net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT, this.u114, this);
6709   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT, this.u115, this);
6710   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT, this.u116, this);
6711   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT, this.u117, this);
6712   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT, this.u118, this);
6713   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_OBSERVED, this.u119, this);
6714   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT, this.u120, this);
6715   msgMan.addMessageListener(net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT, this.u123, this);
6716   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_OBSERVED, this.u124, this);
6717   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT, this.u125, this);
6718   msgMan.addMessageListener(net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT, this.u126, this);
6719   msgMan.addMessageListener(net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE, this.u127, this);
6720   msgMan.addMessageListener(net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE, this.u128, this);
6721   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM, this.u129, this);
6722   msgMan.addMessageListener(net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM, this.u130, this);
6723   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE, this.u131, this);
6724   msgMan.addMessageListener(net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE, this.u132, this);
6725   msgMan.addMessageListener(net.user1.orbiter.UPC.ADD_ROLE_RESULT, this.u134, this);
6726   msgMan.addMessageListener(net.user1.orbiter.UPC.REMOVE_ROLE_RESULT, this.u136, this);
6727   msgMan.addMessageListener(net.user1.orbiter.UPC.BAN_RESULT, this.u138, this);
6728   msgMan.addMessageListener(net.user1.orbiter.UPC.UNBAN_RESULT, this.u140, this);
6729   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT, this.u142, this);
6730   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT, this.u144, this);
6731   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT, this.u146, this);
6732   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED, this.u147, this);
6733   msgMan.addMessageListener(net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED, this.u148, this);
6734   msgMan.addMessageListener(net.user1.orbiter.UPC.KICK_CLIENT_RESULT, this.u150, this);
6735   msgMan.addMessageListener(net.user1.orbiter.UPC.SERVERMODULELIST_SNAPSHOT, this.u152, this);
6736   msgMan.addMessageListener(net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT_RESULT, this.u155, this);
6737   msgMan.addMessageListener(net.user1.orbiter.UPC.UPC_STATS_SNAPSHOT, this.u156, this);
6738   msgMan.addMessageListener(net.user1.orbiter.UPC.RESET_UPC_STATS_RESULT, this.u158, this);
6739   msgMan.addMessageListener(net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS_RESULT, this.u160, this);
6740   msgMan.addMessageListener(net.user1.orbiter.UPC.PROCESSED_UPC_ADDED, this.u161, this);
6741   msgMan.addMessageListener(net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT, this.u163, this);
6742   msgMan.addMessageListener(net.user1.orbiter.UPC.NODELIST_SNAPSHOT, this.u166, this);
6743   msgMan.addMessageListener(net.user1.orbiter.UPC.GATEWAYS_SNAPSHOT, this.u168, this);
6744 }
6745 
6746 net.user1.orbiter.CoreMessageListener.prototype.createHashFromArg = function (arg) {
6747   var list = arg.split(net.user1.orbiter.Tokens.RS);
6748   var hash = new Object();
6749 
6750   for (var i = 0; i < list.length; i += 2) {
6751     hash[list[i]] = list[i+1];
6752   }
6753   return hash;
6754 };
6755 
6756 net.user1.orbiter.CoreMessageListener.prototype.selectConnectionListener = function (e) {
6757   var msgMan = this.orbiter.getMessageManager();
6758   if (msgMan.removeListenersOnDisconnect) {
6759     this.registerCoreListeners();
6760   }
6761 };
6762   
6763 /**
6764  * Room joined.
6765  */
6766 net.user1.orbiter.CoreMessageListener.prototype.u6 = function (roomID) {
6767   // Add the room to the occupied room list
6768   var room = this.roomMan.addOccupiedRoom(roomID);
6769   // Tell the room to do its join duties
6770   room.doJoin();
6771   // Fire JOIN through the client
6772   var selfClient = this.clientMan.self();
6773   if (selfClient) {
6774     selfClient.fireJoinRoom(room, roomID);
6775   }
6776 };
6777 
6778 /**
6779  * Handles sendMessage() calls sent by other clients.
6780  */
6781 net.user1.orbiter.CoreMessageListener.prototype.u7 = function (message,
6782                                                                broadcastType,
6783                                                                fromClientID,
6784                                                                toRoomID) {
6785   var msgMan = this.orbiter.getMessageManager();
6786   var listenerError;
6787   var fromClient;
6788   var toRoom;
6789   var args;  // Args passed to the messsage listener
6790   var userDefinedArgs = Array.prototype.slice.call(arguments).slice(4);
6791 
6792   // Retrieve the Room object for the recipient room. 
6793   toRoom = this.roomMan.getRoom(toRoomID);
6794   
6795   // Retrieve the Client object for the sender
6796   if (fromClientID == "") {
6797     // No client ID was supplied, so the message was generated by the
6798     // server, not by a client, so set fromClient to null.
6799     fromClient = null;
6800   } else {
6801     // A valid ID was supplied, so find or create the matching IClient object
6802     fromClient = this.clientMan.getClient(fromClientID);
6803     fromClient = fromClient == null ? this.clientMan.requestClient(fromClientID) : fromClient;
6804   }
6805 
6806   // ===== To Clients, or To Server =====
6807   // If the message was sent to a specific client, a list of specific clients,
6808   // or to the whole server, then args passed to registered message listeners 
6809   // are: the Client object plus all user-defined arguments originally passed
6810   // to sendMessage().       
6811   if (broadcastType != net.user1.orbiter.ReceiveMessageBroadcastType.TO_ROOMS) {
6812     args = [fromClient].concat(userDefinedArgs);
6813     try {
6814       msgMan.notifyMessageListeners(message, args);
6815     } catch (e) {
6816       listenerError = e;
6817     }
6818   } else {
6819   
6820     // ===== To Rooms =====
6821     // Check if the room is valid
6822     if (toRoom == null) { 
6823       this.log.warn("Message (u7) received for unknown room: [" + toRoomID + "]"
6824         + "Message: [" + message + "]");
6825       return;
6826     }
6827   
6828     // RECEIVE_MESSAGE was a response to SEND_MESSAGE_TO_ROOMS, so 
6829     // we notify listeners only if they asked to be told about messages 
6830     // sent to the recipient room.
6831 
6832     // First, get the list of messsage listeners for this message
6833     var listeners = msgMan.getMessageListeners(message);
6834 
6835     // Split the recipient room ID into two parts
6836     var toRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(toRoomID);
6837     var toRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(toRoomID);
6838 
6839     // If the message can be dispatched, set to true.
6840     var listenerFound; 
6841     // If the listener isn't interested in messages sent to the 
6842     // recipient room, set to true.
6843     var listenerIgnoredMessage;
6844                                          
6845     // ===== Run once for each message listener =====
6846     var messageListener;
6847     for (var i = 0; i < listeners.length; i++) {
6848       messageListener = listeners[i];
6849       
6850       // Assume this listener ignored the message until we prove it didn't
6851       listenerIgnoredMessage = true;
6852       
6853       // --- Has no "forRoomIDs" filter ---
6854       // If the listener doesn't specify any forRoomIDs, then 
6855       // just send it the message notification. (No-rooms-specified
6856       // means the listener wants all of these messages, no matter
6857       // which room they were sent to.) This listener is told which 
6858       // room the message was sent to via args[1] (toRoomID).
6859       if (messageListener.getForRoomIDs() == null) {
6860         args = [fromClient, toRoom].concat(userDefinedArgs);
6861         try {
6862           messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
6863         } catch (e) {
6864           listenerError = e;
6865         }
6866         listenerFound = true;
6867         listenerIgnoredMessage = false;
6868         continue;  // Done with this listener. On to the next.
6869       }
6870       
6871       // --- Has a "forRoomIDs" filter ---
6872       // If the message was sent to any of the rooms the listener is 
6873       // interested in, then notify that listener. Note that a listener 
6874       // for messages sent to room foo.* means the listener wants 
6875       // notifications for all rooms whose ids start with foo.
6876       var listenerRoomIDs  = messageListener.getForRoomIDs();
6877       var listenerRoomQualifier;
6878       var listenerRoomSimpleID;
6879       // ===== Run once for each room id =====
6880       var listenerRoomIDString;
6881       for (var j = 0; j < listenerRoomIDs.length; j++) {
6882         listenerRoomIDString = listenerRoomIDs[j];
6883         // Split the room id
6884         listenerRoomQualifier = net.user1.orbiter.RoomIDParser.getQualifier(listenerRoomIDString);
6885         listenerRoomSimpleID  = net.user1.orbiter.RoomIDParser.getSimpleRoomID(listenerRoomIDString);
6886 
6887         // Check if the listener is interested in the recipient room...
6888         if (listenerRoomQualifier == toRoomQualifier
6889             && 
6890             (listenerRoomSimpleID == toRoomSimpleID
6891              || listenerRoomSimpleID == "*")) {
6892           // Found a match. Notify the listener...
6893             
6894           // Prepare args.
6895           if (listenerRoomIDs.length == 1) {
6896             // The listener is interested in messages sent to a 
6897             // specific room only, so omit the "toRoom" arg.
6898             // (The listener already knows the target room because 
6899             // it's only notified if the message was sent to that room.)
6900             args = [fromClient].concat(userDefinedArgs);
6901           } else {
6902             // The listener is interested in messages sent to 
6903             // multiple rooms. In this case, we have to 
6904             // include the "toRoom" arg so the listener knows 
6905             // which room received the message.
6906             args = [fromClient, toRoom].concat(userDefinedArgs);
6907           }
6908           
6909           try {
6910             messageListener.getListenerFunction().apply(messageListener.getThisArg(), args);
6911           } catch (e) {
6912             listenerError = e;
6913           }
6914           listenerFound = true;
6915           listenerIgnoredMessage = false;
6916           break; // Stop looking at this listener's room ids
6917         }
6918       } // Done looking at this listener's room ids
6919       if (listenerIgnoredMessage) {
6920         this.log.debug("Message listener ignored message: " + message + ". "
6921                        + "Listener registered to receive " 
6922                        + "messages sent to: " + messageListener.getForRoomIDs() 
6923                        + ", but message was sent to: " + toRoomID);
6924       }
6925     } 
6926     if (!listenerFound) {
6927       this.log.warn("No message listener handled incoming message: " 
6928                     + message + ", sent to: " + toRoomID);
6929     }
6930   } // Done looking at listeners for the incoming message
6931   
6932   if (listenerError != null) {
6933     throw new Error("A message listener for incoming message [" + message + "]" +
6934       (fromClient == null ? "" : ", received from client [" + fromClient.getClientID() + "],") + 
6935       " encountered an error:\n\n" + listenerError.toString() +
6936       "\n\nEnsure that all [" + message + "] listeners supply a first" +
6937       " parameter whose datatype is Client (or a compatible type). Listeners" +
6938       " that registered for the message via MessageManager's addMessageListener()" +
6939       " with anything other than a single roomID for the toRoomIDs parameter must" +
6940       " also define a second paramter whose" +
6941       " datatype is Room (or a compatible type). Finally, ensure that" +
6942       " the listener's declared message parameters match the following actual message" +
6943       " arguments:\n    " + userDefinedArgs
6944       + (typeof listenerError.stack === "undefined" ? "" : "\n\nStack trace follows:\n" + listenerError.stack)
6945       );
6946   }
6947 }
6948 
6949 /**
6950  * Client attribute update
6951  */
6952 net.user1.orbiter.CoreMessageListener.prototype.u8 = function (attrScope,
6953                                                                clientID,
6954                                                                userID,
6955                                                                attrName,
6956                                                                attrVal,
6957                                                                attrOptions) { 
6958   var client;
6959   var account;
6960   var options = parseInt(attrOptions);
6961   
6962   if (options &net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
6963     account = this.accountMan.getAccount(userID);
6964     if (account != null) {
6965       account.getAttributeManager().setAttributeLocal(attrName, attrVal, attrScope);
6966     } else {
6967       throw new Error("[CORE_MESSAGE_LISTENER] Received an attribute update for "
6968         + " an unknown user account [" + userID + "]. Please report this error with"
6969         + " the following log to union@user1.net.\n" 
6970         + this.log.getHistory().join("\n"));
6971     }
6972   } else {
6973     client = this.clientMan.getInternalClient(clientID);
6974     if (client != null) {
6975       client.getAttributeManager().setAttributeLocal(attrName, attrVal, attrScope);
6976     } else {
6977       throw new Error("[CORE_MESSAGE_LISTENER] Received an attribute update for "
6978         + " an unknown client [" + clientID + "]. Please report this error with"
6979         + " the following log to union@user1.net.\n" 
6980         + this.log.getHistory().join("\n"));
6981     }
6982   }
6983 };
6984 
6985 /**
6986  * Room attribute update
6987  */
6988 net.user1.orbiter.CoreMessageListener.prototype.u9 = function (roomID, 
6989                                                                byClientID,
6990                                                                attrName,
6991                                                                attrVal) {
6992   var theRoom = this.roomMan.getRoom(roomID);
6993   var byClient;
6994   
6995   // Quit if the room isn't found
6996   if (theRoom == null) {
6997     this.log.warn("Room attribute update received for server-side room with no" + 
6998              " matching client-side Room object. Room ID [" +
6999              roomID + "]. Attribute: [" + attrName + "].");
7000     return;
7001   }
7002 
7003   // Retrieve the Client object for the sender
7004   if (byClientID == "") {
7005     // No client ID was supplied, so the message was generated by the
7006     // server, not by a client, so set fromClient to null.
7007     byClient = null;
7008   } else {
7009     // A valid ID was supplied, so find or create the matching IClient object
7010     byClient = this.clientMan.getClient(byClientID);
7011     byClient = byClient == null ? this.clientMan.requestClient(byClientID) : byClient;
7012   }
7013 
7014   theRoom.getAttributeManager().setAttributeLocal(attrName, attrVal, net.user1.orbiter.Tokens.GLOBAL_ATTR, byClient);
7015 };
7016 
7017 /**
7018  * CLIENT_METADATA
7019  */
7020 net.user1.orbiter.CoreMessageListener.prototype.u29 = function (id) {
7021   var theClient = this.clientMan.requestClient(id);
7022   this.clientMan.setSelf(theClient);
7023 };
7024 
7025 /**
7026  * CREATE_ROOM_RESULT
7027  */
7028 net.user1.orbiter.CoreMessageListener.prototype.u32 = function (roomID, status) {
7029   var theRoom = this.roomMan.getRoom(roomID);
7030   switch (status) {
7031     case net.user1.orbiter.Status.ERROR:
7032     case net.user1.orbiter.Status.SUCCESS:
7033     case net.user1.orbiter.Status.ROOM_EXISTS:
7034     case net.user1.orbiter.Status.PERMISSION_DENIED:
7035       this.roomMan.fireCreateRoomResult(net.user1.orbiter.RoomIDParser.getQualifier(roomID),
7036                                         net.user1.orbiter.RoomIDParser.getSimpleRoomID(roomID),
7037                                         status);
7038       break;
7039     
7040     default: 
7041       this.log.warn("Unrecognized status code for u32."
7042                + " Room ID: [" + roomID + "], status: [" + status + "].");
7043   }
7044 };
7045 
7046 /**
7047  * REMOVE_ROOM_RESULT
7048  */
7049 net.user1.orbiter.CoreMessageListener.prototype.u33 = function (roomID, status) {
7050   this.roomMan.fireRemoveRoomResult(net.user1.orbiter.RoomIDParser.getQualifier(roomID),
7051                                     net.user1.orbiter.RoomIDParser.getSimpleRoomID(roomID),
7052                                     status);
7053   switch (status) {
7054     case net.user1.orbiter.Status.ERROR: 
7055       this.log.warn("Server error for room removal attempt: " + roomID);
7056       break;
7057     case net.user1.orbiter.Status.PERMISSION_DENIED: 
7058       this.log.info("Attempt to remove room [" + roomID 
7059                + "] failed. Permission denied. See server log for details.");
7060       break;
7061       
7062     case net.user1.orbiter.Status.SUCCESS:
7063     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7064       if (this.roomMan.getRoom(roomID) != null) {
7065         this.roomMan.disposeRoom(roomID);
7066       }
7067       break;
7068     
7069     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7070     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7071       break;
7072     
7073     default: 
7074       this.log.warn("Unrecognized status code for u33."
7075                + " Room ID: [" + roomID + "], status: [" + status + "].");
7076   }
7077 };
7078 
7079 /**
7080  * CLIENTCOUNT_SNAPSHOT
7081  */
7082 net.user1.orbiter.CoreMessageListener.prototype.u34 = function (requestID,
7083                                                                 numClients) {
7084   this.snapshotMan.receiveClientCountSnapshot(requestID, parseInt(numClients));
7085 };
7086 
7087 /**
7088  * CLIENT_ADDED_TO_ROOM
7089  */
7090 net.user1.orbiter.CoreMessageListener.prototype.u36 = function (roomID,
7091                                                      clientID,
7092                                                      userID,
7093                                                      globalAttributes,
7094                                                      roomAttributes) {
7095   var theClient = this.clientMan.requestClient(clientID);
7096   var account = this.accountMan.requestAccount(userID);
7097   var clientManifest;
7098   if (account != null
7099       && theClient.getAccount() != account) {
7100     theClient.setAccount(account);
7101   }
7102 
7103   // If it's not the current client, set the client's attributes. 
7104   // (The current client obtains its own attributes through separate u8s.)
7105   var theRoom = this.roomMan.getRoom(roomID);
7106   if (!theClient.isSelf()) {
7107     clientManifest = new net.user1.orbiter.ClientManifest();
7108     clientManifest.deserialize(clientID, userID, null, 
7109                                null, globalAttributes, [roomID, roomAttributes]);
7110     theClient.synchronize(clientManifest);
7111     
7112     // If the client is observed, don't fire JOIN; observed clients always
7113     // fire JOIN based on observation updates. Likewise, don't fire JOIN
7114     // on self; self fires JOIN when it receives a u6.
7115     if (!this.clientMan.isObservingClient(clientID)) {
7116       theClient.fireJoinRoom(theRoom, roomID);
7117     }
7118   }
7119 
7120   // Add the client to the given room.
7121   theRoom.addOccupant(theClient);
7122 };
7123 
7124 /**
7125  * CLIENT_REMOVED_FROM_ROOM
7126  */
7127 net.user1.orbiter.CoreMessageListener.prototype.u37 = function (roomID, 
7128                                                                 clientID) {
7129   // Remove the room from the client's list of occupied rooms
7130   var theClient = this.clientMan.requestClient(clientID);
7131   var theRoom = this.roomMan.getRoom(roomID);
7132 
7133   // Remove the client from the given room
7134   theRoom.removeOccupant(clientID);
7135   
7136   // Don't fire LEAVE on self; self fires LEAVE when it receives a u44.
7137   if (!theClient.isSelf()) {
7138     // If the client is observed, don't fire LEAVE; observed clients always
7139     // fire LEAVE based on observation updates.
7140     if (!this.clientMan.isObservingClient(clientID)) {
7141       theClient.fireLeaveRoom(theRoom, roomID);
7142     }
7143   }
7144 };
7145 
7146 /**
7147  * ROOMLIST_SNAPSHOT
7148  */
7149 net.user1.orbiter.CoreMessageListener.prototype.u38 = function (requestID,
7150                                                                 requestedRoomIDQualifier, 
7151                                                                 recursive) {
7152   var args = Array.prototype.slice.call(arguments).slice(3);
7153   var roomQualifier;
7154   var roomIDs;
7155   var roomList = [];
7156   
7157   if (requestID == "") {
7158     // Synchronize
7159     for (var i = 0; i < args.length; i+=2) {
7160       roomQualifier = args[i];
7161       roomIDs       = args[i+1].split(net.user1.orbiter.Tokens.RS);
7162       
7163       this.roomMan.setWatchedRooms(roomQualifier, roomIDs);
7164     }
7165   } else {
7166     // Snapshot
7167     for (i = 0; i < args.length; i+=2) {
7168       roomQualifier = args[i];
7169       roomIDs = args[i+1].split(net.user1.orbiter.Tokens.RS);
7170       for (var j = 0; j < roomIDs.length; j++) {
7171         roomList.push(roomQualifier + (roomQualifier == "" ? "" : ".") + roomIDs[j]);
7172       }
7173     }
7174     this.snapshotMan.receiveRoomListSnapshot(requestID, roomList, requestedRoomIDQualifier, recursive == "true");
7175   }
7176 };
7177 
7178 /**
7179  * ROOM_ADDED
7180  */
7181 net.user1.orbiter.CoreMessageListener.prototype.u39 = function (roomID) { 
7182   // Add the room 
7183   this.roomMan.addWatchedRoom(roomID);
7184 };
7185 
7186 /**
7187  * ROOM_REMOVED
7188  */
7189 net.user1.orbiter.CoreMessageListener.prototype.u40 = function (roomID) {
7190   this.roomMan.removeWatchedRoom(roomID);
7191   if (this.roomMan.getRoom(roomID) != null) {
7192     this.roomMan.disposeRoom(roomID);
7193   }
7194 };
7195 
7196 /**
7197  * WATCH_FOR_ROOMS_RESULT
7198  */
7199 net.user1.orbiter.CoreMessageListener.prototype.u42 = function (roomIdQualifier, recursive, status) { 
7200   // Broadcast the result of the observation attempt.
7201   this.roomMan.fireWatchForRoomsResult(roomIdQualifier, status);
7202   switch (status) {
7203     case net.user1.orbiter.Status.SUCCESS:
7204     case net.user1.orbiter.Status.ERROR:
7205     case net.user1.orbiter.Status.INVALID_QUALIFIER:
7206     case net.user1.orbiter.Status.ALREADY_WATCHING:
7207     case net.user1.orbiter.Status.PERMISSION_DENIED:
7208       break;
7209       
7210     default: 
7211       this.log.warn("Unrecognized status code for u42."
7212         + " Room ID Qualifier: [" + roomIdQualifier + "], recursive: [" 
7213         + recursive + "], status: [" + status + "].");
7214   }
7215 };
7216 
7217 /**
7218  * STOP_WATCHING_FOR_ROOMS_RESULT
7219  */
7220 net.user1.orbiter.CoreMessageListener.prototype.u43 = function (roomIdQualifier, recursive, status) {
7221   switch (status) {
7222     case net.user1.orbiter.Status.SUCCESS:
7223       if (roomIdQualifier == "" && recursive == "true") {
7224         this.roomMan.removeAllWatchedRooms();
7225       } else {
7226         // Remove all watched rooms for the qualifier
7227         this.roomMan.setWatchedRooms(roomIdQualifier, []);
7228       }
7229     case net.user1.orbiter.Status.ERROR:
7230     case net.user1.orbiter.Status.NOT_WATCHING:
7231     case net.user1.orbiter.Status.INVALID_QUALIFIER:
7232       this.roomMan.fireStopWatchingForRoomsResult(roomIdQualifier, status);
7233       break;
7234       
7235     default: 
7236       this.log.warn("Unrecognized status code for u43."
7237         + " Room ID Qualifier: [" + roomIdQualifier + "], recursive: [" 
7238         + recursive + "], status: [" + status + "].");
7239   }
7240 };
7241 
7242 /**
7243  * LEFT_ROOM
7244  */
7245 net.user1.orbiter.CoreMessageListener.prototype.u44 = function (roomID) {
7246   var leftRoom = this.roomMan.getRoom(roomID);
7247   this.roomMan.removeOccupiedRoom(roomID);
7248   if (leftRoom != null) {
7249     leftRoom.doLeave();
7250     this.clientMan.self().fireLeaveRoom(leftRoom, roomID);
7251   }
7252 };
7253 
7254 /**
7255  * CHANGE_ACCOUNT_PASSWORD_RESULT
7256  */
7257 net.user1.orbiter.CoreMessageListener.prototype.u46 = function (userID, status) {
7258   var account = this.accountMan.getAccount(userID); 
7259   if (account != null) {
7260     account.fireChangePasswordResult(status);
7261   }
7262   this.accountMan.fireChangePasswordResult(userID, status);
7263 };
7264 
7265 /**
7266  * CREATE_ACCOUNT_RESULT
7267  */
7268 net.user1.orbiter.CoreMessageListener.prototype.u47 = function (userID, status) {
7269   switch (status) {
7270     case net.user1.orbiter.Status.SUCCESS:
7271     case net.user1.orbiter.Status.ERROR:
7272     case net.user1.orbiter.Status.ACCOUNT_EXISTS:
7273     case net.user1.orbiter.Status.PERMISSION_DENIED:
7274      this.orbiter.getAccountManager().fireCreateAccountResult(userID, status);
7275       break;
7276     default: 
7277       this.log.warn("Unrecognized status code for u47."
7278         + " Account: [" + userID + "], status: [" + status + "].");
7279   }
7280 };
7281 
7282 /**
7283  * REMOVE_ACCOUNT_RESULT
7284  */
7285 net.user1.orbiter.CoreMessageListener.prototype.u48 = function (userID, status) {
7286   switch (status) {
7287     case net.user1.orbiter.Status.SUCCESS:
7288     case net.user1.orbiter.Status.ERROR:
7289     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7290     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7291     case net.user1.orbiter.Status.PERMISSION_DENIED:
7292      this.orbiter.getAccountManager().fireRemoveAccountResult(userID, status);
7293       break;
7294     default: 
7295       this.log.warn("Unrecognized status code for u48."
7296         + " Account: [" + userID + "], status: [" + status + "].");
7297   }
7298 };
7299 
7300 /**
7301  * LOGIN_RESULT
7302  */
7303 net.user1.orbiter.CoreMessageListener.prototype.u49 = function (userID, status) {
7304   switch (status) {
7305     case net.user1.orbiter.Status.SUCCESS:
7306     case net.user1.orbiter.Status.ERROR:
7307     case net.user1.orbiter.Status.ALREADY_LOGGED_IN:
7308     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7309     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7310     case net.user1.orbiter.Status.PERMISSION_DENIED:
7311       this.orbiter.getAccountManager().fireLoginResult(userID, status);
7312       break;
7313     default:
7314       this.log.warn("Unrecognized status code for u49."
7315         + " Account: [" + userID + "], status: [" + status + "].");
7316   }          
7317 };
7318 
7319 /**
7320  * ROOM_SNAPSHOT
7321  */
7322 net.user1.orbiter.CoreMessageListener.prototype.u54 = function (requestID,
7323                                                                 roomID,
7324                                                                 occupantCount,
7325                                                                 observerCount,
7326                                                                 roomAttributes) {
7327   var clientList = Array.prototype.slice.call(arguments).slice(5);
7328   var clientManifest;
7329   var roomManifest = new net.user1.orbiter.RoomManifest();
7330   var theRoom;
7331   roomManifest.deserialize(roomID, 
7332                            roomAttributes, 
7333                            clientList, 
7334                            parseInt(occupantCount), 
7335                            parseInt(observerCount));
7336   
7337   if (requestID == "") {
7338     // Synchronize
7339     theRoom = this.roomMan.getRoom(roomID);
7340 
7341     if (theRoom == null) { 
7342       // If the server makes the current client join or observe a room, it
7343       // will first send a u54 before sending the u6 or u59 notice. In that
7344       // case, the room might be unknown briefly, so create a cached room
7345       // then wait for the u6 or u59 to arrive.
7346       theRoom = this.roomMan.addCachedRoom(roomID); 
7347     }
7348     
7349     theRoom.synchronize(roomManifest);
7350   } else {
7351     // Snapshot
7352     this.snapshotMan.receiveRoomSnapshot(requestID, roomManifest);
7353   }
7354 };
7355 
7356 
7357 /**
7358  * OBSERVED_ROOM
7359  */
7360 net.user1.orbiter.CoreMessageListener.prototype.u59 = function (roomID) {
7361   // Add the room to the observed room list
7362   var room = this.roomMan.addObservedRoom(roomID);
7363   // Tell the room to do its join duties
7364   room.doObserve();
7365   // Fire OBSERVE through the client
7366   this.clientMan.self().fireObserveRoom(room, roomID);
7367 };
7368 
7369 /**
7370  * GET_ROOM_SNAPSHOT_RESULT
7371  */
7372 net.user1.orbiter.CoreMessageListener.prototype.u60 = function (requestID,
7373                                                                 roomID,
7374                                                                 status) {
7375   switch (status) {
7376     case net.user1.orbiter.Status.SUCCESS:
7377     case net.user1.orbiter.Status.ERROR:
7378     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7379     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7380     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7381     case net.user1.orbiter.Status.PERMISSION_DENIED:
7382       this.snapshotMan.receiveSnapshotResult(requestID, status);
7383       break;
7384     default:
7385       this.log.warn("Unrecognized status code for u60."
7386         + " Request ID: [" + requestID + "], Room ID: [" 
7387         + roomID + "], status: [" + status + "].");
7388   } 
7389 };
7390 
7391 /**
7392  * STOPPED_OBSERVING_ROOM
7393  */
7394 net.user1.orbiter.CoreMessageListener.prototype.u62 = function (roomID) {
7395   var theRoom = this.roomMan.getRoom(roomID);
7396   this.roomMan.removeObservedRoom(roomID);
7397   if (theRoom != null) {
7398     theRoom.doStopObserving();
7399     // self() might return null if a STOP_OBSERVING listener has closed the connection
7400     if (this.clientMan.self() != null) {   
7401       this.clientMan.self().fireStopObservingRoom(theRoom, roomID);
7402     }
7403   }
7404 };
7405 
7406 /**
7407  * SERVER_HELLO
7408  */
7409 net.user1.orbiter.CoreMessageListener.prototype.u66 = function (serverVersion, 
7410                                                                 sessionID,
7411                                                                 serverUPCVersionString,
7412                                                                 protocolCompatible,
7413                                                                 affinityAddress,
7414                                                                 affinityDuration) {
7415   this.log.info("[ORBITER] Server version: " + serverVersion);
7416   this.log.info("[ORBITER] Server UPC version: " + serverUPCVersionString);
7417   
7418   var serverUPCVersion = new net.user1.orbiter.VersionNumber();
7419   serverUPCVersion.fromVersionString(serverUPCVersionString);
7420   this.orbiter.getServer().setVersion(serverVersion);
7421   this.orbiter.getServer().setUPCVersion(serverUPCVersion);
7422 
7423 
7424   var inProgressConnection = this.orbiter.getConnectionManager().getInProgressConnection();
7425   var inProgressConnectionHost = inProgressConnection.getHost();
7426   if (affinityAddress != ""  
7427       && typeof affinityAddress !== "undefined"
7428       && affinityAddress != inProgressConnectionHost) {
7429     this.orbiter.getConnectionManager().setAffinity(inProgressConnectionHost, 
7430                                                     affinityAddress, 
7431                                                     parseFloat(affinityDuration));
7432     inProgressConnection.applyAffinity();
7433   }
7434 };
7435 
7436 
7437 /**
7438  * JOIN_ROOM_RESULT
7439  */
7440 net.user1.orbiter.CoreMessageListener.prototype.u72 = function (roomID,
7441                                                                status) {
7442   var theRoom = this.roomMan.getRoom(roomID);
7443   switch (status) {
7444     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7445       if (this.roomMan.getRoom(roomID) != null) {
7446         this.roomMan.disposeRoom(roomID);
7447       }
7448     
7449     case net.user1.orbiter.Status.ERROR:
7450     case net.user1.orbiter.Status.ROOM_FULL:
7451     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7452     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7453     case net.user1.orbiter.Status.SUCCESS:
7454     case net.user1.orbiter.Status.ALREADY_IN_ROOM:
7455     case net.user1.orbiter.Status.PERMISSION_DENIED:
7456       this.roomMan.fireJoinRoomResult(roomID, status);
7457       if (theRoom != null) {
7458         theRoom.doJoinResult(status);
7459       }
7460       break;
7461     
7462     default: 
7463       this.log.warn("Unrecognized status code for u72."
7464         + " Room ID: [" + roomID + "], status: [" + status + "].");
7465   }
7466 };
7467 
7468 /**
7469  * SET_CLIENT_ATTR_RESULT
7470  */
7471 net.user1.orbiter.CoreMessageListener.prototype.u73 = function (attrScope,
7472                                                                 clientID,
7473                                                                 userID,
7474                                                                 attrName,
7475                                                                 attrOptions,
7476                                                                 status) { 
7477   var theClient;
7478   var theAccount;
7479   
7480   switch (status) {
7481     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7482     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7483       break;
7484       
7485     case net.user1.orbiter.Status.SUCCESS:
7486     case net.user1.orbiter.Status.ERROR:
7487     case net.user1.orbiter.Status.DUPLICATE_VALUE:
7488     case net.user1.orbiter.Status.IMMUTABLE:
7489     case net.user1.orbiter.Status.SERVER_ONLY:
7490     case net.user1.orbiter.Status.EVALUATION_FAILED:
7491     case net.user1.orbiter.Status.PERMISSION_DENIED:
7492       if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7493         // Persistent attr
7494         theAccount = this.accountMan.requestAccount(userID);
7495         theAccount.getAttributeManager().fireSetAttributeResult(attrName, attrScope, status);
7496       } else {
7497         // Non-persistent attr
7498         theClient = this.clientMan.requestClient(clientID);
7499         theClient.getAttributeManager().fireSetAttributeResult(attrName, attrScope, status);
7500       }
7501       break;
7502       
7503     default:
7504       this.log.warn("Unrecognized status received for u73: " + status);
7505   }
7506 };
7507 
7508 /**
7509  * SET_ROOM_ATTR_RESULT
7510  */
7511 net.user1.orbiter.CoreMessageListener.prototype.u74 = function (roomID, 
7512                                                                 attrName,
7513                                                                 status) {
7514   var theRoom = this.roomMan.getRoom(roomID);
7515   
7516   // Quit if the room isn't found
7517   if (theRoom == null) {
7518     this.log.warn("Room attribute update received for room with no" + 
7519       " client-side Room object. Room ID [" +
7520       roomID + "]. Attribute: [" + attrName + "]. Status: ["
7521       + status + "].");
7522     return;
7523   }
7524   
7525   switch (status) {
7526     case net.user1.orbiter.Status.SUCCESS:
7527     case net.user1.orbiter.Status.ERROR:
7528     case net.user1.orbiter.Status.IMMUTABLE:
7529     case net.user1.orbiter.Status.SERVER_ONLY:
7530     case net.user1.orbiter.Status.EVALUATION_FAILED:
7531     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7532     case net.user1.orbiter.Status.PERMISSION_DENIED:
7533       theRoom.getAttributeManager().fireSetAttributeResult(attrName, 
7534                                                            net.user1.orbiter.Tokens.GLOBAL_ATTR,
7535                                                            status);
7536       break;
7537     
7538     default:
7539       this.log.warn("Unrecognized status received for u74: " + status);
7540   }
7541 };
7542 
7543 /**
7544  * GET_CLIENTCOUNT_SNAPSHOT_RESULT
7545  */
7546 net.user1.orbiter.CoreMessageListener.prototype.u75 = function (requestID,
7547                                                                 status) {
7548   this.snapshotMan.receiveSnapshotResult(requestID, status);
7549 };
7550 
7551 /**
7552  * LEAVE_ROOM_RESULT
7553  */
7554 net.user1.orbiter.CoreMessageListener.prototype.u76 = function (roomID, 
7555                                                                 status) {
7556   var leftRoom = this.roomMan.getRoom(roomID);
7557   
7558   switch (status) {
7559     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7560       if (leftRoom != null) {
7561         this.roomMan.disposeRoom(roomID);
7562       }
7563       
7564     case net.user1.orbiter.Status.SUCCESS:
7565     case net.user1.orbiter.Status.ERROR:
7566     case net.user1.orbiter.Status.NOT_IN_ROOM:
7567       this.roomMan.fireLeaveRoomResult(roomID, status);
7568       if (leftRoom != null) {
7569         leftRoom.doLeaveResult(status);
7570       }
7571       break;
7572     
7573     default: 
7574       this.log.warn("Unrecognized status code for u76."
7575         + " Room ID: [" + roomID + "]. Status: [" + status + "].");        
7576   }
7577 };
7578 
7579 /**
7580  * OBSERVE_ROOM_RESULT
7581  */
7582 net.user1.orbiter.CoreMessageListener.prototype.u77 = function (roomID,
7583                       status) {
7584   var theRoom = this.roomMan.getRoom(roomID);
7585   switch (status) { 
7586     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7587       if (theRoom != null) {
7588         this.roomMan.disposeRoom(roomID);
7589       }
7590     
7591     case net.user1.orbiter.Status.ERROR:
7592     case net.user1.orbiter.Status.AUTHORIZATION_REQUIRED:
7593     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7594     case net.user1.orbiter.Status.SUCCESS:
7595     case net.user1.orbiter.Status.ALREADY_OBSERVING:
7596     case net.user1.orbiter.Status.PERMISSION_DENIED:
7597       this.roomMan.fireObserveRoomResult(roomID, status);
7598       
7599       if (theRoom) {
7600         theRoom.doObserveResult(status);
7601       }
7602       break;
7603     
7604     default:
7605       this.log.warn("Unrecognized status code for u77."
7606         + " Room ID: [" + roomID + "], status: " + status + ".");
7607   }
7608 }    
7609 
7610 /**
7611  * STOP_OBSERVING_ROOM_RESULT
7612  */
7613 net.user1.orbiter.CoreMessageListener.prototype.u78 = function (roomID, 
7614                                                                 status) {
7615   var theRoom = this.roomMan.getRoom(roomID);
7616   
7617   switch (status) {
7618     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7619       if (theRoom != null) {
7620         this.roomMan.disposeRoom(roomID);
7621       }
7622     
7623     case net.user1.orbiter.Status.SUCCESS:
7624     case net.user1.orbiter.Status.ERROR:
7625     case net.user1.orbiter.Status.NOT_OBSERVING:
7626       this.roomMan.fireStopObservingRoomResult(roomID, status);
7627       
7628       if (theRoom != null) {
7629         theRoom.doStopObservingResult(status);
7630       }
7631       break;
7632     
7633     default: 
7634       this.log.warn("Unrecognized status code for u78."
7635         + " Room ID: [" + roomID + "], status: " + status + ".");        
7636   }
7637 };
7638 
7639 /**
7640  * ROOM_ATTR_REMOVED
7641  */
7642 net.user1.orbiter.CoreMessageListener.prototype.u79 = function (roomID,
7643                                                                 byClientID,
7644                                                                 attrName) {
7645   var theRoom = this.roomMan.getRoom(roomID);
7646   var theClient;
7647   
7648   // Quit if the room isn't found
7649   if (theRoom == null) {
7650     this.log.warn("Room attribute removal notification received for room with no" + 
7651       " client-side Room object. Room ID [" +
7652       roomID + "]. Attribute: [" + attrName + "].");
7653     return;
7654   }
7655   
7656   // If the clientID is "", the server removed the room, so there's no
7657   // corresponding client.
7658   theClient = byClientID == "" ? null : this.clientMan.requestClient(byClientID);
7659   theRoom.getAttributeManager().removeAttributeLocal(attrName, net.user1.orbiter.Tokens.GLOBAL_ATTR, theClient)
7660 }
7661 
7662 /**
7663  * REMOVE_ROOM_ATTR_RESULT
7664  */
7665 net.user1.orbiter.CoreMessageListener.prototype.u80 = function (roomID,
7666                                                                 attrName,
7667                                                                 status) {
7668   var theRoom = this.roomMan.getRoom(roomID);      
7669   switch (status) {
7670     case net.user1.orbiter.Status.SUCCESS:
7671     case net.user1.orbiter.Status.ERROR:
7672     case net.user1.orbiter.Status.IMMUTABLE:
7673     case net.user1.orbiter.Status.SERVER_ONLY:
7674     case net.user1.orbiter.Status.ROOM_NOT_FOUND:
7675     case net.user1.orbiter.Status.ATTR_NOT_FOUND:
7676     case net.user1.orbiter.Status.PERMISSION_DENIED:
7677       if (theRoom != null) {
7678         theRoom.getAttributeManager().fireDeleteAttributeResult(attrName,
7679                                                                 net.user1.orbiter.Tokens.GLOBAL_ATTR,
7680                                                                 status);
7681       }
7682       break;
7683     
7684     default:
7685       this.log.warn("Unrecognized status received for u80: " + status);
7686   }
7687 };  
7688 
7689 /**
7690  * CLIENT_ATTR_REMOVED
7691  */
7692 net.user1.orbiter.CoreMessageListener.prototype.u81 = function (attrScope,
7693                                                                 clientID,
7694                                                                 userID, 
7695                                                                 attrName,
7696                                                                 attrOptions) {
7697   var client;
7698   var account;
7699   
7700   if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7701     // Persistent attr
7702     account = this.accountMan.requestAccount(userID);
7703     account.getAttributeManager().removeAttributeLocal(attrName, attrScope);
7704   } else {
7705     // Non-persistent attr
7706     client = this.clientMan.requestClient(clientID);
7707     client.getAttributeManager().removeAttributeLocal(attrName, attrScope);
7708   }
7709 };
7710 
7711 /**
7712  * REMOVE_CLIENT_ATTR_RESULT
7713  */
7714 net.user1.orbiter.CoreMessageListener.prototype.u82 = function (attrScope,
7715                                                                 clientID,
7716                                                                 userID,
7717                                                                 attrName,
7718                                                                 attrOptions,
7719                                                                 status) { 
7720   var client;
7721   var account;
7722   
7723   
7724   switch (status) {
7725     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7726     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7727       break;
7728     case net.user1.orbiter.Status.SUCCESS:
7729     case net.user1.orbiter.Status.ERROR:
7730     case net.user1.orbiter.Status.IMMUTABLE:
7731     case net.user1.orbiter.Status.SERVER_ONLY:
7732     case net.user1.orbiter.Status.ATTR_NOT_FOUND: 
7733     case net.user1.orbiter.Status.EVALUATION_FAILED:
7734     case net.user1.orbiter.Status.PERMISSION_DENIED:
7735       if (parseInt(attrOptions) & net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT) {
7736         // Persistent attr
7737         account = this.accountMan.requestAccount(userID);
7738         account.getAttributeManager().fireDeleteAttributeResult(attrName, attrScope, status);
7739       } else {
7740         // Non-persistent attr
7741         client = this.clientMan.requestClient(clientID);
7742         client.getAttributeManager().fireDeleteAttributeResult(attrName, attrScope, status);
7743       }
7744       break;
7745       
7746     default:
7747       this.log.warn("Unrecognized status received for u82: " + status);
7748   }
7749 };
7750 
7751 /**
7752  * SESSION_TERMINATED
7753  */
7754 net.user1.orbiter.CoreMessageListener.prototype.u84 = function () {
7755   this.orbiter.getConnectionManager().dispatchSessionTerminated();
7756 };
7757 
7758 /**
7759  * LOGOFF_RESULT
7760  */
7761 net.user1.orbiter.CoreMessageListener.prototype.u87 = function (userID, status) {
7762   var account = this.accountMan.getAccount(userID);
7763 
7764   switch (status) {
7765     case net.user1.orbiter.Status.SUCCESS:
7766     case net.user1.orbiter.Status.ERROR:
7767     case net.user1.orbiter.Status.AUTHORIZATION_FAILED:
7768     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
7769     case net.user1.orbiter.Status.NOT_LOGGED_IN:
7770     case net.user1.orbiter.Status.PERMISSION_DENIED:
7771       if (account != null) {
7772         account.fireLogoffResult(status);
7773       }
7774       // Tell the account manager
7775       this.accountMan.fireLogoffResult(userID, status);
7776       break;
7777     default:
7778       this.log.warn("Unrecognized status received for u87: " + status);
7779   }
7780 };
7781 
7782 /**
7783  * LOGGED_IN
7784  */
7785 net.user1.orbiter.CoreMessageListener.prototype.u88 = function (clientID, 
7786                                                                 userID,
7787                                                                 globalAttrs) {
7788   var roomAttrs = Array.prototype.slice.call(arguments).slice(3);
7789   var account = this.accountMan.requestAccount(userID);
7790   var client = this.clientMan.requestClient(clientID);
7791   var clientManifest = new net.user1.orbiter.ClientManifest();
7792   clientManifest.deserialize(clientID, userID, null, null, globalAttrs, roomAttrs);      
7793   // Update the account
7794   var scopes = clientManifest.persistentAttributes.getScopes();
7795   var accountAttrs = account.getAttributeManager().getAttributeCollection();
7796   for (var i = scopes.length; --i >= 0;) {
7797     accountAttrs.synchronizeScope(scopes[i], clientManifest.persistentAttributes);
7798   }
7799   
7800   if (client.getAccount() == null) {
7801     // Client doesn't know about this account yet
7802     client.setAccount(account);
7803     client.fireLogin();
7804     account.doLoginTasks();
7805     this.accountMan.fireLogin(account, clientID);
7806   } else {
7807     // Do nothing if the account is known. Logins are reported for 
7808     // observe-account, observe-client, and watch-for-clients, so a 
7809     // client might receive multiple login notifications.
7810   }
7811 };
7812 
7813 /**
7814  * LOGGED_OFF
7815  */
7816 net.user1.orbiter.CoreMessageListener.prototype.u89 = function (clientID, userID) {
7817   var client = this.clientMan.getInternalClient(clientID);
7818   var account = this.accountMan.getAccount(userID);
7819   
7820   if (account != null) {
7821     if (account.getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN) {
7822       if (client != null) {
7823         client.fireLogoff(userID);
7824       }
7825       account.doLogoffTasks();
7826       this.accountMan.fireLogoff(account, clientID);
7827     } else {
7828       // Do nothing if the account is unknown. Logoffs are reported for 
7829       // observe-account, observe-client, and watch-for-clients, so a 
7830       // client might receive multiple logoff notifications.
7831     }
7832   } else {
7833     throw new Error("LOGGED_OFF (u89) received for an unknown user: [" + userID + "].");
7834   }
7835 }
7836 
7837 /**
7838  * PASSWORD_CHANGED
7839  */
7840 net.user1.orbiter.CoreMessageListener.prototype.u90 = function () {
7841   var self = this.orbiter.self();
7842   var selfAccount = self.getAccount();
7843   if (selfAccount != null) {
7844     selfAccount.fireChangePassword();
7845   }
7846   this.accountMan.fireChangePassword(selfAccount ? selfAccount.getUserID() : null);
7847 };
7848 
7849 /**
7850  * CLIENTLIST_SNAPSHOT
7851  */
7852 net.user1.orbiter.CoreMessageListener.prototype.u101 = function (requestID, serializedIDs) {
7853   var ids = serializedIDs.split(net.user1.orbiter.Tokens.RS);
7854   var clientList;
7855   var thisUserID;
7856   
7857   if (requestID == "") {
7858     // Synchronize
7859     this.clientMan.deserializeWatchedClients(serializedIDs);
7860   } else {
7861     // Snapshot
7862     clientList = [];
7863     for (var i = ids.length-1; i >= 0; i-=2) {
7864       thisUserID = ids[i];
7865       thisUserID = thisUserID == "" ? null : thisUserID;
7866       clientList.push({clientID:ids[i-1], userID:thisUserID});
7867     }
7868     this.snapshotMan.receiveClientListSnapshot(requestID, clientList);
7869   }      
7870 };    
7871 
7872 /**
7873  * CLIENT_ADDED_TO_SERVER
7874  */
7875 net.user1.orbiter.CoreMessageListener.prototype.u102 = function (clientID) {
7876   this.clientMan.addWatchedClient(this.clientMan.requestClient(clientID));
7877 };
7878 
7879 /**
7880  * CLIENT_REMOVED_FROM_SERVER
7881  */
7882 net.user1.orbiter.CoreMessageListener.prototype.u103 = function (clientID) {
7883   var client = this.clientMan.getInternalClient(clientID);
7884   
7885   if (this.clientMan.hasWatchedClient(clientID)) {
7886     this.clientMan.removeWatchedClient(clientID);
7887   }
7888   if (this.clientMan.isObservingClient(clientID)) {
7889     this.clientMan.removeObservedClient(clientID);
7890   }
7891   
7892   // If the current client is both observing a client and watching for clients,
7893   // it will receive two u103 notifications. When the second one arrives, the
7894   // client will be unknown, so no disconnection event should be dispatched.
7895   if (client != null) {
7896     client.setConnectionState(net.user1.orbiter.ConnectionState.NOT_CONNECTED);
7897     // Retrieve the client reference using getClient() here so that the
7898     // ClientManagerEvent.CLIENT_DISCONNECTED event provides the application
7899     // with access to the custom client, if available.
7900     this.clientMan.fireClientDisconnected(this.clientMan.getClient(clientID));
7901   }
7902 };
7903 
7904 /**
7905  * CLIENT_SNAPSHOT
7906  */
7907 net.user1.orbiter.CoreMessageListener.prototype.u104 = function (requestID,
7908                                                                  clientID,
7909                                                                  userID,
7910                                                                  serializedOccupiedRoomIDs,
7911                                                                  serializedObservedRoomIDs,
7912                                                                  globalAttrs) {
7913   var roomAttrs = Array.prototype.slice.call(arguments).slice(7);
7914   var theClient;
7915   var account = this.accountMan.requestAccount(userID);
7916   var clientManifest = new net.user1.orbiter.ClientManifest();
7917   clientManifest.deserialize(clientID, userID, serializedOccupiedRoomIDs, 
7918                              serializedObservedRoomIDs, globalAttrs, roomAttrs);
7919   var scopes; // Used with UserAccount only
7920   
7921   if (clientID != "") {  
7922     // --- Client update ---
7923     
7924     if (requestID == "") {
7925       // Synchronize
7926       theClient = this.clientMan.requestClient(clientID);
7927       theClient.setAccount(account);
7928       theClient.synchronize(clientManifest);
7929       theClient.fireSynchronize();
7930     } else {
7931       // Snapshot
7932       this.snapshotMan.receiveClientSnapshot(requestID, clientManifest);
7933     }
7934   } else {  
7935     // --- User account update ---
7936 
7937     if (requestID == "") {
7938       // Synchronize
7939       scopes = clientManifest.persistentAttributes.getScopes();
7940       for (var i = scopes.length; --i >= 0;) {
7941         account.getAttributeManager().getAttributeCollection().synchronizeScope(scopes[i], clientManifest.persistentAttributes);
7942       }
7943       account.fireSynchronize();
7944     } else {
7945       // Snapshot
7946       this.snapshotMan.receiveAccountSnapshot(requestID, clientManifest);
7947     }
7948   }
7949 };
7950 
7951 /**
7952  * OBSERVE_CLIENT_RESULT
7953  */
7954 net.user1.orbiter.CoreMessageListener.prototype.u105 = function (clientID, status) {
7955   var theClient = this.clientMan.getInternalClient(clientID);
7956   switch (status) { 
7957     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7958     case net.user1.orbiter.Status.SUCCESS:
7959     case net.user1.orbiter.Status.ERROR:
7960     case net.user1.orbiter.Status.ALREADY_OBSERVING:
7961     case net.user1.orbiter.Status.PERMISSION_DENIED:
7962       this.clientMan.fireObserveClientResult(clientID, status);
7963       if (theClient != null) {
7964         theClient.fireObserveResult(status);
7965       }
7966       break;
7967     
7968     default:
7969       this.log.warn("Unrecognized status code for u105."
7970                + " Client ID: [" + clientID + "], status: [" + status + "].");
7971   }
7972 };    
7973 
7974 /**
7975  * STOP_OBSERVING_CLIENT_RESULT
7976  */
7977 net.user1.orbiter.CoreMessageListener.prototype.u106 = function (clientID, status) {
7978   var theClient = this.clientMan.getInternalClient(clientID);
7979   switch (status) { 
7980     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
7981     case net.user1.orbiter.Status.SUCCESS:
7982     case net.user1.orbiter.Status.ERROR:
7983     case net.user1.orbiter.Status.NOT_OBSERVING:
7984       this.clientMan.fireStopObservingClientResult(clientID, status);
7985       if (theClient != null) {
7986         theClient.fireStopObservingResult(status);
7987       }
7988       break;
7989     
7990     default:
7991       this.log.warn("Unrecognized status code for u106."
7992                + " Client ID: [" + clientID + "], status: [" + status + "].");
7993   }
7994 };
7995 
7996 /**
7997  * WATCH_FOR_CLIENTS_RESULT
7998  */
7999 net.user1.orbiter.CoreMessageListener.prototype.u107 = function (status) {
8000   switch (status) { 
8001     case net.user1.orbiter.Status.SUCCESS:
8002     case net.user1.orbiter.Status.ERROR:
8003     case net.user1.orbiter.Status.ALREADY_WATCHING:
8004       this.clientMan.fireWatchForClientsResult(status);
8005       break;
8006     
8007     default:
8008       this.log.warn("Unrecognized status code for u107."
8009                     + "Status: [" + status + "].");
8010   }
8011 };
8012 
8013 /**
8014  * STOP_WATCHING_FOR_CLIENTS_RESULT
8015  */
8016 net.user1.orbiter.CoreMessageListener.prototype.u108 = function (status) {
8017   switch (status) { 
8018     case net.user1.orbiter.Status.SUCCESS:
8019       this.clientMan.setIsWatchingForClients(false);
8020       this.clientMan.removeAllWatchedClients();
8021     case net.user1.orbiter.Status.ERROR:
8022     case net.user1.orbiter.Status.NOT_WATCHING:
8023       this.clientMan.fireStopWatchingForClientsResult(status);
8024       break;
8025     
8026     default:
8027       this.log.warn("Unrecognized status code for u108."
8028                + "Status: [" + status + "].");
8029   }
8030 };    
8031 
8032 /**
8033  * WATCH_FOR_USERS_RESULT
8034  */
8035 net.user1.orbiter.CoreMessageListener.prototype.u109 = function (status) {
8036   switch (status) { 
8037     case net.user1.orbiter.Status.SUCCESS:
8038       this.accountMan.setIsWatchingForAccounts(true);
8039     case net.user1.orbiter.Status.ERROR:
8040     case net.user1.orbiter.Status.ALREADY_WATCHING:
8041       this.accountMan.fireWatchForAccountsResult(status);
8042       break;
8043     
8044     default:
8045       this.log.warn("Unrecognized status code for u109."
8046                + "Status: [" + status + "].");
8047   }
8048 };  
8049 
8050 /**
8051  * STOP_WATCHING_FOR_USERS_RESULT
8052  */
8053 net.user1.orbiter.CoreMessageListener.prototype.u110 = function (status) {
8054   switch (status) { 
8055     case net.user1.orbiter.Status.SUCCESS:
8056       this.accountMan.setIsWatchingForAccounts(false);
8057       this.accountMan.removeAllWatchedAccounts();
8058     case net.user1.orbiter.Status.ERROR:
8059     case net.user1.orbiter.Status.NOT_WATCHING:
8060       this.accountMan.fireStopWatchingForAccountsResult(status);
8061       break;
8062     
8063     default:
8064       this.log.warn("Unrecognized status code for u110."
8065                + "Status: [" + status + "].");
8066   }
8067 };    
8068 
8069 /**
8070  * USER_ADDED
8071  */
8072 net.user1.orbiter.CoreMessageListener.prototype.u111 = function (userID) {
8073   this.accountMan.addWatchedAccount(this.accountMan.requestAccount(userID));
8074 };    
8075 
8076 /**
8077  * USER_REMOVED
8078  */
8079 net.user1.orbiter.CoreMessageListener.prototype.u112 = function (userID) {
8080   var account;
8081   if (this.accountMan.hasWatchedAccount(userID)) {
8082     account = this.accountMan.removeWatchedAccount(userID);
8083   }
8084   if (this.accountMan.isObservingAccount(userID)) {
8085     account = this.accountMan.removeObservedAccount(userID);
8086   }
8087   this.accountMan.fireAccountRemoved(userID, account);
8088 };    
8089 
8090 /**
8091  * JOINED_ROOM_ADDED_TO_CLIENT
8092  */
8093 net.user1.orbiter.CoreMessageListener.prototype.u113 = function (clientID, roomID) {
8094   var client = this.clientMan.requestClient(clientID);
8095   client.addOccupiedRoomID(roomID);
8096   client.fireJoinRoom(this.roomMan.getRoom(roomID), roomID);
8097 }    
8098 
8099 /**
8100  * JOINED_ROOM_REMOVED_FROM_CLIENT
8101  */
8102 net.user1.orbiter.CoreMessageListener.prototype.u114 = function (clientID, roomID) {
8103   var client = this.clientMan.requestClient(clientID);
8104   client.removeOccupiedRoomID(roomID);
8105   client.fireLeaveRoom(this.roomMan.getRoom(roomID), roomID);
8106 };    
8107 
8108 /**
8109  * GET_CLIENT_SNAPSHOT_RESULT
8110  */
8111 net.user1.orbiter.CoreMessageListener.prototype.u115 = function (requestID,
8112                                                                  clientID, 
8113                                                                  status) {
8114   this.snapshotMan.receiveSnapshotResult(requestID, status);
8115 };
8116 
8117 /**
8118  * GET_ACCOUNT_SNAPSHOT_RESULT
8119  */
8120 net.user1.orbiter.CoreMessageListener.prototype.u116 = function (requestID,
8121                                                                  userID, 
8122                                                                  status) {
8123   this.snapshotMan.receiveSnapshotResult(requestID, status);
8124 };
8125 
8126 /**
8127  * OBSERVED_ROOM_ADDED_TO_CLIENT
8128  */
8129 net.user1.orbiter.CoreMessageListener.prototype.u117 = function (clientID, roomID) {
8130   var client = this.clientMan.requestClient(clientID);
8131   client.addObservedRoomID(roomID);
8132   client.fireObserveRoom(this.roomMan.getRoom(roomID), roomID);
8133 };    
8134 
8135 /**
8136  * OBSERVED_ROOM_REMOVED_FROM_CLIENT
8137  */
8138 net.user1.orbiter.CoreMessageListener.prototype.u118 = function (clientID, roomID) {
8139   var client = this.clientMan.requestClient(clientID);
8140   client.removeObservedRoomID(roomID);
8141   client.fireStopObservingRoom(this.roomMan.getRoom(roomID), roomID);
8142 }    
8143 
8144 /**
8145  * CLIENT_OBSERVED
8146  */
8147 net.user1.orbiter.CoreMessageListener.prototype.u119 = function (clientID) {
8148   var client = this.clientMan.requestClient(clientID);
8149   this.clientMan.addObservedClient(client);
8150   client.fireObserve();
8151 };
8152 
8153 /**
8154  * STOPPED_OBSERVING_CLIENT
8155  */
8156 net.user1.orbiter.CoreMessageListener.prototype.u120 = function (clientID) {
8157   var client = this.clientMan.getInternalClient(clientID)
8158   this.clientMan.removeObservedClient(clientID);
8159   if (client != null) {
8160     client.fireStopObserving();
8161   }
8162 };
8163 
8164 /**
8165  * OBSERVE_ACCOUNT_RESULT
8166  */
8167 net.user1.orbiter.CoreMessageListener.prototype.u123 = function (userID, status) {
8168   var theAccount = this.accountMan.getAccount(userID);
8169   switch (status) { 
8170     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8171     case net.user1.orbiter.Status.SUCCESS:
8172     case net.user1.orbiter.Status.ERROR:
8173     case net.user1.orbiter.Status.ALREADY_OBSERVING:
8174       this.accountMan.fireObserveAccountResult(userID, status);
8175       if (theAccount) {
8176         theAccount.fireObserveResult(status);
8177       }
8178       break;
8179     
8180     default:
8181       this.log.warn("Unrecognized status code for u123."
8182                + " User ID: [" + userID + "], status: [" + status + "].");
8183   }
8184 };
8185 
8186 /**
8187  * ACCOUNT_OBSERVED
8188  */
8189 net.user1.orbiter.CoreMessageListener.prototype.u124 = function (userID) {
8190   var theAccount = this.accountMan.requestAccount(userID);
8191   this.accountMan.addObservedAccount(theAccount);
8192   theAccount.fireObserve();
8193 };
8194 
8195 /**
8196  * STOP_OBSERVING_ACCOUNT_RESULT
8197  */
8198 net.user1.orbiter.CoreMessageListener.prototype.u125 = function (userID, status) {
8199   var theAccount = this.accountMan.getAccount(userID);
8200   switch (status) { 
8201     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8202     case net.user1.orbiter.Status.SUCCESS:
8203     case net.user1.orbiter.Status.ERROR:
8204     case net.user1.orbiter.Status.ALREADY_OBSERVING:
8205       this.accountMan.fireStopObservingAccountResult(userID, status);
8206       if (theAccount) {
8207         theAccount.fireStopObservingResult(status);
8208       }
8209       break;
8210     
8211     default:
8212       this.log.warn("Unrecognized status code for u125."
8213                + " User ID: [" + userID + "], status: [" + status + "].");
8214   }
8215 };
8216 
8217 /**
8218  * STOPPED_OBSERVING_ACCOUNT
8219  */
8220 net.user1.orbiter.CoreMessageListener.prototype.u126 = function (userID) {
8221   var account = this.accountMan.getAccount(userID);
8222   this.accountMan.removeObservedAccount(userID);
8223   if (account != null) {
8224     account.fireStopObserving();
8225   }
8226 };
8227 
8228 /**
8229  * ACCOUNT_LIST_UPDATE
8230  */
8231 net.user1.orbiter.CoreMessageListener.prototype.u127 = function (requestID, serializedIDs) {
8232   var ids = serializedIDs.split(net.user1.orbiter.Tokens.RS);
8233   var accountList;
8234   
8235   if (requestID == "") {
8236     // Synchronize
8237     this.accountMan.deserializeWatchedAccounts(serializedIDs);
8238   } else {
8239     // Snapshot
8240     accountList = [];
8241     for (var i = ids.length; --i >= 0;) {
8242       accountList.push(ids[i]);
8243     }
8244     this.snapshotMan.receiveAccountListSnapshot(requestID, accountList);
8245   }  
8246 };
8247 
8248 /**
8249  * UPDATE_LEVELS_UPDATE
8250  */
8251 net.user1.orbiter.CoreMessageListener.prototype.u128 = function (updateLevels, roomID) {
8252   var room = this.roomMan.getRoom(roomID);
8253   var levels = new net.user1.orbiter.UpdateLevels();
8254   levels.fromInt(parseInt(updateLevels));
8255   if (room != null) {
8256     if (!levels.occupantList) {
8257       var occupantID;
8258       var occupantIDs = room.getOccupantIDs();
8259       var numOccupantIDs = occupantIDs.length;
8260       for (var i = 0; i < numOccupantIDs; i++) {
8261         occupantID = occupantIDs[i];
8262         room.removeOccupant(occupantID);
8263       }
8264     }
8265     if (!levels.observerList) {
8266       var observerID;
8267       var observerIDs = room.getObserverIDs();
8268       var numObserverIDs = observerIDs.length;
8269       for (i = 0; i < numObserverIDs; i++) {
8270         observerID = observerIDs[i];
8271         room.removeObserver(observerID);
8272       }
8273     }
8274     if (!levels.sharedRoomAttributes
8275         && !levels.allRoomAttributes) {
8276       room.getAttributeManager().removeAll();
8277     }
8278   }
8279 };
8280 
8281 /**
8282  * CLIENT_OBSERVED_ROOM
8283  */
8284 net.user1.orbiter.CoreMessageListener.prototype.u129 = function (roomID,
8285                                                                  clientID,
8286                                                                  userID,
8287                                                                  globalAttributes,
8288                                                                  roomAttributes) {
8289   var theClient = this.clientMan.requestClient(clientID);
8290   var account = this.accountMan.requestAccount(userID);
8291   var clientManifest;
8292   if (account != null
8293       && theClient.getAccount() != account) {
8294     theClient.setAccount(account);
8295   }
8296 
8297   // If it's not the current client, set the client's attributes. 
8298   // (The current client obtains its own attributes through separate u8s.)
8299   var theRoom = this.roomMan.getRoom(roomID);
8300   if (!theClient.isSelf()) {
8301     clientManifest = new net.user1.orbiter.ClientManifest();
8302     clientManifest.deserialize(clientID, userID, null, 
8303                                null, globalAttributes, [roomID, roomAttributes]);
8304     theClient.synchronize(clientManifest);
8305     
8306     // If the client is observed, don't fire OBSERVE_ROOM; observed clients always
8307     // fire OBSERVE_ROOM based on observation updates. Likewise, don't fire OBSERVE_ROOM
8308     // on self; self fires OBSERVE_ROOM when it receives a u59.
8309     if (!this.clientMan.isObservingClient(clientID)) {
8310       theClient.fireObserveRoom(theRoom, roomID);
8311     }
8312   }
8313 
8314   // Add the client to the room's observer list
8315   theRoom.addObserver(theClient);
8316 };
8317 
8318 /**
8319  * CLIENT_STOPPED_OBSERVING_ROOM
8320  */
8321 net.user1.orbiter.CoreMessageListener.prototype.u130 = function (roomID, 
8322                                                                  clientID) {
8323   // Remove the room from the client's list of observed rooms
8324   var theClient = this.clientMan.requestClient(clientID);
8325   var theRoom = this.roomMan.getRoom(roomID);
8326 
8327   // Remove the client from the given room
8328   theRoom.removeObserver(clientID);
8329   
8330   // Don't fire STOP_OBSERVING_ROOM on self; self fires STOP_OBSERVING_ROOM
8331   // when it receives a u62.
8332   if (!theClient.isSelf()) {
8333     // If the client is observed, don't fire STOP_OBSERVING_ROOM; observed 
8334     // clients always fire STOP_OBSERVING_ROOM based on observation updates.
8335     if (!this.clientMan.isObservingClient(clientID)) {
8336       theClient.fireStopObservingRoom(theRoom, roomID);
8337     }
8338   }
8339 };
8340 
8341 /**
8342  * ROOM_OCCUPANTCOUNT_UPDATE
8343  */
8344 net.user1.orbiter.CoreMessageListener.prototype.u131 = function (roomID, 
8345                                                                  numClients) {
8346   var levels = this.clientMan.self().getUpdateLevels(roomID);
8347   
8348   if (levels != null) {
8349     if (!levels.occupantList) {
8350       this.roomMan.getRoom(roomID).setNumOccupants(parseInt(numClients));
8351     }
8352   } else {
8353     throw new Error("[CORE_MESSAGE_LISTENER] Received a room occupant count" +
8354       " update (u131), but update levels are unknown for the room. Synchronization" +
8355       " error. Please report this error to union@user1.net.");
8356   }
8357 };
8358 
8359 /**
8360  * ROOM_OBSERVERCOUNT_UPDATE
8361  */
8362 net.user1.orbiter.CoreMessageListener.prototype.u132 = function (roomID,  
8363                                                                  numClients) {
8364   var levels = this.clientMan.self().getUpdateLevels(roomID);
8365   
8366   if (levels != null) {
8367     if (!levels.observerList) {
8368       this.roomMan.getRoom(roomID).setNumObservers(parseInt(numClients));
8369     }
8370   } else {
8371     throw new Error("[CORE_MESSAGE_LISTENER] Received a room observer count" +
8372       " update (u132), but update levels are unknown for the room. Synchronization" +
8373       " error. Please report this error to union@user1.net.");
8374   }
8375 }
8376 
8377 /**
8378  * ADD_ROLE_RESULT
8379  */
8380 net.user1.orbiter.CoreMessageListener.prototype.u134 = function (userID, role, status) {
8381   var theAccount = this.accountMan.getAccount(userID);
8382   switch (status) { 
8383     case net.user1.orbiter.Status.SUCCESS:
8384     case net.user1.orbiter.Status.ERROR:
8385     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8386     case net.user1.orbiter.Status.ROLE_NOT_FOUND:
8387     case net.user1.orbiter.Status.ALREADY_ASSIGNED:
8388     case net.user1.orbiter.Status.PERMISSION_DENIED:
8389       this.accountMan.fireAddRoleResult(userID, role, status);
8390       if (theAccount) {
8391         theAccount.fireAddRoleResult(role, status);
8392       }
8393       break;
8394     
8395     default:
8396       this.log.warn("Unrecognized status code for u134."
8397                + " User ID: [" + userID + "], role: [" + role 
8398                + "], status: [" + status + "].");
8399   }
8400 };
8401 
8402 /**
8403  * REMOVE_ROLE_RESULT
8404  */
8405 net.user1.orbiter.CoreMessageListener.prototype.u136 = function (userID, role, status) {
8406   var theAccount = this.accountMan.getAccount(userID);
8407   switch (status) { 
8408     case net.user1.orbiter.Status.SUCCESS:
8409     case net.user1.orbiter.Status.ERROR:
8410     case net.user1.orbiter.Status.ACCOUNT_NOT_FOUND:
8411     case net.user1.orbiter.Status.ROLE_NOT_FOUND:
8412     case net.user1.orbiter.Status.NOT_ASSIGNED:
8413     case net.user1.orbiter.Status.PERMISSION_DENIED:
8414       this.accountMan.fireRemoveRoleResult(userID, role, status);
8415       if (theAccount) {
8416         theAccount.fireRemoveRoleResult(role, status);
8417       }
8418       break;
8419     
8420     default:
8421       this.log.warn("Unrecognized status code for u136."
8422                + " User ID: [" + userID + "], role: [" + role 
8423                + "], status: [" + status + "].");
8424   }
8425 };
8426 
8427 /**
8428  * BAN_RESULT
8429  */
8430 net.user1.orbiter.CoreMessageListener.prototype.u138 = function (address, clientID, status) {
8431   switch (status) { 
8432     case net.user1.orbiter.Status.SUCCESS:
8433     case net.user1.orbiter.Status.ERROR:
8434     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
8435     case net.user1.orbiter.Status.ALREADY_BANNED:
8436     case net.user1.orbiter.Status.PERMISSION_DENIED:
8437       this.clientMan.fireBanClientResult(address, clientID, status);
8438       break;
8439     
8440     default:
8441       this.log.warn("Unrecognized status code for u138."
8442                + " Address: [" + address + "], clientID: [" + clientID 
8443                + "], status: [" + status + "].");
8444   }
8445 };
8446 
8447 /**
8448  * UNBAN_RESULT
8449  */
8450 net.user1.orbiter.CoreMessageListener.prototype.u140 = function (address, status) {
8451   switch (status) { 
8452     case net.user1.orbiter.Status.SUCCESS:
8453     case net.user1.orbiter.Status.ERROR:
8454     case net.user1.orbiter.Status.NOT_BANNED:
8455     case net.user1.orbiter.Status.PERMISSION_DENIED:
8456       this.clientMan.fireUnbanClientResult(address, status);
8457       break;
8458     
8459     default:
8460       this.log.warn("Unrecognized status code for u140."
8461                + " Address: [" + address + "],"
8462                + " status: [" + status + "].");
8463   }
8464 };
8465 
8466 /**
8467  * BANNED_LIST_SNAPSHOT
8468  */
8469 net.user1.orbiter.CoreMessageListener.prototype.u142 = function (requestID, bannedListSource) {
8470   var bannedList = bannedListSource == "" ? [] : bannedListSource.split(net.user1.orbiter.Tokens.RS);
8471   
8472   if (requestID == "") {
8473     this.clientMan.setWatchedBannedAddresses(bannedList);
8474   } else {
8475     // Snapshot
8476     this.snapshotMan.receiveBannedListSnapshot(requestID, bannedList);
8477   }
8478 };
8479 
8480 /**
8481  * WATCH_FOR_BANNED_ADDRESSES_RESULT
8482  */
8483 net.user1.orbiter.CoreMessageListener.prototype.u144 = function (status) {
8484   switch (status) { 
8485     case net.user1.orbiter.Status.SUCCESS:
8486     case net.user1.orbiter.Status.ERROR:
8487     case net.user1.orbiter.Status.ALREADY_WATCHING:
8488     case net.user1.orbiter.Status.PERMISSION_DENIED:
8489       this.clientMan.fireWatchForBannedAddressesResult(status);
8490       break;
8491     
8492     default:
8493       this.log.warn("Unrecognized status code for u144:"
8494                + " [" + status + "].");
8495   }
8496 };
8497 
8498 /**
8499  * STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT
8500  */
8501 net.user1.orbiter.CoreMessageListener.prototype.u146 = function (status) {
8502   switch (status) { 
8503     case net.user1.orbiter.Status.SUCCESS:
8504     case net.user1.orbiter.Status.ERROR:
8505     case net.user1.orbiter.Status.NOT_WATCHING:
8506       this.clientMan.fireStopWatchingForBannedAddressesResult(status);
8507       break;
8508     
8509     default:
8510       this.log.warn("Unrecognized status code for u146:"
8511                + " [" + status + "].");
8512   }
8513 };
8514 
8515 /**
8516  * BANNED_ADDRESS_ADDED
8517  */
8518 net.user1.orbiter.CoreMessageListener.prototype.u147 = function (address) {
8519   this.clientMan.addWatchedBannedAddress(address);
8520 };
8521 
8522 /**
8523  * BANNED_ADDRESS_REMOVED
8524  */
8525 net.user1.orbiter.CoreMessageListener.prototype.u148 = function (address) {
8526   this.clientMan.removeWatchedBannedAddress(address);
8527 };
8528 
8529 /**
8530  * KICK_RESULT
8531  */
8532 net.user1.orbiter.CoreMessageListener.prototype.u150 = function (clientID, status) {
8533   switch (status) { 
8534     case net.user1.orbiter.Status.SUCCESS:
8535     case net.user1.orbiter.Status.ERROR:
8536     case net.user1.orbiter.Status.CLIENT_NOT_FOUND:
8537     case net.user1.orbiter.Status.PERMISSION_DENIED:
8538       this.clientMan.fireKickClientResult(clientID, status);
8539       break;
8540     
8541     default:
8542       this.log.warn("Unrecognized status code for u150:"
8543                + " [" + status + "].");
8544   }
8545 };
8546 
8547 /**
8548  * SERVERMODULELIST_SNAPSHOT
8549  */
8550 net.user1.orbiter.CoreMessageListener.prototype.u152 = function (requestID, serverModuleListSource) {
8551   var moduleListArray = serverModuleListSource == "" ? [] : serverModuleListSource.split(net.user1.orbiter.Tokens.RS);
8552   var moduleList = [];
8553   for (var i = 0; i < moduleListArray.length; i+= 3) {
8554     moduleList.push(new ModuleDefinition(moduleListArray[i],
8555                                          moduleListArray[i+1],
8556                                          moduleListArray[i+2]));
8557   }
8558   
8559   if (requestID == "") {
8560     this.log.warn("Incoming SERVERMODULELIST_SNAPSHOT UPC missing required requestID. Ignoring message.");
8561   } else {
8562     // Snapshot
8563     this.snapshotMan.receiveServerModuleListSnapshot(requestID, moduleList);
8564   }
8565 };
8566 
8567 /**
8568  * GET_UPC_STATS_SNAPSHOT_RESULT
8569  */
8570 net.user1.orbiter.CoreMessageListener.prototype.u155 = function (requestID,
8571                                                                  status) {
8572   this.snapshotMan.receiveSnapshotResult(requestID, status);
8573 };
8574 
8575 /**
8576  * UPC_STATS_SNAPSHOT
8577  */
8578 net.user1.orbiter.CoreMessageListener.prototype.u156 = function (requestID,
8579                                                                  totalUPCsProcessed,
8580                                                                  numUPCsInQueue,
8581                                                                  lastQueueWaitTime) {
8582   var longestUPCProcesses = Array.prototype.slice.call(arguments).slice(5);
8583   var upcProcessingRecord;
8584   for (var i = 0; i < longestUPCProcesses.length; i++) {
8585     upcProcessingRecord = new net.user1.orbiter.UPCProcessingRecord();
8586     upcProcessingRecord.deserialize(longestUPCProcesses[i]);
8587     longestUPCProcesses[i] = upcProcessingRecord;
8588   }
8589   
8590   this.snapshotMan.receiveUPCStatsSnapshot(requestID, 
8591                                            parseFloat(totalUPCsProcessed),
8592                                            parseFloat(numUPCsInQueue),
8593                                            parseFloat(lastQueueWaitTime),
8594                                            longestUPCProcesses);
8595 };
8596 
8597 /**
8598  * RESET_UPC_STATS_RESULT
8599  */
8600 net.user1.orbiter.CoreMessageListener.prototype.u158 = function (status) {
8601   switch (status) { 
8602     case net.user1.orbiter.Status.SUCCESS:
8603     case net.user1.orbiter.Status.ERROR:
8604     case net.user1.orbiter.Status.PERMISSION_DENIED:
8605      this.orbiter.getServer().dispatchResetUPCStatsResult(status);
8606       break;
8607     
8608     default:
8609       this.log.warn("Unrecognized status code for u158."
8610                + "Status: [" + status + "].");
8611   }
8612 };
8613 
8614 /**
8615  * WATCH_FOR_PROCESSED_UPCS_RESULT
8616  */
8617 net.user1.orbiter.CoreMessageListener.prototype.u160 = function (status) {
8618   switch (status) { 
8619     case net.user1.orbiter.Status.SUCCESS:
8620       this.orbiter.getServer().setIsWatchingForProcessedUPCs(true);
8621     case net.user1.orbiter.Status.ERROR:
8622     case net.user1.orbiter.Status.ALREADY_WATCHING:
8623     case net.user1.orbiter.Status.PERMISSION_DENIED:
8624       this.orbiter.getServer().dispatchWatchForProcessedUPCsResult(status);
8625       break;
8626     
8627     default:
8628       this.log.warn("Unrecognized status code for u160."
8629                + "Status: [" + status + "].");
8630   }
8631 };
8632 
8633 /**
8634  * PROCESSED_UPC_ADDED
8635  */
8636 net.user1.orbiter.CoreMessageListener.prototype.u161 = function (fromClientID,
8637                                                                  fromUserID,
8638                                                                  fromClientAddress,
8639                                                                  queuedAt,
8640                                                                  processingStartedAt,
8641                                                                  processingFinishedAt,
8642                                                                  source) {
8643   var upcProcessingRecord = new net.user1.orbiter.UPCProcessingRecord();
8644   upcProcessingRecord.deserializeParts(fromClientID,
8645                                        fromUserID,
8646                                        fromClientAddress,
8647                                        queuedAt,
8648                                        processingStartedAt,
8649                                        processingFinishedAt,
8650                                        source);
8651   this.orbiter.getServer().dispatchUPCProcessed(upcProcessingRecord);
8652 };
8653 
8654 /**
8655  * STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT
8656  */
8657 net.user1.orbiter.CoreMessageListener.prototype.u163 = function (status) {
8658   switch (status) { 
8659     case net.user1.orbiter.Status.SUCCESS:
8660       this.orbiter.getServer().setIsWatchingForProcessedUPCs(false);
8661     case net.user1.orbiter.Status.ERROR:
8662     case net.user1.orbiter.Status.NOT_WATCHING:
8663     case net.user1.orbiter.Status.PERMISSION_DENIED:
8664       this.orbiter.getServer().dispatchStopWatchingForProcessedUPCsResult(status);
8665       break;
8666     
8667     default:
8668       this.log.warn("Unrecognized status code for u163."
8669                + "Status: [" + status + "].");
8670   }
8671 };
8672 
8673     
8674 /**
8675  * NODELIST_SNAPSHOT
8676  */
8677 net.user1.orbiter.CoreMessageListener.prototype.u166 = function (requestID, nodeListSource) {
8678   var nodeIDs = nodeListSource == "" ? [] : nodeListSource.split(net.user1.orbiter.Tokens.RS);
8679   
8680   if (requestID == "") {
8681     this.log.warn("Incoming NODELIST_SNAPSHOT UPC missing required requestID. Ignoring message.");
8682   } else {
8683     // Snapshot
8684     this.snapshotMan.receiveNodeListSnapshot(requestID, nodeIDs);
8685   }
8686 };
8687 
8688 /**
8689  * GATEWAYS_SNAPSHOT
8690  */
8691 net.user1.orbiter.CoreMessageListener.prototype.u168 = function (requestID) {
8692   var gatewayListSource = Array.prototype.slice.call(arguments).slice(1);
8693   var gateways = [];
8694 
8695   var gateway;
8696   var gatewayBandwidth;
8697   var gatewayBandwidthSource;
8698   var gatewayIntervalSource;
8699   for (var i = 0; i < gatewayListSource.length; i+=8) {
8700     gateway = new net.user1.orbiter.Gateway();
8701     gateway.id = gatewayListSource[i];
8702     gateway.type = gatewayListSource[i+1];
8703 
8704     gateway.lifetimeConnectionsByCategory = gatewayListSource[i+2] === "" ? {} : this.createHashFromArg(gatewayListSource[i+2]);
8705     for (var p in gateway.lifetimeConnectionsByCategory) {
8706       gateway.lifetimeConnectionsByCategory[p] = parseFloat(gateway.lifetimeConnectionsByCategory[p]);
8707     }
8708     gateway.lifetimeClientsByType = gatewayListSource[i+3] === "" ? {} : this.createHashFromArg(gatewayListSource[i+3]);
8709     for (p in gateway.lifetimeClientsByType) {
8710       gateway.lifetimeClientsByType[p] = parseFloat(gateway.lifetimeClientsByType[p]);
8711     }
8712     gateway.lifetimeClientsByUPCVersion = gatewayListSource[i+4] === "" ? {} : this.createHashFromArg(gatewayListSource[i+4]);
8713     for (p in gateway.lifetimeClientsByUPCVersion) {
8714       gateway.lifetimeClientsByUPCVersion[p] = parseFloat(gateway.lifetimeClientsByUPCVersion[p]);
8715     }
8716     gateway.attributes = gatewayListSource[i+5] === "" ? {} : this.createHashFromArg(gatewayListSource[i+5]);
8717 
8718     gatewayIntervalSource = gatewayListSource[i+6].split(net.user1.orbiter.Tokens.RS);
8719     gateway.connectionsPerSecond = parseFloat(gatewayIntervalSource[0]);
8720     gateway.maxConnectionsPerSecond = parseFloat(gatewayIntervalSource[1]);
8721     gateway.clientsPerSecond = parseFloat(gatewayIntervalSource[2]);
8722     gateway.maxClientsPerSecond = parseFloat(gatewayIntervalSource[3]);
8723 
8724     gatewayBandwidth = new net.user1.orbiter.GatewayBandwidth();
8725     gatewayBandwidthSource = gatewayListSource[i+7].split(net.user1.orbiter.Tokens.RS);
8726     gatewayBandwidth.lifetimeRead = gatewayBandwidthSource[0] === "" ? 0 : parseFloat(gatewayBandwidthSource[0]);
8727     gatewayBandwidth.lifetimeWritten = gatewayBandwidthSource[1] === "" ? 0 : parseFloat(gatewayBandwidthSource[1]);
8728     gatewayBandwidth.averageRead = gatewayBandwidthSource[2] === "" ? 0 : parseFloat(gatewayBandwidthSource[2]);
8729     gatewayBandwidth.averageWritten = gatewayBandwidthSource[3] === "" ? 0 : parseFloat(gatewayBandwidthSource[3]);
8730     gatewayBandwidth.intervalRead = gatewayBandwidthSource[4] === "" ? 0 : parseFloat(gatewayBandwidthSource[4]);
8731     gatewayBandwidth.intervalWritten = gatewayBandwidthSource[5] === "" ? 0 : parseFloat(gatewayBandwidthSource[5]);
8732     gatewayBandwidth.maxIntervalRead = gatewayBandwidthSource[6] === "" ? 0 : parseFloat(gatewayBandwidthSource[6]);
8733     gatewayBandwidth.maxIntervalWritten = gatewayBandwidthSource[7] === "" ? 0 : parseFloat(gatewayBandwidthSource[7]);
8734     gatewayBandwidth.scheduledWrite = gatewayBandwidthSource[8] === "" ? 0 : parseFloat(gatewayBandwidthSource[8]);
8735     gateway.bandwidth = gatewayBandwidth;
8736     gateways.push(gateway);
8737   }
8738 
8739   if (requestID == "") {
8740     this.log.warn("Incoming GATEWAYS_SNAPSHOT UPC missing required requestID. Ignoring message.");
8741   } else {
8742     // Snapshot
8743     this.snapshotMan.receiveGatewaysSnapshot(requestID, gateways);
8744   }
8745 };
8746 //==============================================================================
8747 // CLASS DECLARATION
8748 //==============================================================================
8749 /**
8750  * @class
8751  */
8752 net.user1.orbiter.CustomClient = function () {
8753   this.client = null;
8754 };
8755 
8756 /**
8757  * An initialization method invoked when this CustomClient object is ready
8758  * for use. Subclasses wishing to perform initialization tasks that require
8759  * this CustomClient's composed Client object should override this method. 
8760  * 
8761  * @since Orbiter 1.0.0
8762  */
8763 net.user1.orbiter.CustomClient.prototype.init = function () {
8764 };
8765 
8766 net.user1.orbiter.CustomClient.prototype.addEventListener = function (type, 
8767                                                            listener,
8768                                                            thisArg,
8769                                                            priority) {
8770   this.client.addEventListener(type, listener, thisArg, priority);
8771 };
8772 
8773 net.user1.orbiter.CustomClient.prototype.dispatchEvent = function (event) {
8774   return this.client.dispatchEvent(event);
8775 };
8776 
8777 net.user1.orbiter.CustomClient.prototype.hasEventListener = function (type) {
8778   return this.client.hasEventListener(type);
8779 };
8780 
8781 net.user1.orbiter.CustomClient.prototype.removeEventListener = function (type, 
8782                                                                          listener,
8783                                                                          thisObj) {
8784   this.client.removeEventListener(type, listener, thisObj);
8785 };
8786 
8787 net.user1.orbiter.CustomClient.prototype.willTrigger = function (type) {
8788   return this.client.willTrigger(type);
8789 };
8790 
8791 net.user1.orbiter.CustomClient.prototype.setClient = function (client) {
8792   this.client = client;
8793 };
8794 
8795 net.user1.orbiter.CustomClient.prototype.getClientID = function () {
8796   return this.client.getClientID();
8797 };
8798 
8799 net.user1.orbiter.CustomClient.prototype.getConnectionState = function () {
8800   return this.client.getConnectionState();
8801 };
8802 
8803 net.user1.orbiter.CustomClient.prototype.isSelf = function () {
8804   return this.client.isSelf();
8805 };
8806 
8807 net.user1.orbiter.CustomClient.prototype.setClientClass = function (scope, 
8808                                clientClass) {
8809   var fallbackClasses = Array.prototype.slice.call(arguments).slice(2);
8810   this.client.setClientClass.apply(this.client, [scope, clientClass].concat(fallbackClasses));
8811 };
8812 
8813 net.user1.orbiter.CustomClient.prototype.isInRoom = function (roomID) {
8814   return this.client.isInRoom(roomID);
8815 };
8816 
8817 net.user1.orbiter.CustomClient.prototype.isObservingRoom = function (roomID) {
8818   return this.client.isObservingRoom(roomID);
8819 };
8820 
8821 net.user1.orbiter.CustomClient.prototype.getOccupiedRoomIDs = function () {
8822   return this.client.getOccupiedRoomIDs();
8823 };
8824 
8825 net.user1.orbiter.CustomClient.prototype.getObservedRoomIDs = function () {
8826   return this.client.getObservedRoomIDs();
8827 };
8828 
8829 net.user1.orbiter.CustomClient.prototype.getIP = function () {
8830   return this.client.getIP();
8831 };
8832 
8833 net.user1.orbiter.CustomClient.prototype.getConnectTime = function () {
8834   return this.client.getConnectTime();
8835 };
8836 
8837 net.user1.orbiter.CustomClient.prototype.getPing = function () {
8838   return this.client.getPing();
8839 };
8840 
8841 net.user1.orbiter.CustomClient.prototype.getTimeOnline = function () {
8842   return this.client.getTimeOnline();
8843 };
8844 
8845 net.user1.orbiter.CustomClient.prototype.sendMessage = function (messageName) {
8846   var args = Array.prototype.slice.call(arguments).slice(0);
8847   this.client.sendMessage.apply(this.client, args);
8848 };
8849 
8850 net.user1.orbiter.CustomClient.prototype.setAttribute = function (attrName, 
8851                                                                   attrValue, 
8852                                                                   attrScope, 
8853                                                                   isShared, 
8854                                                                   evaluate) {
8855   this.client.setAttribute(attrName, attrValue, attrScope, isShared, evaluate);
8856 };
8857 
8858 net.user1.orbiter.CustomClient.prototype.deleteAttribute = function (attrName, attrScope) {
8859   this.client.deleteAttribute(attrName, attrScope);
8860 };
8861 
8862 net.user1.orbiter.CustomClient.prototype.getAttribute = function (attrName, attrScope) {
8863   return this.client.getAttribute(attrName, attrScope);
8864 };
8865 
8866 net.user1.orbiter.CustomClient.prototype.getAttributes = function () {
8867   return this.client.getAttributes();
8868 };
8869 
8870 net.user1.orbiter.CustomClient.prototype.getAttributesByScope = function (scope) {
8871   return this.client.getAttributesByScope();
8872 };
8873 
8874 net.user1.orbiter.CustomClient.prototype.getClientManager = function () {
8875   return this.client.getClientManager();
8876 };
8877 
8878 net.user1.orbiter.CustomClient.prototype.getAccount = function () {
8879   return this.client.getAccount();
8880 };
8881 
8882 net.user1.orbiter.CustomClient.prototype.kick = function () {
8883   this.client.kick();
8884 };
8885 
8886 net.user1.orbiter.CustomClient.prototype.ban = function (duration, reason) {
8887   this.client.ban(duration, reason);
8888 };
8889 
8890 net.user1.orbiter.CustomClient.prototype.observe = function () {
8891   this.client.observe();
8892 };
8893 
8894 net.user1.orbiter.CustomClient.prototype.stopObserving = function () {
8895   this.client.stopObserving();
8896 };
8897 
8898 net.user1.orbiter.CustomClient.prototype.isAdmin = function () {
8899   return this.client.isAdmin();
8900 };
8901 
8902 net.user1.orbiter.CustomClient.prototype.toString = function () {
8903   return "[object CustomClient, ID: " + this.getClientID() + "]";
8904 };
8905 //==============================================================================
8906 // EVENT UTILITIES
8907 //==============================================================================
8908 /** @class */
8909 net.user1.utils.EventUtil = new Object();
8910 
8911 net.user1.utils.EventUtil.migrateListeners = function (oldObject, 
8912                                                        newObject,
8913                                                        events,
8914                                                        thisObj) {
8915   var len = events.length
8916   for (var i = 0; i < len; i += 2) {
8917     if (oldObject != null) {
8918       oldObject.removeEventListener(events[i], events[i+1], thisObj);
8919     }
8920     if (newObject != null) {
8921       newObject.addEventListener(events[i], events[i+1], thisObj);
8922     }
8923   }
8924 };
8925 //==============================================================================
8926 // CLASS DECLARATION
8927 //==============================================================================
8928 /** 
8929  * @class
8930  */
8931 net.user1.orbiter.filters.FilterSet = function () {
8932   this.filters = new Array();
8933 };
8934     
8935 net.user1.orbiter.filters.FilterSet.prototype.addFilter = function (filter) {
8936   this.filters.push(filter);
8937 };
8938     
8939 net.user1.orbiter.filters.FilterSet.prototype.getFilters = function () {
8940   return this.filters.slice(0);
8941 };
8942     
8943 net.user1.orbiter.filters.FilterSet.prototype.toXMLString = function () {
8944   var s = "<filters>\n";
8945   
8946   var filter;
8947   for (var i = 0; i < this.filters.length; i++) {
8948     filter = this.filters[i];
8949     s += filter.toXMLString() + "\n";
8950   }
8951   s += "</filters>";
8952   return s;
8953 };
8954 //==============================================================================
8955 // CLASS DECLARATION
8956 //==============================================================================
8957 /** 
8958  * @class
8959  */
8960 net.user1.orbiter.GatewayBandwidth = function () {
8961   /**
8962    * @field
8963    * @type Number
8964    */
8965   this.lifetimeRead = 0;
8966   /**
8967    * @field
8968    * @type Number
8969    */
8970   this.lifetimeWritten = 0;
8971   /**
8972    * @field
8973    * @type Number
8974    */
8975   this.averageRead = 0;
8976   /**
8977    * @field
8978    * @type Number
8979    */
8980   this.averageWritten = 0;
8981   /**
8982    * @field
8983    * @type Number
8984    */
8985   this.intervalRead = 0;
8986   /**
8987    * @field
8988    * @type Number
8989    */
8990   this.intervalWritten = 0;
8991   /**
8992    * @field
8993    * @type Number
8994    */
8995   this.maxIntervalRead = 0;
8996   /**
8997    * @field
8998    * @type Number
8999    */
9000   this.maxIntervalWritten = 0;
9001   /**
9002    * @field
9003    * @type Number
9004    */
9005   this.scheduledWrite = 0;
9006 };
9007 //==============================================================================
9008 // CLASS DECLARATION
9009 //==============================================================================
9010 /**
9011  * @class
9012  */
9013 net.user1.orbiter.Gateway = function () {
9014   /**
9015    * @field
9016    * @type String
9017   this.id = null;
9018   /**
9019    * @field
9020    * @type String
9021    */
9022   this.type = null;
9023   /**
9024    * @field
9025    * @type Object
9026    */
9027   this.lifetimeConnectionsByCategory = null;
9028   /**
9029    * @field
9030    * @type Object
9031    */
9032   this.lifetimeClientsByType = null;
9033   /**
9034    * @field
9035    * @type Object
9036    */
9037   this.lifetimeClientsByUPCVersion = null;
9038   /**
9039    * @field
9040    * @type Object
9041    */
9042   this.attributes = null;
9043   /**
9044    * @field
9045    * @type Number
9046    */
9047   this.connectionsPerSecond = 0;
9048   /**
9049    * @field
9050    * @type Number
9051    */
9052   this.maxConnectionsPerSecond = 0;
9053   /**
9054    * @field
9055    * @type Number
9056    */
9057   this.clientsPerSecond = 0;
9058   /**
9059    * @field
9060    * @type Number
9061    */
9062   this.maxClientsPerSecond = 0;
9063   /**
9064    * @field
9065    * @type net.user1.orbiter.GatewayBandwidth
9066    */
9067   this.bandwidth = null;
9068 };
9069 //==============================================================================
9070 // CLASS DECLARATION
9071 //==============================================================================
9072 /** 
9073  * @class
9074  */
9075 net.user1.utils.LRUCache = function (maxLength) {
9076   this.maxLength = maxLength;
9077   this.length = 0;
9078   this.hash = new net.user1.utils.UDictionary();
9079   this.first = null;
9080   this.last = null;
9081 };
9082     
9083 net.user1.utils.LRUCache.prototype.get = function (key) {
9084   var node = this.hash[key];
9085   
9086   if (node != null) {
9087     this.moveToHead(node);
9088     return node.value;
9089   } else {
9090     return null;
9091   }
9092 };
9093 
9094 net.user1.utils.LRUCache.prototype.put = function (key, value) {
9095   var node = this.hash[key];
9096   if (node == null) {
9097     if (this.length >= this.maxLength) {
9098       this.removeLast();
9099     } else {
9100       this.length++;
9101     }
9102     node = new net.user1.utils.CacheNode();
9103   }
9104   
9105   node.value = value;
9106   node.key = key;
9107   this.moveToHead(node);
9108   this.hash[key] = node;
9109 };
9110 
9111 net.user1.utils.LRUCache.prototype.remove = function (key) {
9112   var node = this.hash[key];
9113   if (node != null) {
9114     if (node.prev != null) {
9115       node.prev.next = node.next;
9116     }
9117     if (node.next != null) {
9118       node.next.prev = node.prev;
9119     }
9120     if (this.last == node) {
9121       this.last = node.prev;
9122     }
9123     if (this.first == node) {
9124       this.first = node.next;
9125     }
9126   }
9127   return node;
9128 }
9129 
9130 net.user1.utils.LRUCache.prototype.clear = function () {
9131   this.first = null;
9132   this.last = null;
9133   this.length = 0;
9134   this.hash = new net.user1.utils.UDictionary();
9135 };
9136 
9137 /**
9138  * @private
9139  */
9140 net.user1.utils.LRUCache.prototype.removeLast = function () {
9141   if (this.last != null) {
9142     delete this.hash[this.last.key];
9143     if (this.last.prev != null) {
9144       this.last.prev.next = null;
9145     } else {
9146       this.first = null;
9147     }
9148     this.last = this.last.prev;
9149   }
9150 };
9151 
9152 /**
9153  * @private
9154  */
9155 net.user1.utils.LRUCache.prototype.moveToHead = function (node) {
9156   if (node == this.first) {
9157     return;
9158   }
9159   if (node.prev != null) {
9160     node.prev.next = node.next;
9161   }
9162   if (node.next != null) {
9163     node.next.prev = node.prev;
9164   }
9165   if (this.last == node) {
9166     this.last = node.prev;
9167   }
9168   if (this.first != null) {
9169     node.next = this.first;
9170     this.first.prev = node;
9171   }
9172   this.first = node;
9173   node.prev = null;
9174   if (this.last == null) {
9175     this.last = this.first;
9176   }
9177 };
9178 //==============================================================================
9179 // CLASS DECLARATION
9180 //==============================================================================
9181 /**
9182  * @class
9183  */
9184 net.user1.orbiter.ModuleDefinition = function (id, type, source) {
9185   this.id = id;
9186   this.type = type;
9187   this.source = source;
9188 };
9189 //==============================================================================
9190 // MODULE TYPE CONSTANTS
9191 //==============================================================================
9192 /** @class */
9193 net.user1.orbiter.ModuleType = new Object();
9194 /** @constant */
9195 net.user1.orbiter.ModuleType.CLASS = "class";
9196 /** @constant */
9197 net.user1.orbiter.ModuleType.SCRIPT = "script";
9198 //==============================================================================
9199 // CLASS DECLARATION
9200 //==============================================================================
9201 /** 
9202  * @class
9203  * @extends net.user1.orbiter.snapshot.Snapshot
9204  */
9205 net.user1.orbiter.snapshot.NodeListSnapshot = function () {
9206   // Call superconstructor
9207   net.user1.orbiter.snapshot.Snapshot.call(this);
9208   this.nodeList = null;
9209   this.method = net.user1.orbiter.UPC.GET_NODELIST_SNAPSHOT;
9210 };
9211 
9212 //==============================================================================
9213 // INHERITANCE
9214 //==============================================================================
9215 net.user1.utils.extend(net.user1.orbiter.snapshot.NodeListSnapshot, net.user1.orbiter.snapshot.Snapshot);
9216 
9217 //==============================================================================
9218 // INSTANCE METHODS
9219 //==============================================================================         
9220 /**
9221  * @private
9222  */    
9223 net.user1.orbiter.snapshot.NodeListSnapshot.prototype.setNodeList = function (value) {
9224   this.nodeList = value;
9225 }
9226 
9227 net.user1.orbiter.snapshot.NodeListSnapshot.prototype.getNodeList = function () {
9228   if (!this.nodeList) {
9229     return null;
9230   }
9231   return this.nodeList.slice();
9232 };
9233 //==============================================================================
9234 // A COLLECTION OF NUMERIC UTILITIES
9235 //==============================================================================
9236 /** @class */
9237 net.user1.utils.integer = new Object();
9238 /** @constant */
9239 net.user1.utils.integer.MAX_VALUE = Math.pow(2,32) - 1;
9240 //==============================================================================
9241 // CLASS DECLARATION
9242 //==============================================================================
9243 /** 
9244  * @class
9245  * @extends net.user1.orbiter.filters.BooleanGroup
9246  */
9247 net.user1.orbiter.filters.OrGroup = function () {
9248   net.user1.orbiter.filters.BooleanGroup.call(this, net.user1.orbiter.filters.BooleanGroupType.OR);
9249 };
9250 
9251 //==============================================================================
9252 // INHERITANCE
9253 //==============================================================================
9254 net.user1.utils.extend(net.user1.orbiter.filters.OrGroup, net.user1.orbiter.filters.BooleanGroup);
9255 /**
9256  * @private
9257  */  
9258 net.user1.orbiter.upc.RemoveClientAttr = function (clientID, userID, name, scope) { 
9259   // Abort if name is invalid.
9260   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
9261     throw new Error("Cannot delete attribute. Illegal name" + 
9262                     " (see Validator.isValidAttributeName()): " + name);
9263   }
9264   
9265   // Abort if scope is invalid.
9266   if (!net.user1.orbiter.Validator.isValidAttributeScope(scope)) {
9267     throw new Error("Cannot delete client attribute. Illegal scope" + 
9268              " (see Validator.isValidAttributeScope()): " + scope);
9269   }
9270   
9271   this.method = net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR;
9272   this.args   = [clientID, userID, name, scope];
9273 };
9274 /**
9275  * @private
9276  */  
9277 net.user1.orbiter.upc.RemoveRoomAttr = function (roomID, name) { 
9278   // Abort if name is invalid.
9279   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
9280     throw new Error("Cannot delete attribute. Illegal name" + 
9281                     " (see Validator.isValidAttributeName()): " + name);
9282   }
9283   
9284   this.method = net.user1.orbiter.UPC.REMOVE_ROOM_ATTR;
9285   this.args   = [roomID, name];
9286 };
9287 /** @function */
9288 net.user1.utils.resolveMemberExpression = function (value) {
9289   var parts = value.split(".");
9290   var reference = globalObject;
9291   for (var i = 0; i < parts.length; i++) {
9292     reference = reference[parts[i]];
9293   }
9294   return reference;
9295 };
9296 //==============================================================================
9297 // CLASS DECLARATION
9298 //==============================================================================
9299 /** @class
9300 
9301 The Room class dispatches the following events:
9302 
9303 <ul class="summary">
9304 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN}</li> 
9305 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN_RESULT}</li> 
9306 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE}</li> 
9307 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE_RESULT}</li> 
9308 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.ADD_OCCUPANT}</li> 
9309 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT}</li> 
9310 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.ADD_OBSERVER}</li> 
9311 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVE_OBSERVER}</li> 
9312 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE}</li> 
9313 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE}</li> 
9314 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OCCUPANT_COUNT}</li> 
9315 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OSERVER_COUNT}</li> 
9316 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.SYNCHRONIZE}</li> 
9317 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE}</li> 
9318 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE_RESULT}</li> 
9319 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING}</li> 
9320 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT}</li> 
9321 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.REMOVED}</li> 
9322 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE}</li> 
9323 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.UPDATE}</li> 
9324 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.SET_RESULT}</li> 
9325 <li class="fixedFont">{@link net.user1.orbiter.AttributeEvent.DELETE_RESULT}</li> 
9326 </ul>
9327 
9328 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
9329 
9330     @extends net.user1.events.EventDispatcher
9331 */
9332 net.user1.orbiter.Room = function (id,
9333                                    roomManager,
9334                                    messageManager,
9335                                    clientManager,
9336                                    accountManager,
9337                                    log) {
9338   // Call superconstructor
9339   net.user1.events.EventDispatcher.call(this);
9340 
9341   // Variables
9342   this.disposed = false;
9343   this.id = null;
9344   this.syncState = null;
9345   this._clientIsInRoom = false;
9346   this._clientIsObservingRoom = false;
9347   this.numOccupants = 0;
9348   this.numObservers = 0;
9349   this.defaultClientClass = null;
9350 
9351   // Initialization  
9352   this.setRoomID(id);
9353   this.roomManager    = roomManager;
9354   this.messageManager = messageManager;
9355   this.clientManager  = clientManager;
9356   this.accountManager = accountManager;
9357   this.log = log;
9358 
9359   this.occupantList     = new net.user1.orbiter.ClientSet();
9360   this.observerList     = new net.user1.orbiter.ClientSet();
9361   this.attributeManager = new net.user1.orbiter.AttributeManager(this, this.messageManager, this.log);
9362 
9363   this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9364 }
9365 
9366 //==============================================================================    
9367 // INHERITANCE
9368 //============================================================================== 
9369 net.user1.utils.extend(net.user1.orbiter.Room, net.user1.events.EventDispatcher);
9370 
9371 // =============================================================================
9372 // DEPENDENCIES
9373 // =============================================================================
9374 /** @private */
9375 net.user1.orbiter.Room.prototype.getAttributeManager = function () {
9376   return this.attributeManager;
9377 };   
9378 
9379 // =============================================================================
9380 // ROOM ID MANAGEMENT
9381 // =============================================================================
9382 
9383  /**
9384   * @private
9385   */
9386 net.user1.orbiter.Room.prototype.setRoomID = function (roomID) {
9387   var errorMsg;
9388   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
9389     errorMsg = "Invalid room ID specified during room creation. Offending ID: " + roomID;
9390     this.log.error(errorMsg);
9391     throw new Error(errorMsg);
9392   }
9393   this.id = roomID;
9394 };
9395   
9396 net.user1.orbiter.Room.prototype.getRoomID = function () {
9397   return this.id;
9398 };
9399 
9400 net.user1.orbiter.Room.prototype.getSimpleRoomID = function () {
9401   return net.user1.orbiter.RoomIDParser.getSimpleRoomID(this.id);
9402 };
9403 
9404 net.user1.orbiter.Room.prototype.getQualifier = function () {
9405   return net.user1.orbiter.RoomIDParser.getQualifier(this.id);
9406 };
9407   
9408 // =============================================================================
9409 // JOIN/LEAVE
9410 // =============================================================================
9411   
9412 net.user1.orbiter.Room.prototype.join = function (password,
9413                                                   updateLevels) {
9414   if (this.disposed) return;
9415   
9416   // Client can't join a room the its already in.
9417   if (this.clientIsInRoom()) {
9418     this.log.warn(this + "Room join attempt aborted. Already in room.");
9419     return;
9420   }
9421   // Validate the password
9422   if (password == null) {
9423     password = "";
9424   }
9425   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
9426     this.log.error(this + "Invalid room password supplied to join(). "
9427                    + " Join request not sent. See Validator.isValidPassword().");
9428     return;
9429   }
9430   
9431   // If any update levels are specified, send them before joining.
9432   if (updateLevels != null) {
9433     this.setUpdateLevels(updateLevels);
9434   }
9435 
9436   this.messageManager.sendUPC(net.user1.orbiter.UPC.JOIN_ROOM, 
9437                               this.getRoomID(), 
9438                               password);
9439 };
9440   
9441 net.user1.orbiter.Room.prototype.leave = function () {
9442   if (this.disposed) return;
9443   
9444   if (this.clientIsInRoom()) {
9445     this.messageManager.sendUPC(net.user1.orbiter.UPC.LEAVE_ROOM, this.getRoomID());
9446   } else {
9447     this.log.debug(this + " Leave-room request ignored. Not in room.");
9448   }
9449 };
9450   
9451 /**
9452  * @private
9453  */
9454 net.user1.orbiter.Room.prototype.doJoin = function () {
9455   this._clientIsInRoom = true;
9456   this.fireJoin();
9457 };
9458     
9459 /**
9460  * @private
9461  */
9462 net.user1.orbiter.Room.prototype.doJoinResult = function (status) {
9463   this.fireJoinResult(status);
9464 };
9465     
9466     /**
9467      * @private
9468      */
9469 net.user1.orbiter.Room.prototype.doLeave = function () {
9470   var rid = this.getRoomID();
9471 
9472   // If the client is not observing the room, then dispose
9473   // of all of the room's information.
9474   if (!this.clientIsObservingRoom()) {
9475     this.purgeRoomData();
9476   }
9477     
9478   // Note that the client is no longer in this room.
9479   this._clientIsInRoom = false;
9480   this.fireLeave();
9481 }
9482 
9483 /**
9484  * @private
9485  */
9486 net.user1.orbiter.Room.prototype.doLeaveResult = function (status) {
9487   this.fireLeaveResult(status);
9488 };
9489     
9490 //==============================================================================
9491 // MESSAGING
9492 //==============================================================================
9493 
9494 net.user1.orbiter.Room.prototype.sendMessage = function (messageName, 
9495                                                          includeSelf,
9496                                                          filters) {
9497   if (this.disposed) return;
9498   
9499   // Delegate to RoomManager.sendMessage()
9500   var rest = Array.prototype.slice.call(arguments).slice(3)
9501   var roomMan = this.roomManager;
9502   var args = [messageName, 
9503              [this.getRoomID()],
9504              includeSelf,
9505              filters != null ? filters : null];
9506   roomMan.sendMessage.apply(roomMan, args.concat(rest));
9507 };
9508 
9509 net.user1.orbiter.Room.prototype.addMessageListener = function (message, listener, thisArg) {
9510   if (this.messageManager != null) {
9511     this.messageManager.addMessageListener(message,
9512                                            listener, 
9513                                            thisArg,
9514                                            [this.getRoomID()]);
9515   }
9516 };
9517 
9518 net.user1.orbiter.Room.prototype.removeMessageListener = function (message, listener) {
9519   if (this.messageManager != null) {
9520     this.messageManager.removeMessageListener(message, 
9521                                               listener);
9522   }
9523 };
9524 
9525 net.user1.orbiter.Room.prototype.hasMessageListener = function (message,
9526                                                                 listener) {
9527   // First, get the list of messsage listeners for this message
9528   var listeners = this.messageManager.getMessageListeners(message);
9529   var messageListener;
9530   for (var i = 0; i < listeners.length; i++) {
9531     messageListener = listeners[i];
9532     var listenerRoomIDs = messageListener.getForRoomIDs();
9533     // ===== Run once for each room id =====
9534     var listenerRoomID;
9535     for (var j = 0; j < listenerRoomIDs.length; j++) {
9536       listenerRoomID = listenerRoomIDs[i];
9537       if (listenerRoomID == this.getRoomID()) {
9538         return true;
9539       }
9540     }
9541   }
9542   return false;
9543 };
9544 
9545 //==============================================================================
9546 // SYNCHRONIZATION
9547 //==============================================================================
9548     
9549 /**
9550  * @private
9551  */        
9552 net.user1.orbiter.Room.prototype.synchronize = function (manifest) {
9553   var oldSyncState = this.getSyncState();
9554   this.log.debug(this + " Begin synchronization.");
9555   this.setSyncState(net.user1.orbiter.SynchronizationState.SYNCHRONIZING);
9556 
9557   // SYNC ROOM ATTRIBUTES
9558   this.getAttributeManager().getAttributeCollection().synchronizeScope(net.user1.orbiter.Tokens.GLOBAL_ATTR, manifest.attributes);
9559   if (this.disposed) {
9560     return;
9561   }
9562 
9563   // SYNC OCCUPANT LIST
9564   var oldOccupantList = this.getOccupantIDs();
9565   var newOccupantList = [];
9566   var thisOccupantClientID;
9567   var thisOccupantUserID;
9568   var thisOccupant;
9569   var thisOccupantAccount;
9570   
9571   // Add all unknown occupants to the room's occupant list, and
9572   // synchronize all existing occupants.
9573   for (var i = manifest.occupants.length; --i >= 0;) {
9574     thisOccupantClientID = manifest.occupants[i].clientID;
9575     thisOccupantUserID = manifest.occupants[i].userID;
9576     
9577     newOccupantList.push(thisOccupantClientID);
9578     
9579     thisOccupant = this.clientManager.requestClient(thisOccupantClientID);
9580     // Init user account, if any
9581     thisOccupantAccount = this.accountManager.requestAccount(thisOccupantUserID);
9582     if (thisOccupantAccount != null) {
9583       thisOccupant.setAccount(thisOccupantAccount);
9584     }
9585     
9586     // If it's not the current client, update it.
9587     // The current client obtains its attributes through separate u8s.
9588     if (!thisOccupant.isSelf()) {
9589       thisOccupant.synchronize(manifest.occupants[i]);
9590     }
9591     
9592     this.addOccupant(thisOccupant);
9593     if (this.disposed) {
9594       return;
9595     }
9596   }
9597   
9598   // Remove occupants that are now gone...
9599   var oldClientID;
9600   for (i = oldOccupantList.length; --i >= 0;) {
9601     oldClientID = oldOccupantList[i];
9602     if (net.user1.utils.ArrayUtil.indexOf(newOccupantList, oldClientID) == -1) {
9603       this.removeOccupant(oldClientID);
9604       if (this.disposed) {
9605         return;
9606       }
9607     }
9608   }
9609 
9610   // SYNC OBSERVER LIST
9611   var oldObserverList = this.getObserverIDs();
9612   var newObserverList = [];
9613   var thisObserverClientID;
9614   var thisObserverUserID;
9615   var thisObserver;
9616   var thisObserverAccount;
9617   
9618   // Add all unknown observers to the room's observer list, and
9619   // synchronize all existing observers.
9620   for (i = manifest.observers.length; --i >= 0;) {
9621     thisObserverClientID = manifest.observers[i].clientID;
9622     thisObserverUserID = manifest.observers[i].userID;
9623     
9624     newObserverList.push(thisObserverClientID);
9625     
9626     thisObserver = this.clientManager.requestClient(thisObserverClientID);
9627     // Init user account, if any
9628     thisObserverAccount = this.accountManager.requestAccount(thisObserverUserID);
9629     if (thisObserverAccount != null) {
9630       thisObserver.setAccount(thisObserverAccount);
9631     }
9632     
9633     // If it's not the current client, update it.
9634     // The current client obtains its attributes through separate u8s.
9635     if (!thisObserver.isSelf()) {
9636       thisObserver.synchronize(manifest.observers[i]);
9637     }
9638     
9639     this.addObserver(thisObserver);
9640     if (this.disposed) {
9641       return;
9642     }
9643   }
9644   
9645   // Remove observers that are now gone...
9646   var oldClientID;
9647   for (i = oldObserverList.length; --i >= 0;) {
9648     oldClientID = oldObserverList[i]
9649     if (net.user1.utils.ArrayUtil.indexOf(newObserverList, oldClientID) == -1) {
9650       this.removeObserver(oldClientID);
9651       if (this.disposed) {
9652         return;
9653       }
9654     }
9655   }
9656   
9657   // UPDATE CLIENT COUNTS
9658   //   If a client list is available, use its length to calculate the
9659   //   client count. That way, the list length and the "get count" method
9660   //   return values will be the same (e.g., getOccupants().length and
9661   //   getNumOccupants()). Otherwise, rely on the server's reported count.
9662   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());
9663   if (levels.occupantList) {
9664     this.setNumOccupants(this.occupantList.length());
9665   } else if (levels.occupantCount) {
9666     this.setNumOccupants(manifest.occupantCount);
9667   }
9668   if (levels.observerList) {
9669     this.setNumObservers(this.observerList.length());
9670   } else if (levels.observerCount) {
9671     this.setNumObservers(manifest.observerCount);
9672   }
9673   
9674   // Update sync state 
9675   this.setSyncState(oldSyncState);
9676   
9677   // Tell listeners that synchronization is complete
9678   this.fireSynchronize(net.user1.orbiter.Status.SUCCESS);
9679 }
9680     
9681 /**
9682  * @private
9683  */
9684 net.user1.orbiter.Room.prototype.setSyncState = function (newSyncState) {
9685   this.syncState = newSyncState;
9686 };
9687 
9688 /**
9689  * @private
9690  */
9691 net.user1.orbiter.Room.prototype.updateSyncState = function () {
9692   if (this.disposed) {
9693     this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9694   } else {
9695     if (this.roomManager.hasObservedRoom(this.getRoomID()) 
9696         || this.roomManager.hasOccupiedRoom(this.getRoomID())
9697         || this.roomManager.hasWatchedRoom(this.getRoomID())) {
9698       this.setSyncState(net.user1.orbiter.SynchronizationState.SYNCHRONIZED);
9699     } else {
9700       this.setSyncState(net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED);
9701     }
9702   }
9703 }
9704 
9705 net.user1.orbiter.Room.prototype.getSyncState = function () {
9706   return this.syncState;
9707 };
9708 
9709 //==============================================================================
9710 // UPDATE LEVELS
9711 //==============================================================================
9712 
9713 net.user1.orbiter.Room.prototype.setUpdateLevels = function (updateLevels) {
9714   if (this.messageManager) {
9715     this.messageManager.sendUPC(net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS,
9716                                 this.getRoomID(),
9717                                 updateLevels.toInt());
9718   }
9719 };
9720 
9721 //==============================================================================
9722 // OBSERVATION
9723 //==============================================================================
9724    
9725 net.user1.orbiter.Room.prototype.observe = function (password,
9726                                                      updateLevels) {
9727   if (this.disposed) return;
9728   
9729   this.roomManager.observeRoom(this.getRoomID(), 
9730                                password,
9731                                updateLevels);
9732 };
9733   
9734    
9735 net.user1.orbiter.Room.prototype.stopObserving = function () {
9736   if (this.disposed) return;
9737   
9738   if (this.clientIsObservingRoom()) {
9739     this.messageManager.sendUPC(net.user1.orbiter.UPC.STOP_OBSERVING_ROOM, this.getRoomID());
9740   } else {
9741     this.log.debug(this + " Stop-observing-room request ignored. Not observing room.");
9742   }
9743 };
9744 
9745 /**
9746  * @private
9747  */
9748 net.user1.orbiter.Room.prototype.doObserve = function () {
9749   this._clientIsObservingRoom = true;
9750   this.fireObserve();
9751 };
9752 
9753 /**
9754  * @private
9755  */
9756 net.user1.orbiter.Room.prototype.doObserveResult = function (status) {
9757   this.fireObserveResult(status);
9758 };
9759 
9760 /**
9761  * @private
9762  */
9763 net.user1.orbiter.Room.prototype.doStopObserving = function () {
9764   var rid = this.getRoomID();
9765 
9766   // If the client is not in the room, then we dispose
9767   // of all of the room's information.
9768   if (!this.clientIsInRoom()) {
9769     this.purgeRoomData();
9770   }
9771     
9772   this._clientIsObservingRoom = false;
9773   
9774   this.fireStopObserving();
9775 };
9776     
9777 /**
9778  * @private
9779  */
9780 net.user1.orbiter.Room.prototype.doStopObservingResult = function (status) {
9781   this.fireStopObservingResult(status);
9782 }
9783     
9784 //==============================================================================
9785 // OCCUPANT MANAGEMENT
9786 //==============================================================================
9787 
9788 /**
9789  * @private
9790  */
9791 net.user1.orbiter.Room.prototype.addOccupant = function (client) {
9792   // Don't add the client if it's already in the list.
9793   if (this.occupantList.contains(client)) {
9794       this.log.info(this + " ignored addOccupant() request. Occupant list" +
9795                     " already contains client:" + client + ".");
9796       return;
9797   }
9798 
9799   // Add the client
9800   this.occupantList.add(client);
9801   
9802   // Update the number of clients in the room
9803   this.setNumOccupants(this.occupantList.length());  
9804   
9805   // Register for attribute change events
9806   if (!this.observerList.contains(client)) {
9807     this.addClientAttributeListeners(client);
9808   }
9809   
9810   // Tell listeners an occupant was added
9811   this.fireAddOccupant(client.getClientID());  
9812 };
9813 
9814 /**
9815  * @private
9816  */
9817 net.user1.orbiter.Room.prototype.removeOccupant = function (clientID) {
9818   var client = this.occupantList.removeByClientID(clientID);
9819   var clientFound = client != null;
9820   
9821   // Update the number of clients in the room
9822   this.setNumOccupants(this.occupantList.length());
9823   
9824   // Unregister for attribute change events
9825   if (!this.observerList.contains(client)) {
9826     this.removeClientAttributeListeners(client);
9827   }
9828   
9829   // Tell listeners an occupant was removed
9830   var customClient = client.getCustomClient(this.getRoomID());
9831   this.fireRemoveOccupant(customClient != null ? customClient : client);
9832 
9833   if (!clientFound) {
9834     this.log.debug(this + " could not remove occupant: " 
9835                    + clientID + ". No such client in the room's occupant list.");
9836   }
9837 };
9838 
9839 net.user1.orbiter.Room.prototype.getOccupantIDs = function () {
9840   if (this.disposed) return null;
9841   
9842   return this.occupantList.getAllIDs();
9843 }
9844 
9845 net.user1.orbiter.Room.prototype.getOccupants = function () {
9846   if (this.disposed) return null;
9847   
9848   var occupants = this.occupantList.getAll();
9849   var occupantsList = new Array();
9850   var customClient;
9851   var occupant;
9852   
9853   for (var clientID in occupants) {
9854     occupant = occupants[clientID];
9855     customClient = occupant.getCustomClient(this.getRoomID());
9856     if (customClient != null) {
9857       occupantsList.push(customClient);
9858     } else {
9859       occupantsList.push(occupant);
9860     }
9861   }
9862   return occupantsList;
9863 }
9864         
9865 /**
9866  * @private
9867  */        
9868 net.user1.orbiter.Room.prototype.getOccupantsInternal = function () {
9869   return this.occupantList.getAll();
9870 }
9871 
9872 net.user1.orbiter.Room.prototype.clientIsInRoom = function (clientID) {
9873   if (this.disposed) return false;
9874       
9875   if (clientID == null) {
9876     return this._clientIsInRoom;
9877   }
9878   return this.occupantList.containsClientID(clientID);
9879 };
9880     
9881 net.user1.orbiter.Room.prototype.getNumOccupants = function () {
9882   if (this.disposed) return 0;
9883   
9884   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());;
9885   if (levels != null) {
9886     if (levels.occupantCount || levels.occupantList) {
9887       return this.numOccupants;
9888     } else {
9889       this.log.warn(this + " getNumOccupants() called, but no occupant count is " +
9890                     "available. To enable occupant count, turn on occupant list" +
9891                     " updates or occupant count updates via the Room's setUpdateLevels()" +
9892                     " method.");
9893       return 0;
9894     }
9895   } else {
9896     this.log.debug(this + " getNumOccupants() called, but the current client's update"
9897                    + " levels for the room are unknown. To determine the room's"
9898                    + " occupant count, first join or observe the room.");
9899     return 0;
9900   }
9901 };
9902 
9903 /**
9904  * @private
9905  */
9906 net.user1.orbiter.Room.prototype.setNumOccupants = function (newNumOccupants) {
9907   var oldNumClients = this.numOccupants;
9908   this.numOccupants = newNumOccupants;
9909 
9910   // Tell listeners that the number of clients in the room has changed.
9911   if (oldNumClients != newNumOccupants) {
9912     this.fireOccupantCount(newNumOccupants);
9913   }
9914 };
9915     
9916 //==============================================================================
9917 // ROOM SETTINGS
9918 //==============================================================================
9919 
9920 net.user1.orbiter.Room.prototype.getRoomSettings = function () {
9921   if (this.disposed) return null;
9922   
9923   var settings = new net.user1.orbiter.RoomSettings();
9924   var maxClients = this.getAttribute(net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR);
9925   var removeOnEmpty = this.getAttribute(net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR);
9926   
9927   settings.maxClients = maxClients == null ? null : maxClients;
9928   switch (removeOnEmpty) {
9929     case null:
9930       settings.removeOnEmpty = null;
9931       break;
9932       
9933     case "true":
9934       settings.removeOnEmpty = true;
9935       break;
9936       
9937     case "false":
9938       settings.removeOnEmpty = false;
9939       break;
9940   }
9941   
9942   return settings;
9943 };
9944 
9945 net.user1.orbiter.Room.prototype.setRoomSettings = function (settings) {
9946   if (this.disposed) return;
9947   
9948   if (settings.maxClients != null) {
9949     this.setAttribute(net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR, settings.maxClients.toString());
9950   } 
9951   if (settings.password != null) {
9952     this.setAttribute(net.user1.orbiter.Tokens.PASSWORD_ATTR, settings.password);
9953   } 
9954   if (settings.removeOnEmpty != null) {
9955     this.setAttribute(net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR, settings.removeOnEmpty.toString());
9956   } 
9957 };
9958     
9959 //==============================================================================
9960 // OBSERVER MANAGEMENT
9961 //==============================================================================
9962 
9963 /**
9964  * @private
9965  */
9966 net.user1.orbiter.Room.prototype.addObserver = function (client) {
9967   // Don't add the client if it's already in the list.
9968   if (this.observerList.contains(client)) {
9969       this.log.info(this + " ignored addObserver() request. Observer list" +
9970                     " already contains client:" + client + ".");
9971       return;
9972   }
9973 
9974   // Add the client
9975   this.observerList.add(client);
9976   
9977   // Update the number of clients in the room
9978   this.setNumObservers(this.observerList.length());  
9979   
9980   // Register for attribute change events
9981   if (!this.occupantList.contains(client)) {
9982     this.addClientAttributeListeners(client);
9983   }
9984   
9985   // Tell listeners an observer was added
9986   this.fireAddObserver(client.getClientID());  
9987 };
9988 
9989 /**
9990  * @private
9991  */
9992 net.user1.orbiter.Room.prototype.removeObserver = function (clientID) {
9993   var client = this.observerList.removeByClientID(clientID);
9994   var clientFound = client != null;
9995   
9996   // Update the number of clients in the room
9997   this.setNumObservers(this.observerList.length());
9998   
9999   // Unregister for attribute change events
10000   if (!this.occupantList.contains(client)) {
10001     this.removeClientAttributeListeners(client);
10002   }
10003   
10004   // Tell listeners an observer was removed
10005   var customClient = client.getCustomClient(this.getRoomID());
10006   this.fireRemoveObserver(customClient != null ? customClient : client);
10007 
10008   if (!clientFound) {
10009     this.log.debug(this + " could not remove observer: " 
10010                    + clientID + ". No such client in the room's observer list.");
10011   }
10012 };
10013 
10014 net.user1.orbiter.Room.prototype.getObserverIDs = function () {
10015   if (this.disposed) return null;
10016   
10017   return this.observerList.getAllIDs();
10018 };
10019 
10020 net.user1.orbiter.Room.prototype.getObservers = function () {
10021   if (this.disposed) return null;
10022   
10023   var observers = this.observerList.getAll();
10024   var observersList = new Array();
10025   var customClient;
10026   var observer;
10027   
10028   for (var clientID in observers) {
10029     observer = observers[clientID];
10030     customClient = observer.getCustomClient(this.getRoomID());
10031     if (customClient != null) {
10032       observersList.push(customClient);
10033     } else {
10034       observersList.push(observer);
10035     }
10036   }
10037   return observersList;
10038 };
10039     
10040 /**
10041  * @private
10042  */        
10043 net.user1.orbiter.Room.prototype.getObserversInternal = function () {
10044   return this.observerList.getAll();
10045 }
10046 
10047 net.user1.orbiter.Room.prototype.clientIsObservingRoom = function (clientID) {
10048   if (this.disposed) return false;
10049   
10050   if (clientID == null) {
10051     return this._clientIsObservingRoom;
10052   }
10053   return this.observerList.containsClientID(clientID);
10054 }
10055     
10056 net.user1.orbiter.Room.prototype.getNumObservers = function () {
10057   if (this.disposed) return 0;
10058   
10059   var levels = this.clientManager.self().getUpdateLevels(this.getRoomID());
10060   if (levels != null) {
10061     if (levels.observerCount || levels.observerList) {
10062       return this.numObservers;
10063     } else {
10064       this.log.warn(this + " getNumObservers() called, but no observer count is " +
10065                "available. To enable observer count, turn on observer list" +
10066                " updates or observer count updates via the Room's setUpdateLevels()" +
10067                " method.");
10068       return 0;
10069     }
10070   } else {
10071     this.log.warn(this + " getNumObservers() called, but the current client's update "
10072       + " levels for the room are unknown. Please report this issue to union@user1.net.");
10073     return 0;
10074   }
10075 };
10076 
10077 /**
10078  * @private
10079  */
10080 net.user1.orbiter.Room.prototype.setNumObservers = function (newNumObservers) {
10081   var oldNumClients = this.numObservers;
10082   this.numObservers = newNumObservers;
10083 
10084   // Tell listeners that the number of clients in the room has changed.
10085   if (oldNumClients != newNumObservers) {
10086     this.fireObserverCount(newNumObservers);
10087   }
10088 };
10089     
10090 //==============================================================================
10091 // CLIENT ACCESS
10092 //==============================================================================
10093 
10094 net.user1.orbiter.Room.prototype.getClient = function (id) {
10095   if (this.disposed) return null;
10096   
10097   var customClient;
10098   var client = this.occupantList.getByClientID(id);
10099   client = (client == null) ? this.observerList.getByClientID(id) : client;
10100   
10101   if (client != null) {
10102     customClient = client.getCustomClient(this.getRoomID());
10103   }
10104   return customClient == null ? client : customClient;
10105 };
10106     
10107 //==============================================================================
10108 // CLIENT ATTRIBUTE LISTENERS
10109 //==============================================================================
10110 /** private */
10111 net.user1.orbiter.Room.prototype.addClientAttributeListeners = function (client) {
10112   client.addEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateClientAttributeListener, this);
10113   client.addEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteClientAttributeListener, this);
10114 };
10115     
10116 /** private */
10117 net.user1.orbiter.Room.prototype.removeClientAttributeListeners = function (client) {
10118   client.removeEventListener(net.user1.orbiter.AttributeEvent.UPDATE, this.updateClientAttributeListener, this);
10119   client.removeEventListener(net.user1.orbiter.AttributeEvent.DELETE, this.deleteClientAttributeListener, this);
10120 };
10121     
10122 /** private */
10123 net.user1.orbiter.Room.prototype.updateClientAttributeListener = function (e) {
10124   var attr = e.getChangedAttr();
10125   var client = e.target;
10126   var customClient = client.getCustomClient(this.getRoomID());
10127   
10128   this.fireUpdateClientAttribute(customClient == null ? client : customClient,
10129                                  attr.scope, attr.name, attr.value, attr.oldValue);
10130 };
10131     
10132 /** private */
10133 net.user1.orbiter.Room.prototype.deleteClientAttributeListener = function (e) {
10134   var attr = e.getChangedAttr();
10135   var client = e.target;
10136   var customClient = client.getCustomClient(this.getRoomID());
10137   
10138   this.fireDeleteClientAttribute(customClient == null ? client : customClient,
10139                                  attr.scope, attr.name, attr.value);
10140 }
10141     
10142 //==============================================================================
10143 // CLIENT CLASS
10144 //==============================================================================
10145 
10146 net.user1.orbiter.Room.prototype.setDefaultClientClass = function (defaultClass) {
10147   this.defaultClientClass = defaultClass;
10148 };
10149     
10150 net.user1.orbiter.Room.prototype.getDefaultClientClass = function () {
10151   return this.defaultClientClass;
10152 }
10153     
10154 //==============================================================================
10155 // ATTRIBUTES
10156 //==============================================================================
10157 
10158 net.user1.orbiter.Room.prototype.setAttribute = function (attrName,
10159                                   attrValue, 
10160                                   isShared,
10161                                   isPersistent,
10162                                   evaluate) {
10163   if (this.disposed) return;
10164   
10165   if (isShared !== false) {
10166     isShared = true;
10167   }
10168   
10169   // Create an integer to hold the attribute options.
10170   var attrOptions = (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
10171                     | (isPersistent ? net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT : 0)
10172                     | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
10173   this.attributeManager.setAttribute(new net.user1.orbiter.upc.SetRoomAttr(attrName, attrValue, attrOptions, this.getRoomID()));
10174 }
10175   
10176 net.user1.orbiter.Room.prototype.deleteAttribute = function (attrName) {
10177   if (this.disposed) return;
10178   
10179   var deleteRequest = new net.user1.orbiter.upc.RemoveRoomAttr(this.getRoomID(), attrName);
10180   this.attributeManager.deleteAttribute(deleteRequest);
10181 };
10182   
10183 net.user1.orbiter.Room.prototype.getAttribute = function (attrName) {
10184   if (this.disposed) return null;
10185   
10186   return this.attributeManager.getAttribute(attrName);
10187 };
10188   
10189 net.user1.orbiter.Room.prototype.getAttributes = function () {
10190   if (this.disposed) return null;
10191   
10192   // Room attributes are considered global
10193   return this.attributeManager.getAttributesByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR);
10194 }
10195 
10196 // =============================================================================
10197 // ROOM MODULES
10198 // =============================================================================
10199 
10200 net.user1.orbiter.Room.prototype.sendModuleMessage = function (messageName, 
10201                                                                messageArguments) {
10202   if (this.disposed) return;
10203   
10204   var sendupcArgs = [net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE, this.getRoomID(), messageName];
10205   
10206   for (var arg in messageArguments) {
10207     sendupcArgs.push(arg + "|" + messageArguments[arg]);
10208   }
10209         
10210   this.messageManager.sendUPC.apply(this.messageManager, sendupcArgs);
10211 };
10212 
10213 // =============================================================================
10214 // ROOM REMOVAL
10215 // =============================================================================
10216 
10217 net.user1.orbiter.Room.prototype.remove = function (password) {
10218   if (this.disposed) return;
10219   
10220   this.roomManager.removeRoom(this.getRoomID(), password);
10221 };
10222 
10223 // =============================================================================
10224 // TOSTRING
10225 // =============================================================================
10226 
10227 net.user1.orbiter.Room.prototype.toString = function () {
10228   return "[ROOM id: " + this.getRoomID() + "]";
10229 };
10230     
10231     
10232 // =============================================================================
10233 // EVENT DISPATCHING
10234 // =============================================================================
10235 
10236 /**
10237  * @private
10238  */
10239 net.user1.orbiter.Room.prototype.fireJoin = function () {
10240   if (this.log) this.log.info(this + " Room joined.");
10241 
10242   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN);
10243   this.dispatchEvent(e);
10244 };
10245   
10246 /**
10247  * @private
10248  */
10249 net.user1.orbiter.Room.prototype.fireJoinResult = function (status) {
10250   if (this.log) this.log.info(this + " Join result: " + status);
10251 
10252   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN_RESULT, 
10253                                   null,  null, status);
10254   this.dispatchEvent(e);
10255 };
10256   
10257 /**
10258  * @private
10259  */
10260 net.user1.orbiter.Room.prototype.fireLeave = function () {
10261   if (this.log) this.log.info(this + " Room left.");
10262   
10263   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE);
10264   this.dispatchEvent(e);
10265 };
10266   
10267 /**
10268  * @private
10269  */
10270 net.user1.orbiter.Room.prototype.fireLeaveResult = function (status) {
10271   if (this.log) this.log.info(this + " Leave result: " + status);
10272   
10273   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE_RESULT, 
10274                                   null, null, status);
10275   this.dispatchEvent(e);
10276 };
10277   
10278 /**
10279  * @private
10280  */
10281 net.user1.orbiter.Room.prototype.fireAddOccupant = function (id) {
10282   if (this.log) this.log.info(this + " Added occupant: [" + id + "].");
10283 
10284   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.ADD_OCCUPANT, 
10285                                   this.getClient(id),
10286                                   id);
10287   this.dispatchEvent(e);
10288 };
10289   
10290 /**
10291  * @private
10292  */
10293 net.user1.orbiter.Room.prototype.fireRemoveOccupant = function (client) {
10294   if (this.log) this.log.info(this + " Removed occupant: " + client + ".");
10295 
10296   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT,
10297                                   client,
10298                                   client.getClientID());
10299   this.dispatchEvent(e);
10300 };
10301   
10302 /**
10303  * @private
10304  */
10305 net.user1.orbiter.Room.prototype.fireAddObserver = function (id) {
10306   if (this.log) this.log.info(this + " Added observer: [" + id + "].");
10307 
10308   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.ADD_OBSERVER, 
10309                                   this.getClient(id),
10310                                   id);
10311   this.dispatchEvent(e);
10312 };
10313   
10314 /**
10315  * @private
10316  */
10317 net.user1.orbiter.Room.prototype.fireRemoveObserver = function (client) {
10318   if (this.log) this.log.info(this + " Removed observer: " + client + ".");
10319 
10320   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVE_OBSERVER,
10321                                   client,
10322                                   client.getClientID());
10323   this.dispatchEvent(e);
10324 };
10325   
10326 /**
10327  * @private
10328  */
10329 net.user1.orbiter.Room.prototype.fireUpdateClientAttribute = function (client, 
10330                                              scope,
10331                                              attrName,
10332                                              attrVal, 
10333                                              oldVal) {
10334   if (this.log) this.log.info(this + " Client attribute updated on " + client + "."
10335             + " Attribute [" + attrName + "] is now: [" 
10336             + attrVal + "]. Old value was: [" + oldVal + "].");
10337 
10338   var changedAttr = new net.user1.orbiter.Attribute (attrName, 
10339                                              attrVal, 
10340                                              oldVal, 
10341                                              scope);
10342   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE,
10343                                   client,
10344                                   client.getClientID(), 
10345                                   null, 
10346                                   changedAttr);
10347   this.dispatchEvent(e);
10348 };
10349 
10350 /**
10351  * @private
10352  */
10353 net.user1.orbiter.Room.prototype.fireDeleteClientAttribute = function (client, 
10354                                                  scope,
10355                                                  attrName,
10356                                                  attrValue) {
10357   if (this.log) this.log.info(this + " Client attribute deleted from " + client + "."
10358            + " Deleted attribute: [" + attrName + "].");
10359 
10360   var deletedAttr = new net.user1.orbiter.Attribute(attrName, attrValue, null, scope);
10361 
10362   // Trigger event on listeners.
10363   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE,
10364                                   client,
10365                                   client.getClientID(),
10366                                   null,
10367                                   deletedAttr);
10368   this.dispatchEvent(e);
10369 };
10370     
10371 /**
10372  * @private
10373  */
10374 net.user1.orbiter.Room.prototype.fireOccupantCount = function (newNumClients) {
10375   if (this.log) this.log.info(this + " New occupant count: " + newNumClients);
10376 
10377   // Trigger event on listeners.
10378   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OCCUPANT_COUNT, 
10379                              null, null, null, null, newNumClients);
10380   this.dispatchEvent(e);
10381 };
10382     
10383 /**
10384  * @private
10385  */
10386 net.user1.orbiter.Room.prototype.fireObserverCount = function (newNumClients) {
10387   if (this.log) this.log.info(this + " New observer count: " + newNumClients);
10388 
10389   // Trigger event on listeners.
10390   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVER_COUNT, 
10391                              null, null, null, null, newNumClients);
10392   this.dispatchEvent(e);
10393 };
10394     
10395 /**
10396  * @private
10397  */
10398 net.user1.orbiter.Room.prototype.fireSynchronize = function (status) {
10399   if (this.log) this.log.info(this + " Synchronization complete.");
10400   
10401   // Trigger event on listeners.
10402   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.SYNCHRONIZE,
10403                                           null, null, status);
10404   this.dispatchEvent(e);
10405 };
10406 
10407 /**
10408  * @private
10409  */
10410 net.user1.orbiter.Room.prototype.fireObserve = function () {
10411   if (this.log) this.log.info(this + " Room observed.");
10412   
10413   // Trigger event on listeners.
10414   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE);
10415   this.dispatchEvent(e);
10416 };
10417 
10418 /**
10419  * @private
10420  */
10421 net.user1.orbiter.Room.prototype.fireObserveResult = function (status) {
10422   if (this.log) this.log.info(this + " Observe result: " + status);
10423   
10424   // Trigger event on listeners.
10425   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE_RESULT, null, null, status);
10426   this.dispatchEvent(e);
10427 };
10428 
10429 /**
10430  * @private
10431  */
10432 net.user1.orbiter.Room.prototype.fireStopObserving = function () {
10433   if (this.log) this.log.info(this + " Observation stopped.");
10434   
10435   // Trigger event on listeners.
10436   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING);
10437   this.dispatchEvent(e);
10438 };
10439     
10440 /**
10441  * @private
10442  */
10443 net.user1.orbiter.Room.prototype.fireStopObservingResult = function (status) {
10444   if (this.log) this.log.info(this + "Stop observing result:  " + 
10445            status);
10446   
10447   // Trigger event on listeners.
10448   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT,
10449                                   null, null, status);
10450   this.dispatchEvent(e);
10451 };
10452     
10453 /**
10454  * @private
10455  */
10456 net.user1.orbiter.Room.prototype.fireRemoved = function () {
10457   // Trigger event on listeners.
10458   var e = new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.REMOVED);
10459   this.dispatchEvent(e);
10460 };
10461 
10462 //==============================================================================
10463 // CLEANUP and DISPOSAL
10464 //==============================================================================
10465 /** private */
10466 net.user1.orbiter.Room.prototype.purgeRoomData = function () {
10467   if (this.disposed) return;
10468   
10469   // Clear the client lists
10470   this.log.debug(this + " Clearing occupant list.");
10471   for (var occupantID in this.occupantList.getAll()) {
10472     this.removeClientAttributeListeners(this.occupantList.getByClientID(occupantID));
10473   }
10474   this.occupantList.removeAll();
10475   
10476   this.log.debug(this + " Clearing observer list.");
10477   for (var observerID in this.observerList.getAll()) {
10478     this.removeClientAttributeListeners(this.observerList.getByClientID(observerID));
10479   }
10480   this.observerList.removeAll();
10481       
10482   // Clear room attributes.
10483   this.log.debug(this + " Clearing room attributes.");
10484   this.attributeManager.removeAll();
10485 };
10486     
10487 /**
10488  * @private
10489  */
10490 net.user1.orbiter.Room.prototype.shutdown = function () {
10491   if (this.disposed) return;
10492   
10493   // Store a temp reference to the log for use in this method after
10494   // the room has released all its resources.
10495   var theLog = this.log;
10496   
10497   theLog.debug(this + " Shutdown started.");
10498 
10499   // Notify the room's listeners that the client left the room.
10500   if (this.clientIsInRoom()) {
10501     theLog.info(this + " Current client is in the room. Forcing the client to leave...");
10502     this.doLeave();
10503   }
10504 
10505   // Notify the room's listeners that the client stopped observing the room.
10506   if (this.clientIsObservingRoom()) {
10507     theLog.info(this + " Current client is observing the room. Forcing the client to stop observing...");
10508     this.doStopObserving();
10509   }
10510   
10511   theLog.info(this + " Dereferencing resources.");
10512   
10513   // Dereference objects.
10514   this.purgeRoomData();
10515   
10516   this.attributeManager.dispose();
10517   // Fire removed before nulling the MessageManager object so that listeners have a
10518   // last chance to respond by communicating with the server (or by
10519   // removing themselves from the connection's listener list)
10520   this.fireRemoved();  
10521   this.dispose();
10522 
10523   theLog.info(this + " Shutdown complete.");
10524 }
10525 
10526 /**
10527  * @private
10528  */
10529 net.user1.orbiter.Room.prototype.dispose = function () {
10530   this.log = null;
10531   this.syncState = null;
10532   this.occupantList = null;
10533   this.observerList = null;
10534   this.attributeManager = null;
10535   this.numOccupants = 0;
10536   this.defaultClientClass = null
10537   this.messageManager = null;
10538   this.roomManager = null;
10539   this.disposed = true;
10540 };
10541 //==============================================================================
10542 // CLASS DECLARATION
10543 //==============================================================================
10544 /**
10545  * @class
10546  */
10547 net.user1.orbiter.RoomClassRegistry = function () {
10548   this.registry = new Object();
10549 };
10550 
10551 net.user1.orbiter.RoomClassRegistry.prototype.setRoomClass = function (roomID, roomClass) {
10552   this.registry[roomID] = roomClass;
10553 };
10554 
10555 net.user1.orbiter.RoomClassRegistry.prototype.clearRoomClass = function (roomID) {
10556   delete this.registry[roomID];
10557 };
10558 
10559 
10560 net.user1.orbiter.RoomClassRegistry.prototype.getRoomClass = function (roomID) {
10561   return this.registry[roomID] ? this.registry[roomID] : net.user1.orbiter.Room;
10562 };
10563 //==============================================================================
10564 // CLASS DECLARATION
10565 //==============================================================================
10566 /** @class
10567     @extends net.user1.events.Event
10568 */
10569 net.user1.orbiter.RoomEvent = function (type,
10570                                         client,
10571                                         clientID, 
10572                                         status, 
10573                                         changedAttr, 
10574                                         numClients,
10575                                         roomID) {
10576   net.user1.events.Event.call(this, type);
10577   
10578   this.client = client;
10579   this.clientID = clientID == "" ? null : clientID;
10580   this.status = status;
10581   this.changedAttr = changedAttr;
10582   this.numClients = numClients;
10583   this.roomID = roomID;
10584 };
10585 
10586 
10587 //==============================================================================
10588 // INHERITANCE
10589 //==============================================================================
10590 net.user1.utils.extend(net.user1.orbiter.RoomEvent, net.user1.events.Event);
10591 
10592 //==============================================================================
10593 // STATIC VARIABLES
10594 //==============================================================================
10595 
10596 /** @constant */
10597 net.user1.orbiter.RoomEvent.JOIN = "JOIN";
10598 /** @constant */
10599 net.user1.orbiter.RoomEvent.JOIN_RESULT = "JOIN_RESULT";
10600 /** @constant */
10601 net.user1.orbiter.RoomEvent.LEAVE = "LEAVE";
10602 /** @constant */
10603 net.user1.orbiter.RoomEvent.LEAVE_RESULT = "LEAVE_RESULT";
10604 /** @constant */
10605 net.user1.orbiter.RoomEvent.OBSERVE = "OBSERVE";
10606 /** @constant */
10607 net.user1.orbiter.RoomEvent.OBSERVE_RESULT = "OBSERVE_RESULT";    
10608 /** @constant */
10609 net.user1.orbiter.RoomEvent.STOP_OBSERVING = "STOP_OBSERVING";
10610 /** @constant */
10611 net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT = "STOP_OBSERVING_RESULT";        
10612 /** @constant */
10613 net.user1.orbiter.RoomEvent.SYNCHRONIZE = "SYNCHRONIZE";
10614 /** @constant */
10615 net.user1.orbiter.RoomEvent.UPDATE_CLIENT_ATTRIBUTE = "UPDATE_CLIENT_ATTRIBUTE";
10616 /** @constant */
10617 net.user1.orbiter.RoomEvent.DELETE_CLIENT_ATTRIBUTE = "DELETE_CLIENT_ATTRIBUTE";
10618 /** @constant */
10619 net.user1.orbiter.RoomEvent.ADD_OCCUPANT = "ADD_OCCUPANT";
10620 /** @constant */
10621 net.user1.orbiter.RoomEvent.REMOVE_OCCUPANT = "REMOVE_OCCUPANT";
10622 /** @constant */
10623 net.user1.orbiter.RoomEvent.ADD_OBSERVER = "ADD_OBSERVER";
10624 /** @constant */
10625 net.user1.orbiter.RoomEvent.REMOVE_OBSERVER = "REMOVE_OBSERVER";
10626 /** @constant */
10627 net.user1.orbiter.RoomEvent.OCCUPANT_COUNT = "OCCUPANT_COUNT";
10628 /** @constant */
10629 net.user1.orbiter.RoomEvent.OBSERVER_COUNT = "OBSERVER_COUNT";
10630 /** @constant */
10631 net.user1.orbiter.RoomEvent.REMOVED = "REMOVED";
10632 
10633 
10634 net.user1.orbiter.RoomEvent.prototype.getRoomID = function () {
10635   return this.roomID;
10636 };
10637 
10638 net.user1.orbiter.RoomEvent.prototype.getClient = function () {
10639   return this.client;
10640 };
10641 
10642 net.user1.orbiter.RoomEvent.prototype.getClientID = function () {
10643   return this.clientID;
10644 };
10645 
10646 net.user1.orbiter.RoomEvent.prototype.getStatus = function () {
10647   return this.status;
10648 };
10649 
10650 net.user1.orbiter.RoomEvent.prototype.getNumClients = function () {
10651   return this.numClients;
10652 };
10653 
10654 net.user1.orbiter.RoomEvent.prototype.getChangedAttr = function () {
10655   return this.changedAttr;
10656 };
10657 
10658 net.user1.orbiter.RoomEvent.prototype.toString = function () {
10659   return "[object RoomEvent]";
10660 };  
10661 //==============================================================================
10662 // CLASS DECLARATION
10663 //==============================================================================
10664   /**
10665    * @private
10666    */  
10667 net.user1.orbiter.RoomList = function () {
10668   // Call superconstructor
10669   net.user1.events.EventDispatcher.call(this);
10670     
10671   this.rooms = new Array();
10672 };
10673 
10674 //==============================================================================    
10675 // INHERITANCE
10676 //============================================================================== 
10677 net.user1.utils.extend(net.user1.orbiter.RoomList, net.user1.events.EventDispatcher);  
10678 
10679 //==============================================================================    
10680 // INSTANCE METHODS
10681 //============================================================================== 
10682 net.user1.orbiter.RoomList.prototype.add = function (room) {
10683   if (!this.contains(room)) {
10684     this.rooms.push(room);
10685     this.dispatchAddItem(room);
10686     return room;
10687   } else {
10688     return null;
10689   }
10690 };
10691 
10692 net.user1.orbiter.RoomList.prototype.remove = function (room) {
10693   var index = net.user1.utils.ArrayUtil.indexOf(this.rooms, room);
10694   if (index != -1) {
10695     this.rooms.splice(index, 1)[0];
10696     this.dispatchRemoveItem(room);
10697     return room;
10698   } else {
10699     return null;
10700   }
10701 };
10702 
10703 net.user1.orbiter.RoomList.prototype.removeAll = function () {
10704   var room;
10705   for (var i = this.rooms.length; --i >= 0; ) {
10706     room = this.rooms.splice(i, 1)[0];
10707     this.dispatchRemoveItem(room);
10708   }
10709 };
10710 
10711 net.user1.orbiter.RoomList.prototype.removeByRoomID = function (roomID) {
10712   var room;
10713   for (var i = this.rooms.length; --i >= 0; ) {
10714     if (this.rooms[i].getRoomID() == roomID) {
10715       room = this.rooms.splice(i, 1)[0];
10716       this.dispatchRemoveItem(room);
10717       return room;
10718     }
10719   }
10720   return null;
10721 };
10722 
10723 net.user1.orbiter.RoomList.prototype.contains = function (room) {
10724   return net.user1.utils.ArrayUtil.indexOf(this.rooms, room) != -1;
10725 }
10726 
10727 net.user1.orbiter.RoomList.prototype.containsRoomID = function (roomID) {
10728   if (roomID == "" || roomID == null) {
10729     return false;
10730   }
10731   return this.getByRoomID(roomID) != null;
10732 }
10733 
10734 net.user1.orbiter.RoomList.prototype.getByRoomID = function (roomID) {
10735   var room;
10736   for (var i = this.rooms.length; --i >= 0;) {
10737     room = this.rooms[i];
10738     if (room.getRoomID() == roomID) {
10739       return room;
10740     }
10741   }
10742   return null;
10743 };
10744 
10745 net.user1.orbiter.RoomList.prototype.getAll = function () {
10746   return this.rooms.slice(0);
10747 };
10748 
10749 net.user1.orbiter.RoomList.prototype.length = function () {
10750   return this.rooms.length;
10751 }
10752 
10753 net.user1.orbiter.RoomList.prototype.dispatchAddItem = function (item) {
10754   this.dispatchEvent(new net.user1.orbiter.CollectionEvent(net.user1.orbiter.CollectionEvent.ADD_ITEM, item));
10755 };
10756 
10757 net.user1.orbiter.RoomList.prototype.dispatchRemoveItem = function (item) {
10758   this.dispatchEvent(new net.user1.orbiter.CollectionEvent(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, item));
10759 };
10760 //==============================================================================
10761 // CLASS DECLARATION
10762 //==============================================================================
10763 /** 
10764  * @class
10765  * @extends net.user1.orbiter.snapshot.Snapshot
10766  */
10767 net.user1.orbiter.snapshot.RoomListSnapshot = function (qualifier, 
10768                                                         recursive) {
10769   // Call superconstructor
10770   net.user1.orbiter.snapshot.Snapshot.call(this);
10771   this.roomList = null;
10772   this.qualifier = null;
10773   this.recursive = null;
10774   this.method = net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT;
10775   this.args   = [qualifier,
10776                  recursive];
10777 };
10778     
10779 //==============================================================================
10780 // INHERITANCE
10781 //==============================================================================
10782 net.user1.utils.extend(net.user1.orbiter.snapshot.RoomListSnapshot, net.user1.orbiter.snapshot.Snapshot);
10783 
10784 //==============================================================================
10785 // INSTANCE METHODS
10786 //==============================================================================                
10787 /**
10788  * @private
10789  */    
10790 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setRoomList = function (value) {
10791   this.roomList = value;
10792 };
10793 
10794 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getRoomList = function () {
10795   if (!this.roomList) {
10796     return null;
10797   }
10798   return this.roomList.slice();
10799 };
10800     
10801 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getQualifier = function () {
10802   return this.qualifier;
10803 };
10804         
10805     /**
10806      * @private
10807      */        
10808 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setQualifier = function (value) {
10809   this.qualifier = value;
10810 };
10811     
10812 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.getRecursive = function () {
10813   return this.recursive;
10814 };
10815         
10816 /**
10817  * @private
10818  */    
10819 net.user1.orbiter.snapshot.RoomListSnapshot.prototype.setRecursive = function (value) {
10820   this.recursive = value;
10821 };
10822 //==============================================================================
10823 // CLASS DECLARATION
10824 //==============================================================================
10825 /** @class
10826 
10827 The RoomManager class dispatches the following events:
10828 
10829 <ul class="summary">
10830 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT}</li>   
10831 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT}</li>   
10832 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT}</li>   
10833 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT}</li>   
10834 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_ADDED}</li>   
10835 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED}</li>   
10836 <li class="fixedFont">{@link net.user1.orbiter.RoomManagerEvent.ROOM_COUNT}</li>   
10837 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.JOIN_RESULT}</li>   
10838 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.LEAVE_RESULT}</li>   
10839 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.OBSERVE_RESULT}</li>   
10840 <li class="fixedFont">{@link net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT}</li>   
10841 </ul>
10842 
10843 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
10844 
10845     @extends net.user1.events.EventDispatcher
10846 */
10847 net.user1.orbiter.RoomManager = function (orbiter) {
10848   // Call superconstructor
10849   net.user1.events.EventDispatcher.call(this);
10850     
10851   this.watchedQualifiers = [];
10852   
10853   this.cachedRooms = new net.user1.orbiter.RoomList();
10854   this.occupiedRooms = new net.user1.orbiter.RoomList();
10855   this.observedRooms = new net.user1.orbiter.RoomList();
10856   this.watchedRooms = new net.user1.orbiter.RoomList();
10857   
10858   this.cachedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10859   this.occupiedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10860   this.occupiedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10861   this.observedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10862   this.observedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10863   this.watchedRooms.addEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
10864   this.watchedRooms.addEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
10865   
10866   this.orbiter = orbiter;
10867 
10868   this.addEventListener(net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
10869                         this.watchForRoomsResultListener, this);
10870   this.addEventListener(net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
10871                         this.stopWatchingForRoomsResultListener, this);
10872                                                  
10873 
10874   this.roomClassRegistry = new net.user1.orbiter.RoomClassRegistry();
10875                              
10876   // Store a reference to the this.log.
10877   this.log = this.orbiter.getLog();
10878 };
10879 
10880 //==============================================================================    
10881 // INHERITANCE
10882 //============================================================================== 
10883 net.user1.utils.extend(net.user1.orbiter.RoomManager, net.user1.events.EventDispatcher);    
10884 
10885 //==============================================================================
10886 // ROOM CREATION AND REMOVAL
10887 //==============================================================================
10888 /**
10889 * @param attributes An array of JavaScript objects that describes the initial room 
10890 * attributes for the room in the following format (note that this format differs
10891 * from the XML format used for createRoom() by Reactor, Union's Flash client framework):
10892 * 
10893 * [
10894 *   attribute: {
10895 *     name:"attrName1",
10896 *     value:"attrValue1",
10897 *     shared:true,
10898 *     persistent:false,
10899 *     immutable:false
10900 *   },
10901 *   attribute: {
10902 *     name:"attrName2",
10903 *     value:"attrValue2",
10904 *     shared:true,
10905 *     persistent:false,
10906 *     immutable:false
10907 *   }
10908 * ]
10909 * </listing>
10910 */
10911 
10912 net.user1.orbiter.RoomManager.prototype.createRoom = function (roomID, 
10913                                                                roomSettings,
10914                                                                attributes,
10915                                                                modules) {
10916   // GET ROOM SETTINGS
10917   if (roomSettings == null) {
10918     roomSettings = new net.user1.orbiter.RoomSettings();
10919   }
10920   
10921   // GET ROOM MODULES
10922   if (modules == null) {
10923     modules = new net.user1.orbiter.RoomModules();
10924   }
10925 
10926   // ERROR CHECKING
10927   
10928   // Abort if invalid module name found.
10929   var moduleIDs = modules.getIdentifiers();
10930   var moduleID;
10931   for (var i = moduleIDs.length; --i >= 0;) {
10932     var moduleID = moduleIDs[i];
10933     if (!net.user1.orbiter.Validator.isValidModuleName(moduleID)) {
10934       throw new Error("[ROOM_MANAGER] createRoom() failed. Illegal room module name: ["
10935                       + moduleID + "]. See net.user1.orbiter.Validator.isValidModuleName().");
10936     }
10937   }
10938 
10939   // If a roomID is specified, we must validated it
10940   if (roomID != null) {
10941     // Abort if the supplied id can't be resolved to a single room
10942     if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
10943       throw new Error("[ROOM_MANAGER] createRoom() failed. Illegal room id: ["
10944                 + roomID + "]. See net.user1.orbiter.Validator.isValidResolvedRoomID().");
10945     }
10946   }
10947 
10948   // MAKE THE ROOM LOCALLY
10949   
10950   // Send "" as the roomID if no roomID is specified. When the server
10951   // receives a request to create a roomID of "", it auto-generates
10952   // the id, and returns it via RoomManagerEvent.CREATE_ROOM_RESULT.
10953   if (roomID == null) {
10954     // Don't make the local room. Instead wait for the server to
10955     // report the new room via u39.
10956     roomID = "";
10957   } else {
10958     // Make the local room.
10959     this.addCachedRoom(roomID);
10960   }
10961 
10962   // TELL THE SERVER TO MAKE THE ROOM
10963 
10964   // Create attributes
10965   if (attributes != null) {
10966     var attr;
10967     var attrArg = "";
10968     for (var i = 0; i < attributes.length; i++) {
10969       attr = attributes[i];
10970       attrSettings = 0;
10971       attrSettings |= attr.shared ? AttributeOptions.FLAG_SHARED : 0;
10972       attrSettings |= attr.persistent ? AttributeOptions.FLAG_PERSISTENT : 0;
10973       attrSettings |= attr.immutable ? AttributeOptions.FLAG_IMMUTABLE : 0;
10974       attrArg += attr.NAME 
10975               + net.user1.orbiter.Tokens.RS + attr.VALUE
10976               + net.user1.orbiter.Tokens.RS + attrSettings.toString();
10977               
10978       if (i < attributes.length-1) {
10979         attrArg += Tokens.RS;
10980       }
10981     }
10982   }
10983 
10984   // Send the create room request to the server.
10985   var msgMan = this.orbiter.getMessageManager();
10986   msgMan.sendUPC(net.user1.orbiter.UPC.CREATE_ROOM, 
10987                  roomID, 
10988                  roomSettings.serialize(), 
10989                  attrArg, 
10990                  modules.serialize());
10991 
10992   // RETURN A REFERENCE TO THE LOCAL ROOM, IF ONE WAS CREATED
10993   if (roomID == "") {
10994     return null;
10995   } else {
10996     return this.getRoom(roomID);
10997   }
10998 };
10999 
11000 net.user1.orbiter.RoomManager.prototype.removeRoom = function (roomID, password) {
11001   // Quit if no room specified.
11002   if (roomID == null || !net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
11003     throw new Error("Invalid room id supplied to removeRoom(): ["
11004                     + roomID + "]. Request not sent.");
11005   }
11006   
11007   if (password == null) {
11008     password = "";
11009   }
11010 
11011   var msgMan = this.orbiter.getMessageManager();  
11012   msgMan.sendUPC(net.user1.orbiter.UPC.REMOVE_ROOM,
11013                  roomID,
11014                  password);
11015 };
11016 
11017 //==============================================================================
11018 // ROOM OBSERVATION
11019 //==============================================================================
11020 
11021 net.user1.orbiter.RoomManager.prototype.observeRoom = function (roomID,
11022                                                                 password,
11023                                                                 updateLevels) {
11024   var theRoom;
11025   
11026   // If the room is not valid, quit
11027   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
11028     throw new Error("Invalid room id supplied to observeRoom(): ["
11029               + roomID + "]. Request not sent."
11030               + " See net.user1.orbiter.Validator.isValidResolvedRoomID().");
11031   }
11032 
11033   // Try to get a reference to the room
11034   theRoom = this.getRoom(roomID);
11035     
11036   // If the room exists
11037   if (theRoom != null) {
11038     if (theRoom.clientIsObservingRoom()) {
11039       this.log.warn("[ROOM_MANAGER] Room observe attempt ignored. Already observing room: '" 
11040                + roomID + "'.");
11041       return null;
11042     } 
11043   } else {
11044     // Make the local room
11045     theRoom = this.addCachedRoom(roomID);
11046   }
11047     
11048   // Validate the password
11049   if (password == null) {
11050     password = "";
11051   }
11052   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
11053     throw new Error("Invalid room password supplied to observeRoom(). "
11054               + " Room ID: [" + roomID + "], password: [" + password + "]." 
11055               + " See net.user1.orbiter.Validator.isValidPassword().");
11056   }
11057 
11058     // If update levels were specified for this room, send them now.
11059   if (updateLevels != null) {
11060     theRoom.setUpdateLevels(updateLevels);
11061   }
11062 
11063   // Send the UPC only if at least one valid room was found      
11064   var msgMan = this.orbiter.getMessageManager();  
11065   msgMan.sendUPC(net.user1.orbiter.UPC.OBSERVE_ROOM, 
11066                  roomID,
11067                  password);
11068   
11069   return theRoom; 
11070 };
11071 
11072 //==============================================================================
11073 // WATCHING FOR ROOMS
11074 //==============================================================================
11075 
11076 net.user1.orbiter.RoomManager.prototype.watchForRooms = function (roomQualifier) {
11077   var recursive = false;
11078   
11079   // null means watch the whole server
11080   if (roomQualifier == null) {
11081     roomQualifier = "";
11082     recursive = true;
11083   }
11084 
11085   var msgMan = this.orbiter.getMessageManager();
11086   msgMan.sendUPC(net.user1.orbiter.UPC.WATCH_FOR_ROOMS,
11087                  roomQualifier,
11088                  recursive.toString());
11089 };
11090 
11091 net.user1.orbiter.RoomManager.prototype.stopWatchingForRooms = function (roomQualifier) {
11092   var recursive = false;
11093   // null means whole server
11094   if (roomQualifier == null) {
11095     roomQualifier = "";
11096     recursive = true;
11097   }
11098 
11099   var msgMan = this.orbiter.getMessageManager();
11100   msgMan.sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS, 
11101                  roomQualifier,
11102                  recursive.toString());
11103 };
11104 
11105 net.user1.orbiter.RoomManager.prototype.isWatchingQualifier = function (qualifier) {
11106   return net.user1.utils.ArrayUtil.indexOf(this.watchedQualifiers, qualifier) != -1;
11107 };
11108 
11109 /**
11110  * @private
11111  */
11112 net.user1.orbiter.RoomManager.prototype.watchForRoomsResultListener = function (e) {
11113   if (e.getStatus() == net.user1.orbiter.Status.SUCCESS) {
11114     this.watchedQualifiers.push(e.getRoomIdQualifier());
11115   }
11116 };
11117 
11118 /**
11119  * @private
11120  */
11121 net.user1.orbiter.RoomManager.prototype.stopWatchingForRoomsResultListener = function (e) {
11122   var unwatchedQualifier = e.getRoomIdQualifier();
11123   var unwatchedQualifierIndex;
11124   
11125   if (e.getStatus() == net.user1.orbiter.Status.SUCCESS) {
11126     unwatchedQualifierIndex = net.user1.utils.ArrayUtil.indexOf(watchedQualifiers, unwatchedQualifier);
11127     if (unwatchedQualifierIndex != -1) {
11128       watchedQualifiers.splice(unwatchedQualifierIndex, 1);
11129     }
11130   }
11131 };
11132 
11133 //==============================================================================
11134 // SENDING MESSAGES
11135 //==============================================================================
11136 
11137 net.user1.orbiter.RoomManager.prototype.sendMessage = function (messageName, 
11138                                                                 rooms,
11139                                                                 includeSelf,
11140                                                                 filters) {
11141   var rest = Array.prototype.slice.call(arguments).slice(4);
11142   
11143   // An array of arguments to send to the server.
11144   var args;
11145 
11146   // Can't continue without a valid messageName.
11147   if (messageName == null || messageName == "") {
11148     this.log.warn("[ROOM_MANAGER]  sendMessage() failed. No messageName supplied.");
11149     return;
11150   }
11151   
11152   // Send the UPC.
11153   var msgMan = this.orbiter.getMessageManager();
11154   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS, 
11155           messageName, 
11156           rooms.join(net.user1.orbiter.Tokens.RS),
11157           String(includeSelf),
11158           filters != null ? filters.toXMLString() : ""];
11159   msgMan.sendUPC.apply(msgMan, args.concat(rest));
11160 }
11161 
11162 
11163 //==============================================================================
11164 // JOINING ROOMS
11165 //==============================================================================
11166 
11167 net.user1.orbiter.RoomManager.prototype.joinRoom = function (roomID, 
11168                                                              password,
11169                                                              updateLevels) {
11170   if (!this.orbiter.isReady()) { 
11171     this.log.warn("[ROOM_MANAGER] Connection not open. Request to join room ["
11172               + roomID + "] could not be sent.");
11173     return null;
11174   }
11175   
11176   // If the room ID is not valid, quit
11177   if (!net.user1.orbiter.Validator.isValidResolvedRoomID(roomID)) {
11178     this.log.error("[ROOM_MANAGER] Invalid room id supplied to joinRoom(): ["
11179                    + roomID + "]. Join request not sent."
11180                    + " See net.user1.orbiter.Validator.isValidResolvedRoomID().");
11181     return null;
11182   }
11183   
11184   // Try to get a reference to the room
11185   var theRoom = this.getRoom(roomID);
11186     
11187   // If the room exists
11188   if (theRoom != null) {
11189     // Can't join a room you're already in.
11190     if (theRoom.clientIsInRoom()) {
11191       this.log.warn("[ROOM_MANAGER] Room join attempt aborted. Already in room: [" 
11192                     + theRoom.getRoomID() + "].");
11193       return theRoom;
11194     }
11195   } else {
11196     // Make the local room.
11197     theRoom = this.addCachedRoom(roomID);
11198   }
11199     
11200   // Validate the password
11201   if (password == null) {
11202     password = "";
11203   }
11204   if (!net.user1.orbiter.Validator.isValidPassword(password)) {
11205     this.log.error("[ROOM_MANAGER] Invalid room password supplied to joinRoom(): ["
11206                    + roomID + "]. Join request not sent." 
11207                    + " See net.user1.orbiter.Validator.isValidPassword().");
11208     return theRoom;
11209   }
11210 
11211   
11212   // If any update levels are specified, send them before joining.
11213   if (updateLevels != null) {
11214     theRoom.setUpdateLevels(updateLevels);
11215   }
11216   
11217   var msgMan = this.orbiter.getMessageManager();
11218   msgMan.sendUPC(net.user1.orbiter.UPC.JOIN_ROOM, 
11219                  roomID, 
11220                  password);
11221   return theRoom;
11222 };
11223 
11224 // =============================================================================
11225 // ROOM OBJECT CREATION/DISPOSAL
11226 // =============================================================================
11227 
11228 /**
11229  * @private
11230  */
11231 net.user1.orbiter.RoomManager.prototype.requestRoom = function (roomID) {
11232   if (roomID == "") {
11233     this.log.warn("[ROOM_MANAGER] requestRoom() failed. Supplied room ID was empty.");
11234     return null;
11235   }
11236   
11237   var theRoom = this.getRoom(roomID);
11238   if (theRoom != null) {
11239     return theRoom;
11240   } else {
11241     this.log.debug("[ROOM_MANAGER] Creating new room object for id: [" + roomID + "]");
11242     var RoomClass = this.roomClassRegistry.getRoomClass(roomID);
11243     theRoom = new RoomClass(roomID, 
11244                             this, 
11245                             this.orbiter.getMessageManager(), 
11246                             this.orbiter.getClientManager(), 
11247                             this.orbiter.getAccountManager(), 
11248                             this.log);
11249     return theRoom;
11250   }
11251 };
11252 
11253 /**
11254  * @private
11255  */
11256 net.user1.orbiter.RoomManager.prototype.disposeRoom = function (roomID) {
11257   var room = this.getRoom(roomID);
11258   if (room != null) {
11259     this.log.debug("[ROOM_MANAGER] Disposing room: " + room);
11260     this.removeCachedRoom(roomID);
11261     this.removeWatchedRoom(roomID);
11262     this.removeOccupiedRoom(roomID);
11263     this.removeObservedRoom(roomID);
11264   } else {
11265     this.log.debug("[ROOM_MANAGER] disposeRoom() called for unknown room: [" + roomID + "]");
11266   }
11267 };
11268 
11269 /**
11270  * @private
11271  */    
11272 net.user1.orbiter.RoomManager.prototype.removeAllRooms = function () {
11273   this.log.debug("[ROOM_MANAGER] Removing all local room object references.");
11274   this.cachedRooms.removeAll();
11275   this.watchedRooms.removeAll();
11276   this.occupiedRooms.removeAll();
11277   this.observedRooms.removeAll();
11278 };
11279 
11280 // =============================================================================
11281 // CACHED ROOMS
11282 // =============================================================================
11283 
11284 /**
11285  * @private
11286  */    
11287 net.user1.orbiter.RoomManager.prototype.addCachedRoom = function (roomID) {
11288   var cachedRoom = this.cachedRooms.getByRoomID(roomID);
11289   if (cachedRoom == null) {
11290     this.log.debug("[ROOM_MANAGER] Adding cached room: [" + roomID + "]"); 
11291     return this.cachedRooms.add(this.requestRoom(roomID));
11292   } else {
11293     return cachedRoom;
11294   }
11295 };
11296 
11297 /**
11298  * @private
11299  */    
11300 net.user1.orbiter.RoomManager.prototype.removeCachedRoom = function (roomID) {
11301   if (this.cachedRooms.containsRoomID(roomID)) {
11302     this.cachedRooms.removeByRoomID(roomID);
11303   } else {
11304     throw new Error("[ROOM_MANAGER] Could not remove cached room: [" + roomID + "]." 
11305                     + " Room not found.");
11306   }
11307 };
11308 
11309 net.user1.orbiter.RoomManager.prototype.hasCachedRoom = function (roomID) {
11310   return this.cachedRooms.containsRoomID(roomID);
11311 };
11312 
11313 net.user1.orbiter.RoomManager.prototype.disposeCachedRooms = function () {
11314   var room;
11315   var rooms = cachedRooms.getAll();
11316   for (var i = 0; i <= rooms.length; i++) {
11317     room = rooms[i];
11318     removeCachedRoom(room.getRoomID());
11319   }
11320 };
11321 
11322 // =============================================================================
11323 // WATCHED ROOMS
11324 // =============================================================================
11325   
11326 /**
11327  * @private
11328  */    
11329 net.user1.orbiter.RoomManager.prototype.addWatchedRoom = function (roomID) {
11330   this.log.debug("[ROOM_MANAGER] Adding watched room: [" + roomID + "]"); 
11331   var room = this.watchedRooms.add(this.requestRoom(roomID));
11332   room.updateSyncState();
11333 };
11334   
11335 /**
11336  * @private
11337  */    
11338 net.user1.orbiter.RoomManager.prototype.removeWatchedRoom = function (roomID) {
11339   var room = this.watchedRooms.removeByRoomID(roomID);
11340   if (room != null) {
11341     room.updateSyncState();
11342   } else {
11343     this.log.debug("[ROOM_MANAGER] Request to remove watched room [" 
11344               + roomID + "] ignored; room not in watched list.");
11345   }
11346 };
11347   
11348 /**
11349  * @private
11350  */    
11351 net.user1.orbiter.RoomManager.prototype.removeAllWatchedRooms = function () {
11352   var rooms = this.watchedRooms.getAll();
11353   var room;
11354   for (var i = 0; i <= rooms.length; i++) {
11355     room = rooms[i];
11356     removeWatchedRoom(room.getRoomID());
11357     room.updateSyncState();
11358   }
11359 };
11360   
11361 /**
11362  * @private
11363  */
11364 net.user1.orbiter.RoomManager.prototype.setWatchedRooms = function (qualifier, newRoomIDs) {
11365   // Remove rooms from local list
11366   var rooms = this.getRoomsWithQualifier(qualifier);
11367   var room;
11368   for (var i = 0; i < rooms.length; i++) {
11369     room = rooms[i];
11370     if (net.user1.utils.ArrayUtil.indexOf(newRoomIDs, room.getSimpleRoomID()) == -1) {
11371       this.removeWatchedRoom(room.getRoomID());
11372     }
11373   }
11374   // Add rooms to local list
11375   var fullRoomID;
11376   var roomID;
11377   for (var i = 0; i < newRoomIDs.length; i++) {
11378     roomID = newRoomIDs[i];
11379     fullRoomID = qualifier + (qualifier != "" ? "." : "") + roomID;
11380     if (!this.watchedRooms.containsRoomID(fullRoomID)) {
11381       this.addWatchedRoom(fullRoomID);
11382     }
11383   }
11384 };
11385 
11386 net.user1.orbiter.RoomManager.prototype.hasWatchedRoom = function (roomID) {
11387   return this.watchedRooms.containsRoomID(roomID);
11388 }
11389 
11390 // =============================================================================
11391 // OCCUPIED ROOMS
11392 // =============================================================================
11393   
11394 /**
11395  * @private
11396  */    
11397 net.user1.orbiter.RoomManager.prototype.addOccupiedRoom = function (roomID) {
11398   this.log.debug("[ROOM_MANAGER] Adding occupied room: [" + roomID + "]"); 
11399   var room = this.occupiedRooms.add(this.requestRoom(roomID));
11400   room.updateSyncState();
11401   return room;
11402 };
11403   
11404 /**
11405  * @private
11406  */    
11407 net.user1.orbiter.RoomManager.prototype.removeOccupiedRoom = function (roomID) {
11408   var room = this.occupiedRooms.removeByRoomID(roomID);
11409   if (room != null) {
11410     room.updateSyncState();
11411   } else {
11412     this.log.debug("[ROOM_MANAGER] Request to remove occupied room [" 
11413               + roomID + "] ignored; client is not in room."); 
11414   }
11415 };
11416 
11417 net.user1.orbiter.RoomManager.prototype.hasOccupiedRoom = function (roomID) {
11418   return this.occupiedRooms.containsRoomID(roomID);
11419 };
11420   
11421 // =============================================================================
11422 // OBSERVED ROOMS
11423 // =============================================================================
11424   
11425 /**
11426  * @private
11427  */    
11428 net.user1.orbiter.RoomManager.prototype.addObservedRoom = function (roomID) {
11429   this.log.debug("[ROOM_MANAGER] Adding observed room: [" + roomID + "]");
11430   var room = this.observedRooms.add(this.requestRoom(roomID));
11431   room.updateSyncState();
11432   return room;
11433 };
11434   
11435 /**
11436  * @private
11437  */    
11438 net.user1.orbiter.RoomManager.prototype.removeObservedRoom = function (roomID) {
11439   var room = this.observedRooms.removeByRoomID(roomID);
11440   if (room != null) {
11441     room.updateSyncState();
11442   } else {
11443     this.log.debug("[ROOM_MANAGER] Request to remove observed room [" 
11444               + roomID + "] ignored; client is not observing room."); 
11445   }
11446 };
11447 
11448 net.user1.orbiter.RoomManager.prototype.hasObservedRoom = function (roomID) {
11449   return this.observedRooms.containsRoomID(roomID);
11450 };
11451 
11452 //==============================================================================
11453 // ROOM LIST LISTENERS
11454 //==============================================================================
11455 
11456 /**
11457  * @private
11458  */    
11459 net.user1.orbiter.RoomManager.prototype.addRoomListener = function (e) {
11460   var room = e.getItem();
11461   
11462   // Only trigger added for first known reference
11463   if (this.getKnownReferenceCount(room.getRoomID()) == 1) {
11464     this.fireRoomAdded(room.getQualifier(), room.getRoomID(), room);
11465     this.fireRoomCount(this.getNumRooms());
11466   }
11467 };
11468 
11469 /**
11470  * @private
11471  */    
11472 net.user1.orbiter.RoomManager.prototype.removeRoomListener = function (e) {
11473   var room = e.getItem();
11474   var knownReferenceCount = this.getKnownReferenceCount(room.getRoomID());
11475   
11476   switch (e.target) {
11477     case this.occupiedRooms:
11478       this.log.debug("[ROOM_MANAGER] Removed occupied room: " + room);
11479       if (knownReferenceCount == 0) {
11480         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11481         this.fireRoomCount(this.getNumRooms());
11482       }
11483       break;
11484     
11485     case this.observedRooms:
11486       this.log.debug("[ROOM_MANAGER] Removed observed room: " + room); 
11487       if (knownReferenceCount == 0) {
11488         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11489         this.fireRoomCount(this.getNumRooms());
11490       }
11491       break;
11492     
11493     case this.watchedRooms:
11494       this.log.debug("[ROOM_MANAGER] Removed watched room: " + room); 
11495       if (knownReferenceCount == 0) {
11496         this.fireRoomRemoved(room.getQualifier(), room.getRoomID(), room);
11497         this.fireRoomCount(this.getNumRooms());
11498       }
11499       break;
11500     
11501     case this.cachedRooms:
11502       this.log.debug("[ROOM_MANAGER] Removed cached room: " + room); 
11503       break;
11504   }
11505   
11506   // When the RoomManager has no more references to the room, shut it down
11507   if (knownReferenceCount == 0 && !this.cachedRooms.contains(room)) {
11508     room.shutdown();
11509   }
11510 };
11511 
11512 //==============================================================================
11513 // ROOM ACCESS
11514 //==============================================================================
11515 
11516 /**
11517  * @private 
11518  */    
11519 net.user1.orbiter.RoomManager.prototype.getKnownReferenceCount = function (roomID) {
11520   var count = 0;
11521   count += this.hasObservedRoom(roomID) ? 1 : 0;
11522   count += this.hasOccupiedRoom(roomID) ? 1 : 0;
11523   count += this.hasWatchedRoom(roomID) ? 1 : 0;
11524   return count;
11525 }
11526 
11527 net.user1.orbiter.RoomManager.prototype.getRooms = function () {
11528   var roomlist = net.user1.utils.ArrayUtil.combine(this.occupiedRooms.getAll(),
11529                                                    this.observedRooms.getAll(),
11530                                                    this.watchedRooms.getAll());
11531   return roomlist;
11532 };
11533 
11534 net.user1.orbiter.RoomManager.prototype.roomIsKnown = function (roomID) {
11535   var rooms = this.getRooms();
11536   var room;
11537   for (var i = rooms.length; --i >= 0;) {
11538     room = rooms[i];
11539     if (room.getRoomID() == roomID) {
11540       return true;
11541     }
11542   }
11543   return false;
11544 };
11545 
11546 net.user1.orbiter.RoomManager.prototype.getRoomIDs = function () {
11547   var roomIDs = new Array();
11548   var rooms = this.getRooms();
11549 
11550   for (var i = 0; i <= rooms.length; i++) {
11551     roomIDs.push(rooms[i].getRoomID());
11552   }
11553 
11554   return roomIDs;
11555 };
11556 
11557 net.user1.orbiter.RoomManager.prototype.getAllRooms = function () {
11558   var roomlist = net.user1.utils.ArrayUtil.combine(this.occupiedRooms.getAll(),
11559                                                    this.observedRooms.getAll(),
11560                                                    this.watchedRooms.getAll(),
11561                                                    this.cachedRooms.getAll());
11562   
11563   return roomlist;
11564 };
11565 
11566 net.user1.orbiter.RoomManager.prototype.getRoomsWithQualifier = function (qualifier) {
11567   if (qualifier == null)  {
11568     return this.getRooms();
11569   }
11570   
11571   var roomlist = [];
11572   var rooms = this.getRooms();
11573   var room;
11574   for (var i = 0; i < rooms.length; i++) {
11575     room = rooms[i];
11576     if (net.user1.orbiter.RoomIDParser.getQualifier(room.getRoomID()) == qualifier) {
11577       roomlist.push(room);
11578     }
11579   }
11580   
11581   return roomlist;
11582 };
11583 
11584 net.user1.orbiter.RoomManager.prototype.getNumRooms = function (qualifier) {
11585   return this.getRoomsWithQualifier(qualifier).length;
11586 }
11587 
11588 net.user1.orbiter.RoomManager.prototype.getRoom = function (roomID) {
11589   var rooms = this.getAllRooms();
11590   var room;
11591   for (var i = rooms.length; --i >= 0;) {
11592     room = rooms[i];
11593     if (room.getRoomID() == roomID) {
11594       return room;
11595     }
11596   }
11597   return null;
11598 };
11599 
11600 // =============================================================================
11601 // ROOM CLASS REGISTRY
11602 // =============================================================================
11603 
11604 net.user1.orbiter.RoomManager.prototype.getRoomClassRegistry = function () {
11605   return this.roomClassRegistry;
11606 };
11607 
11608 // =============================================================================
11609 // CLIENT ACCESS
11610 // =============================================================================
11611 
11612 /**
11613  * @private
11614  */        
11615 net.user1.orbiter.RoomManager.prototype.getAllClients = function () {
11616   var clientSets = [];
11617   var rooms = this.getRooms();
11618   var room;
11619   for (var i = rooms.length; --i >= 0;) {
11620     room = rooms[i];
11621     clientSets.push(room.getOccupantsInternal());
11622     clientSets.push(room.getObserversInternal());
11623   }
11624   return net.user1.utils.ObjectUtil.combine(clientSets);
11625 };
11626 
11627 net.user1.orbiter.RoomManager.prototype.clientIsKnown = function (clientID) {
11628   var clientSets = [];
11629   
11630   var rooms = this.getRooms();
11631   var room;
11632   for (var i = rooms.length; --i >= 0;) {
11633     room = rooms[i];
11634     clientSets.push(room.getOccupantsInternal());
11635     clientSets.push(room.getObserversInternal());
11636   }
11637   
11638   for (var i = clientSets.length; --i >= 0;) {
11639     if (clientSets[i][clientID] != null) {
11640       return true;
11641     }
11642   }
11643   return false;
11644 };
11645 
11646 // =============================================================================
11647 // EVENT DISPATCHING
11648 // =============================================================================
11649 
11650 /**
11651  * @private
11652  */
11653 net.user1.orbiter.RoomManager.prototype.fireWatchForRoomsResult = function (roomIDQualifier,
11654                                                                             status) {
11655   var e = new net.user1.orbiter.RoomManagerEvent(
11656                               net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT,
11657                               null, status, roomIDQualifier);
11658   this.dispatchEvent(e);  
11659 };
11660 
11661 /**
11662  * @private
11663  */
11664 net.user1.orbiter.RoomManager.prototype.fireStopWatchingForRoomsResult = function (roomIDQualifier,
11665                                                                                    status) {
11666   var e = new net.user1.orbiter.RoomManagerEvent(
11667                             net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT,
11668                             null, status, roomIDQualifier);
11669   this.dispatchEvent(e);  
11670 };
11671 
11672 /**
11673  * @private
11674  */
11675 net.user1.orbiter.RoomManager.prototype.fireCreateRoomResult = function (roomIDQualifier, 
11676                                                                          roomID,
11677                                                                          status) {
11678   var e = new net.user1.orbiter.RoomManagerEvent(
11679                                        net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT,
11680                                        roomID, status, roomIDQualifier);
11681   this.dispatchEvent(e);
11682 };
11683 
11684 /**
11685  * @private
11686  */
11687 net.user1.orbiter.RoomManager.prototype.fireRemoveRoomResult = function (roomIDQualifier, 
11688                                                                          roomID,
11689                                                                          status) {
11690   var e = new net.user1.orbiter.RoomManagerEvent(
11691                                        net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT, 
11692                                        roomID, status, roomIDQualifier);
11693 
11694   this.dispatchEvent(e);
11695 }
11696 
11697 /**
11698  * @private
11699  */
11700 net.user1.orbiter.RoomManager.prototype.fireRoomAdded = function (roomIDQualifier, 
11701                                                                   roomID,
11702                                                                   theRoom) {
11703   var e = new net.user1.orbiter.RoomManagerEvent(
11704                                      net.user1.orbiter.RoomManagerEvent.ROOM_ADDED, 
11705                                      roomID, null, roomIDQualifier, theRoom);
11706   this.dispatchEvent(e);
11707 }
11708 
11709 /**
11710  * @private
11711  */
11712 net.user1.orbiter.RoomManager.prototype.fireRoomRemoved = function (roomIDQualifier, 
11713                                                                     roomID,
11714                                                                     theRoom) {
11715   var e = new net.user1.orbiter.RoomManagerEvent(
11716                                      net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED, 
11717                                      roomID, 
11718                                      null,
11719                                      roomIDQualifier,
11720                                      theRoom);
11721   this.dispatchEvent(e);
11722 }
11723 
11724 /**
11725  * @private
11726  */
11727 net.user1.orbiter.RoomManager.prototype.fireRoomCount = function (numRooms) {
11728   this.dispatchEvent(new net.user1.orbiter.RoomManagerEvent(net.user1.orbiter.RoomManagerEvent.ROOM_COUNT, 
11729                                                             null, null, null, null, numRooms));
11730 };
11731 
11732 /**
11733  * @private
11734  */
11735 net.user1.orbiter.RoomManager.prototype.fireJoinRoomResult = function (roomID, status) {
11736   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.JOIN_RESULT, null, null, 
11737                                                      status, null, 0, roomID));
11738 }
11739 
11740 /**
11741  * @private
11742  */
11743 net.user1.orbiter.RoomManager.prototype.fireLeaveRoomResult = function (roomID, status) {
11744   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.LEAVE_RESULT, null, null, 
11745                                                      status, null, 0, roomID));
11746 };
11747 
11748 /**
11749  * @private
11750  */
11751 net.user1.orbiter.RoomManager.prototype.fireObserveRoomResult = function (roomID, status) {
11752   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.OBSERVE_RESULT, null, null, 
11753                                                      status, null, 0, roomID));
11754 };
11755 
11756 /**
11757  * @private
11758  */
11759 net.user1.orbiter.RoomManager.prototype.fireStopObservingRoomResult = function (roomID, status) {
11760   this.dispatchEvent(new net.user1.orbiter.RoomEvent(net.user1.orbiter.RoomEvent.STOP_OBSERVING_RESULT, null, null, 
11761                                                      status, null, 0, roomID));
11762 };
11763 
11764 
11765 // =============================================================================
11766 // CLEANUP and DISPOSAL
11767 // =============================================================================
11768 
11769 /**
11770  * @private
11771  * 
11772  * Clears all resources. The object remains alive, and can be reused. To 
11773  * permanently deactivate this object, use dispose().
11774  */
11775 net.user1.orbiter.RoomManager.prototype.cleanup = function () {
11776   this.log.info("[ROOM_MANAGER] Cleaning resources.");
11777   this.removeAllRooms();
11778   this.watchedQualifiers = [];
11779 };
11780 
11781 /**
11782  * @private
11783  */
11784 net.user1.orbiter.RoomManager.prototype.dispose = function () {
11785   this.log.info("[ROOM_MANAGER] Disposing resources.");
11786   this.watchedQualifiers = null;
11787   var room;
11788   var rooms = this.getAllRooms();
11789   for (var i = this.getAllRooms().length; --i >= 0;) {
11790     room = rooms[i];
11791     room.dispose();
11792   }
11793   this.cachedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11794   this.occupiedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11795   this.occupiedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11796   this.observedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11797   this.observedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11798   this.watchedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.ADD_ITEM, this.addRoomListener, this);
11799   this.watchedRooms.removeEventListener(net.user1.orbiter.CollectionEvent.REMOVE_ITEM, this.removeRoomListener, this);
11800   this.occupiedRooms = null;
11801   this.observedRooms = null;
11802   this.watchedRooms  = null;
11803   this.cachedRooms  = null;
11804   this.log = null;
11805   this.orbiter = null;
11806   this.roomClassRegistry = null;
11807 };
11808 //==============================================================================
11809 // CLASS DECLARATION
11810 //==============================================================================
11811 /** @class
11812     @extends net.user1.events.Event
11813 */
11814 net.user1.orbiter.RoomManagerEvent = function (type,
11815                                                roomID, 
11816                                                status,
11817                                                roomIdQualifier,
11818                                                room,
11819                                                numRooms) {
11820   net.user1.events.Event.call(this, type);
11821 
11822   this.roomID = roomID;
11823   this.status = status;
11824   this.roomIdQualifier  = roomIdQualifier;
11825   this.room = room;
11826   this.numRooms = numRooms;
11827 };
11828 
11829 //==============================================================================
11830 // INHERITANCE
11831 //==============================================================================
11832 net.user1.utils.extend(net.user1.orbiter.RoomManagerEvent, net.user1.events.Event);
11833 
11834 //==============================================================================
11835 // STATIC VARIABLES
11836 //==============================================================================
11837 
11838 /** @constant */
11839 net.user1.orbiter.RoomManagerEvent.CREATE_ROOM_RESULT = "CREATE_ROOM_RESULT";
11840 /** @constant */
11841 net.user1.orbiter.RoomManagerEvent.REMOVE_ROOM_RESULT = "REMOVE_ROOM_RESULT";
11842 /** @constant */
11843 net.user1.orbiter.RoomManagerEvent.WATCH_FOR_ROOMS_RESULT = "WATCH_FOR_ROOMS_RESULT";
11844 /** @constant */
11845 net.user1.orbiter.RoomManagerEvent.STOP_WATCHING_FOR_ROOMS_RESULT = "STOP_WATCHING_FOR_ROOMS_RESULT";
11846 /** @constant */
11847 net.user1.orbiter.RoomManagerEvent.ROOM_ADDED = "ROOM_ADDED";
11848 /** @constant */
11849 net.user1.orbiter.RoomManagerEvent.ROOM_REMOVED = "ROOM_REMOVED";
11850 /** @constant */
11851 net.user1.orbiter.RoomManagerEvent.ROOM_COUNT = "ROOM_COUNT";
11852 
11853 net.user1.orbiter.RoomManagerEvent.prototype.getRoomIdQualifier = function () {
11854   if (this.roomIdQualifier == null && this.room != null) {
11855     return this.room.getQualifier();
11856   } else {
11857     return this.roomIdQualifier;
11858   }
11859 };
11860 
11861 net.user1.orbiter.RoomManagerEvent.prototype.getRoomID = function () {
11862   var fullRoomID;
11863   var qualifier;
11864   
11865   if (this.room != null) {
11866     return this.room.getRoomID();
11867   } else if (this.roomID == null) {
11868     return null;
11869   } else {
11870     qualifier = this.getRoomIdQualifier();
11871     fullRoomID = qualifier == "" || qualifier == null 
11872                  ? this.roomID
11873                  : qualifier + "." + this.roomID;
11874     return fullRoomID;
11875   }
11876 };
11877 
11878 net.user1.orbiter.RoomManagerEvent.prototype.getSimpleRoomID = function () {
11879   if (this.roomID == null && this.room != null) {
11880     return this.room.getSimpleRoomID();
11881   } else {
11882     return this.roomID;
11883   }
11884 };
11885 
11886 net.user1.orbiter.RoomManagerEvent.prototype.getRoom = function () {
11887   return this.room;
11888 }
11889 
11890 net.user1.orbiter.RoomManagerEvent.prototype.getStatus = function () {
11891   return this.status;
11892 }
11893 
11894 net.user1.orbiter.RoomManagerEvent.prototype.getNumRooms = function () {
11895   return this.numRooms;
11896 }
11897 
11898 net.user1.orbiter.RoomManagerEvent.prototype.toString = function () {
11899   return "[object RoomManagerEvent]";
11900 };
11901 //==============================================================================
11902 // CLASS DECLARATION
11903 //==============================================================================
11904   /**
11905    * @private
11906    */  
11907 net.user1.orbiter.RoomManifest = function () {
11908 };
11909   
11910 net.user1.orbiter.RoomManifest.prototype.deserialize = function (roomID,
11911                                                                  serializedAttributes,
11912                                                                  clientList,
11913                                                                  occupantCount,
11914                                                                  observerCount) {
11915   this.roomID = roomID;
11916   this.attributes = null;
11917   this.occupantCount = occupantCount;
11918   this.observerCount = observerCount;
11919   this.occupants = [];
11920   this.observers = [];
11921   
11922   this.deserializeAttributes(serializedAttributes);
11923   this.deserializeClientList(clientList);
11924 };
11925         
11926 /**
11927  * @private
11928  */        
11929 net.user1.orbiter.RoomManifest.prototype.deserializeAttributes = function (serializedAttributes) {
11930   var attrList = serializedAttributes.split(net.user1.orbiter.Tokens.RS);
11931   this.attributes = new net.user1.orbiter.AttributeCollection();
11932   
11933   for (var i = attrList.length-2; i >= 0; i -=2) {
11934     this.attributes.setAttribute(attrList[i], attrList[i+1], net.user1.orbiter.Tokens.GLOBAL_ATTR);
11935   }
11936 };
11937     
11938 /**
11939  * @private
11940  */        
11941 net.user1.orbiter.RoomManifest.prototype.deserializeClientList = function (clientList) {
11942   var clientManifest;
11943   
11944   for (var i = clientList.length-5; i >= 0; i -=5) {
11945     clientManifest = new net.user1.orbiter.ClientManifest();
11946     clientManifest.deserialize(clientList[i], clientList[i+1], null, null, clientList[i+3], [this.roomID, clientList[i+4]]);
11947     if (clientList[i+2] == "0") {
11948       this.occupants.push(clientManifest);
11949     } else {
11950       this.observers.push(clientManifest);
11951     }
11952   }
11953 };
11954 //==============================================================================
11955 // CLASS DECLARATION
11956 //==============================================================================
11957 /** @class
11958  *
11959  * @example
11960  * var modules = new net.user1.orbiter.RoomModules();
11961  * modules.addModule("com.business.StockTickerListener", net.user1.orbiter.ModuleType.CLASS);
11962  * orbiter.getRoomManager().createRoom("someRoomID",
11963  *                           null,
11964  *                           null,
11965  *                           modules);
11966  */
11967 net.user1.orbiter.RoomModules = function () {
11968   this.modules = new Array();
11969 };
11970 
11971 net.user1.orbiter.RoomModules.prototype.addModule = function (identifier, 
11972                                                   type) {
11973   this.modules.push([type, identifier]);
11974 };
11975 
11976 net.user1.orbiter.RoomModules.prototype.serialize = function () {
11977   var modulesString = "";
11978 
11979   var numModules = this.modules.length;
11980   for (var i = 0; i < numModules; i++) {
11981     modulesString += this.modules[i][0] + net.user1.orbiter.Tokens.RS + this.modules[i][1];
11982     if (i < numModules-1) {
11983       modulesString += net.user1.orbiter.Tokens.RS;
11984     } 
11985   }
11986 
11987   return modulesString;
11988 };
11989 
11990 net.user1.orbiter.RoomModules.prototype.getIdentifiers = function () {
11991   var ids = new Array();
11992   
11993   var module;
11994   for (var i = 0; i < this.modules.length; i++) {
11995     module = this.modules[i];
11996     ids.push(module[1]);
11997   }
11998   return ids;
11999 };
12000 //==============================================================================
12001 // CLASS DECLARATION
12002 //==============================================================================
12003 /** @class */
12004 net.user1.orbiter.RoomSettings = function () {
12005   this.removeOnEmpty = null;
12006   this.maxClients = null; 
12007   this.password = null;
12008 };
12009 
12010 net.user1.orbiter.RoomSettings.prototype.serialize = function () {
12011   var RS = net.user1.orbiter.Tokens.RS;
12012   var settingsString =
12013       net.user1.orbiter.Tokens.REMOVE_ON_EMPTY_ATTR + RS + 
12014       (this.removeOnEmpty == null ? "true" : this.removeOnEmpty.toString()) 
12015       + RS + net.user1.orbiter.Tokens.MAX_CLIENTS_ATTR + RS + 
12016       (this.maxClients == null ? "-1" : this.maxClients.toString()) 
12017       + RS + net.user1.orbiter.Tokens.PASSWORD_ATTR + RS +
12018       (this.password == null ? "" : this.password);
12019   return settingsString;
12020 }
12021 //==============================================================================
12022 // CLASS DECLARATION
12023 //==============================================================================
12024 /** 
12025  * @class
12026  * @extends net.user1.orbiter.snapshot.Snapshot
12027  */
12028 net.user1.orbiter.snapshot.RoomSnapshot = function (roomID, password, updateLevels) {
12029   // Call superconstructor
12030   net.user1.orbiter.snapshot.Snapshot.call(this);
12031   this.manifest = null;
12032   this.method = net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT;
12033   this.args   = [roomID, password, updateLevels != null ? updateLevels.toInt() : ""];
12034   this.hasStatus = true;
12035 };
12036 //==============================================================================
12037 // INHERITANCE
12038 //==============================================================================
12039 net.user1.utils.extend(net.user1.orbiter.snapshot.RoomSnapshot, net.user1.orbiter.snapshot.Snapshot);
12040 
12041 //==============================================================================
12042 // INSTANCE METHODS
12043 //==============================================================================                
12044 /**
12045  * @private
12046  */    
12047 net.user1.orbiter.snapshot.RoomSnapshot.prototype.setManifest = function (value) {
12048   this.manifest = value;
12049 };
12050     
12051 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getAttribute = function (name) {
12052   if (!this.manifest) {
12053     return null;
12054   }
12055   return this.manifest.attributes.getAttribute(name, net.user1.orbiter.Tokens.GLOBAL_ATTR);
12056 };
12057 
12058 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getAttributes = function () {
12059   if (!this.manifest) {
12060     return null;
12061   }
12062   return this.manifest.attributes.getByScope(net.user1.orbiter.Tokens.GLOBAL_ATTR);
12063 };
12064 
12065 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getRoomID = function () {
12066   if (!this.manifest) {
12067     return null;
12068   }
12069   return this.manifest.roomID;
12070 };
12071 
12072 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getOccupants = function () {
12073   return this.manifest.occupants.slice();
12074 }
12075 
12076 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getObservers = function () {
12077   return this.manifest.observers.slice();
12078 }
12079 
12080 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getOccupant = function (clientID) {
12081   var client;
12082   for (var i = this.manifest.occupants.length; --i >= 0;) {
12083     if (this.manifest.occupants[i].clientID == clientID) {
12084       return this.manifest.occupants[i];
12085     } 
12086   }
12087   return null;
12088 };
12089 
12090 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getObserver = function (clientID) {
12091   var client;
12092   for (var i = this.manifest.observers.length; --i >= 0;) {
12093     if (this.manifest.observers[i].clientID == clientID) {
12094       return this.manifest.observers[i];
12095     } 
12096   }
12097   return null;
12098 };
12099 
12100 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getNumOccupants = function () {
12101   return Math.max(this.manifest.occupants.length, this.manifest.occupantCount);
12102 }
12103 
12104 net.user1.orbiter.snapshot.RoomSnapshot.prototype.getNumObservers = function () {
12105   return Math.max(this.manifest.observers.length, this.manifest.observerCount);
12106 }
12107 //==============================================================================
12108 //  SECURITY_ROLE CONSTANTS
12109 //==============================================================================
12110 /** @class */
12111 net.user1.orbiter.SecurityRole = new Object();
12112 /** @constant */
12113 net.user1.orbiter.SecurityRole.MODERATOR = "MODERATOR";
12114 //==============================================================================
12115 // CLASS DECLARATION
12116 //==============================================================================
12117 /** @class
12118 
12119 The Server class dispatches the following events:
12120 
12121 <ul class="summary">
12122 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.TIME_SYNC}</li> 
12123 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.UPC_PROCESSED}</li> 
12124 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT}</li> 
12125 <li class="fixedFont">{@link net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT}</li> 
12126 </ul>
12127 
12128 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
12129 
12130 @extends net.user1.events.EventDispatcher
12131 */
12132 net.user1.orbiter.Server = function (orbiter) {
12133   // Call superconstructor
12134   net.user1.events.EventDispatcher.call(this);
12135 
12136   this.orbiter = orbiter;
12137   this.version = null;
12138   this.upcVersion = null;
12139   this.localAgeAtLastSync = NaN;
12140   this.lastKnownServerTime = NaN;
12141   this._isWatchingForProcessedUPCs = false;
12142   
12143   this.log = orbiter.getLog();
12144   
12145   orbiter.addEventListener(net.user1.orbiter.OrbiterEvent.READY, this.readyListener, this);
12146 }
12147 
12148 //==============================================================================    
12149 // INHERITANCE
12150 //============================================================================== 
12151 net.user1.utils.extend(net.user1.orbiter.Server, net.user1.events.EventDispatcher);
12152 
12153 // =============================================================================
12154 // SERVER-WIDE MESSAGING
12155 // =============================================================================
12156 net.user1.orbiter.Server.prototype.sendMessage = function (messageName, 
12157                                                            includeSelf,
12158                                                            filters) {
12159   var rest = Array.prototype.slice.call(arguments).slice(3);
12160   var args;
12161 
12162   if (messageName == null || messageName == "") {
12163     this.log.warn("Server.sendMessage() failed. No messageName supplied.");
12164     return;
12165   }
12166 
12167   var msgMan = this.orbiter.getMessageManager();
12168   args = [net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER, 
12169           messageName, 
12170           includeSelf.toString(),
12171           filters != null ? filters.toXMLString() : ""];
12172   msgMan.sendUPC.apply(msgMan, args.concat(rest));
12173 };
12174 
12175 // =============================================================================
12176 // SERVER MODULES
12177 // =============================================================================
12178 net.user1.orbiter.Server.prototype.sendModuleMessage = function (moduleID,
12179                                                                  messageName, 
12180                                                                  messageArguments) {
12181   var sendupcArgs = [net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE, moduleID, messageName];
12182   
12183   for (var arg in messageArguments) {
12184     sendupcArgs.push(arg + net.user1.orbiter.Tokens.RS + messageArguments[arg]);
12185   }
12186         
12187   this.orbiter.getMessageManager().sendUPC.apply(this.orbiter.getMessageManager(), sendupcArgs);
12188 };
12189 
12190 net.user1.orbiter.Server.prototype.clearModuleCache = function () {
12191   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.CLEAR_MODULE_CACHE);
12192 };
12193 
12194 // =============================================================================
12195 // VERSION ACCESS
12196 // =============================================================================
12197 
12198 /**
12199  * @private
12200  */
12201 net.user1.orbiter.Server.prototype.setVersion = function (value) {
12202   this.version = value;
12203 };
12204 
12205 net.user1.orbiter.Server.prototype.getVersion = function () {
12206   return this.version;
12207 };
12208 
12209 /**
12210  * @private
12211  */
12212 net.user1.orbiter.Server.prototype.setUPCVersion = function (value) {
12213   this.upcVersion = value;
12214 };
12215 
12216 net.user1.orbiter.Server.prototype.getUPCVersion = function () {
12217   return this.upcVersion;
12218 };
12219 
12220 // =============================================================================
12221 // UPC STATS AND PROCESSING
12222 // =============================================================================
12223 
12224 net.user1.orbiter.Server.prototype.resetUPCStats = function () {
12225   this.orbiter.getMessageManager().sendUPC(UPC.RESET_UPC_STATS);
12226 };
12227 
12228 net.user1.orbiter.Server.prototype.watchForProcessedUPCs = function () {
12229   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS);
12230 };
12231 
12232 net.user1.orbiter.Server.prototype.stopWatchingForProcessedUPCs = function () {
12233   this.orbiter.getMessageManager().sendUPC(net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS);
12234 };
12235 
12236 net.user1.orbiter.Server.prototype.isWatchingForProcessedUPCs = function () {
12237   return this._isWatchingForProcessedUPCs;
12238 };
12239 
12240 net.user1.orbiter.Server.prototype.setIsWatchingForProcessedUPCs = function (value) {
12241   this._isWatchingForProcessedUPCs = value;
12242 };
12243 
12244 // =============================================================================
12245 // TIME RETRIEVAL METHODS
12246 // =============================================================================
12247 net.user1.orbiter.Server.prototype.getServerTime = function () {
12248   var self = this.orbiter.self();
12249   var lastServerTime = NaN;
12250   var estimatedServerTime = NaN;
12251   
12252   if (self != null) {
12253     lastServerTime = isNaN(this.lastKnownServerTime) 
12254                      ? self.getConnectTime() 
12255                      : this.lastKnownServerTime;
12256                      
12257     estimatedServerTime = isNaN(lastServerTime) 
12258                          ? NaN
12259                          : (lastServerTime + (new Date().getTime()-this.localAgeAtLastSync));
12260   }
12261  
12262   if (estimatedServerTime == 0) {
12263     log.warn("Server time requested, but is unknown.");
12264   }
12265   
12266   return estimatedServerTime;
12267 };
12268 
12269 net.user1.orbiter.Server.prototype.syncTime = function () {
12270   var msgMan = this.orbiter.getMessageManager();
12271   msgMan.sendUPC(net.user1.orbiter.UPC.SYNC_TIME);
12272 };
12273 
12274 /**
12275  * @private 
12276  */ 
12277 net.user1.orbiter.Server.prototype.readyListener = function (e) {
12278   this.orbiter.getMessageManager().addMessageListener(net.user1.orbiter.UPC.SERVER_TIME_UPDATE, this.u50);  
12279   this.localAgeAtLastSync = new Date().getTime();;
12280 };
12281 
12282 // =============================================================================
12283 // EVENT DISPATCHING
12284 // =============================================================================
12285 
12286 /**
12287  * @private
12288  */
12289 net.user1.orbiter.Server.prototype.fireTimeSync = function () {
12290   this.dispatchEvent(new net.user1.orbiter.ServerEvent(ServerEvent.TIME_SYNC));
12291 };
12292 
12293 /**
12294  * @private
12295  */
12296 net.user1.orbiter.Server.prototype.dispatchWatchForProcessedUPCsResult = function (status) {
12297   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT,
12298                      null, status));
12299 };
12300 
12301 /**
12302  * @private
12303  */
12304 net.user1.orbiter.Server.prototype.dispatchStopWatchingForProcessedUPCsResult = function (status) {
12305   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT,
12306                      null, status));
12307 };
12308 
12309 /**
12310  * @private
12311  */
12312 net.user1.orbiter.Server.prototype.dispatchUPCProcessed = function (record) {
12313   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.UPC_PROCESSED, record));
12314 };
12315 
12316 /**
12317  * @private
12318  */
12319 net.user1.orbiter.Server.prototype.dispatchResetUPCStatsResult = function (status) {
12320   this.dispatchEvent(new net.user1.orbiter.ServerEvent(net.user1.orbiter.ServerEvent.RESET_UPC_STATS_RESULT,
12321                      null, status));
12322 };
12323 
12324 //==============================================================================
12325 // UPC LISTENERS
12326 //==============================================================================
12327 
12328 /**
12329  * @private
12330  */
12331 net.user1.orbiter.Server.prototype.u50 = function (newTime) {             // SERVER_TIME
12332   this.lastKnownServerTime = Number(newTime);
12333   this.localAgeAtLastSync  = new Date().getTime();
12334   this.fireTimeSync();
12335 }
12336 
12337 //==============================================================================
12338 // CLEANUP AND DISPOSAL
12339 //==============================================================================  
12340 /**
12341  * @private
12342  */    
12343 net.user1.orbiter.Server.prototype.cleanup = function () {
12344   this.log.info("[SERVER] Cleaning resources.");
12345   this.setIsWatchingForProcessedUPCs(false);
12346 }
12347 //==============================================================================
12348 // CLASS DECLARATION
12349 //==============================================================================
12350 /** 
12351  * @class
12352  * @extends net.user1.orbiter.snapshot.Snapshot
12353  */
12354 net.user1.orbiter.snapshot.GatewaysSnapshot = function () {
12355   // Call superconstructor
12356   net.user1.orbiter.snapshot.Snapshot.call(this);
12357   this.gateways = null;
12358   this.method = net.user1.orbiter.UPC.GET_GATEWAYS_SNAPSHOT;
12359 };
12360 
12361 //==============================================================================
12362 // INHERITANCE
12363 //==============================================================================
12364 net.user1.utils.extend(net.user1.orbiter.snapshot.GatewaysSnapshot, net.user1.orbiter.snapshot.Snapshot);
12365 
12366 //==============================================================================
12367 // INSTANCE METHODS
12368 //==============================================================================         
12369 /**
12370  * @private
12371  */    
12372 net.user1.orbiter.snapshot.GatewaysSnapshot.prototype.setGateways = function (value) {
12373   this.gateways = value;
12374 };
12375 
12376 net.user1.orbiter.snapshot.GatewaysSnapshot.prototype.getGateways = function () {
12377   if (!this.gateways) {
12378     return [];
12379   }
12380   return this.gateways.slice();
12381 };
12382 //==============================================================================
12383 // CLASS DECLARATION
12384 //==============================================================================
12385 /** @class
12386     @extends net.user1.events.Event
12387 */
12388 net.user1.orbiter.ServerEvent = function (type, 
12389                                           upcProcessingRecord,
12390                                           status) {
12391   net.user1.events.Event.call(this, type);
12392   
12393   this.upcProcessingRecord = upcProcessingRecord;
12394   this.status = status;
12395 };
12396 
12397 //==============================================================================
12398 // INHERITANCE
12399 //==============================================================================
12400 net.user1.utils.extend(net.user1.orbiter.ServerEvent, net.user1.events.Event);
12401 
12402 //==============================================================================
12403 // STATIC VARIABLES
12404 //==============================================================================
12405 
12406 /** @constant */
12407 net.user1.orbiter.ServerEvent.TIME_SYNC = "TIME_SYNC";
12408 /** @constant */
12409 net.user1.orbiter.ServerEvent.UPC_PROCESSED = "UPC_PROCESSED";
12410 /** @constant */
12411 net.user1.orbiter.ServerEvent.WATCH_FOR_PROCESSED_UPCS_RESULT = "WATCH_FOR_PROCESSED_UPCS_RESULT";
12412 /** @constant */
12413 net.user1.orbiter.ServerEvent.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT = "STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT";
12414 /** @constant */
12415 net.user1.orbiter.ServerEvent.RESET_UPC_STATS_RESULT = "RESET_UPC_STATS_RESULT";
12416 
12417 //==============================================================================
12418 // VARIABLES
12419 //==============================================================================
12420 net.user1.orbiter.ServerEvent.prototype.getUPCProcessingRecord = function () {
12421   return upcProcessingRecord;
12422 }
12423   
12424 net.user1.orbiter.ServerEvent.prototype.getStatus = function () {
12425   return status;
12426 }
12427     
12428 
12429 net.user1.orbiter.ServerEvent.prototype.toString = function () {
12430   return "[object ServerEvent]";
12431 };
12432 //==============================================================================
12433 // CLASS DECLARATION
12434 //==============================================================================
12435 /** 
12436  * @class
12437  * @extends net.user1.orbiter.snapshot.Snapshot
12438  */
12439 net.user1.orbiter.snapshot.ServerModuleListSnapshot = function () {
12440   // Call superconstructor
12441   net.user1.orbiter.snapshot.Snapshot.call(this);
12442   this.moduleList = null;
12443   this.method = net.user1.orbiter.UPC.GET_SERVERMODULELIST_SNAPSHOT;
12444 };
12445 
12446 //==============================================================================
12447 // INHERITANCE
12448 //==============================================================================
12449 net.user1.utils.extend(net.user1.orbiter.snapshot.ServerModuleListSnapshot, net.user1.orbiter.snapshot.Snapshot);
12450 
12451 //==============================================================================
12452 // INSTANCE METHODS
12453 //==============================================================================         
12454 /**
12455  * @private
12456  */    
12457 net.user1.orbiter.snapshot.ServerModuleListSnapshot.prototype.setModuleList = function (value) {
12458   this.moduleList = value;
12459 }
12460 
12461 net.user1.orbiter.snapshot.ServerModuleListSnapshot.prototype.getModuleList = function () {
12462   if (!this.moduleList) {
12463     return null;
12464   }
12465   return this.moduleList.slice();
12466 };
12467 //==============================================================================
12468 // CLASS DECLARATION
12469 //==============================================================================
12470 /** 
12471  * @private
12472  * @class
12473  */
12474 net.user1.orbiter.upc.SetAttr = function (name,
12475                                           value,
12476                                           options) {
12477     
12478   // Abort if name is invalid.
12479   if (!net.user1.orbiter.Validator.isValidAttributeName(name)) {
12480     throw new Error("Cannot set attribute. Illegal name" + 
12481       " (see Validator.isValidAttributeName()). " +
12482       " Illegal attribute is: " + name + "=" + value);
12483   }
12484 
12485   // Abort if value is invalid.
12486   if (!net.user1.orbiter.Validator.isValidAttributeValue(value)) {
12487     throw new Error("Cannot set attribute. Illegal value" + 
12488       " (see Validator.isValidAttributeValue()). " +
12489       " Illegal attribute is: " + name + "=" + value);
12490   }
12491   
12492   if (value == null) {
12493     value = "";
12494   }
12495   
12496   // Validation passed, so assign instance vars.
12497   this.name = name;
12498   this.value = value;
12499   this.options = options;
12500 };
12501 /**
12502  * @private
12503  */  
12504 net.user1.orbiter.upc.SetClientAttr = function (name, 
12505                                                 value, 
12506                                                 options,
12507                                                 scope,
12508                                                 clientID,
12509                                                 userID) {
12510   // Call superconstructor
12511   net.user1.orbiter.upc.SetAttr.call(this, name, value, options);
12512       
12513   // Abort if scope is invalid.
12514   if (!net.user1.orbiter.Validator.isValidAttributeScope(scope)) {
12515     throw new Error("Cannot set client attribute. Illegal scope" + 
12516              " (see Validator.isValidAttributeScope()). " +
12517              " Illegal attribute is: " + name + "=" + value);
12518   }
12519 
12520   // A scope null means the attribute is global.
12521   if (scope == null) {
12522     scope = net.user1.orbiter.Tokens.GLOBAL_ATTR;
12523   }
12524 
12525   this.method = net.user1.orbiter.UPC.SET_CLIENT_ATTR;
12526   this.args   = [clientID, userID, name, value, scope, options.toString()];
12527 };
12528 
12529 //==============================================================================    
12530 // INHERITANCE
12531 //============================================================================== 
12532 net.user1.utils.extend(net.user1.orbiter.upc.SetClientAttr, net.user1.orbiter.upc.SetAttr);
12533 /**
12534  * @private
12535  */  
12536 net.user1.orbiter.upc.SetRoomAttr = function (name, 
12537                                               value, 
12538                                               options,
12539                                               roomID) {
12540   // Call superconstructor
12541   net.user1.orbiter.upc.SetAttr.call(this, name, value, options);
12542       
12543   this.method = net.user1.orbiter.UPC.SET_ROOM_ATTR;
12544   this.args   = [roomID, name, value, options.toString()];
12545 };
12546 
12547 //==============================================================================    
12548 // INHERITANCE
12549 //============================================================================== 
12550 net.user1.utils.extend(net.user1.orbiter.upc.SetRoomAttr, net.user1.orbiter.upc.SetAttr);
12551 //==============================================================================
12552 // CLASS DECLARATION
12553 //==============================================================================
12554 /** 
12555  * @class
12556  * 
12557  * Note: Due to JavaScript's lack of memory measurement APIs and byte-measurement 
12558  * APIs, Orbiter's Statistics class does not include many of the statistics found
12559  * in the equivalent Reactor Statistics class.
12560  */
12561 net.user1.orbiter.Statistics = function (orbiter) {
12562   this.statsTimer;
12563   this.lastTick = NaN;
12564   this.lastTotalMessages = 0;
12565   this.messagesPerSecond = 0;
12566   
12567   // Peaks
12568   this.peakMessagesPerSecond = 0;
12569 
12570   this.orbiter = null;
12571   this.connection = null;
12572   
12573   this.statsIntervalID = -1;
12574   
12575   this.init(orbiter);
12576 };
12577 
12578 /**
12579  * @private
12580  */
12581 net.user1.orbiter.Statistics.prototype.init = function (orbiter) {
12582   this.setOrbiter(orbiter);
12583   this.start();
12584 };
12585 
12586 /**
12587  * @private
12588  */
12589 net.user1.orbiter.Statistics.prototype.setOrbiter = function (orbiter) {
12590   // Register new orbiter
12591   this.orbiter = orbiter;
12592 };
12593 
12594 net.user1.orbiter.Statistics.prototype.start = function () {
12595   this.stop();
12596   
12597   this.statsIntervalID = setInterval(statsTimerListener, 1000);
12598   
12599   this.lastTick = new Date().getTime();
12600   this.lastTotalMessages = this.getTotalMessages();
12601 };
12602 
12603 net.user1.orbiter.Statistics.prototype.stop = function () {
12604   clearInterval(statsIntervalID);
12605   this.clearStats();
12606 };
12607 
12608 /**
12609  * @private 
12610  */
12611 net.user1.orbiter.Statistics.prototype.clearStats = function () {
12612   this.lastTick = 0;
12613   this.lastTotalMessages = 0;
12614   this.messagesPerSecond = 0;
12615   this.peakMessagesPerSecond = 0;
12616 };
12617 
12618 net.user1.orbiter.Statistics.prototype.getLifetimeNumClientsConnected = function () {
12619   return this.orbiter.getClientManager().getLifetimeNumClientsKnown();
12620 };
12621 
12622 net.user1.orbiter.Statistics.prototype.getCurrentNumClientsConnected = function () {
12623   return this.orbiter.getClientManager().getNumClients();
12624 };
12625 
12626 net.user1.orbiter.Statistics.prototype.getTotalMessagesReceived = function () {
12627   return this.orbiter.getMessageManager().getNumMessagesReceived();
12628 }
12629 
12630 net.user1.orbiter.Statistics.prototype.getTotalMessagesSent = function () {
12631   return this.orbiter.getMessageManager().getNumMessagesSent();
12632 };
12633 
12634 net.user1.orbiter.Statistics.prototype.getTotalMessages = function () {
12635   return this.getTotalMessagesReceived() + this.getTotalMessagesSent();
12636 };
12637 
12638 net.user1.orbiter.Statistics.prototype.getMessagesPerSecond = function () {
12639   return this.messagesPerSecond;
12640 };
12641 
12642 //==============================================================================
12643 // PEAK MESSAGES PER SECOND
12644 //==============================================================================
12645 
12646 net.user1.orbiter.Statistics.prototype.getPeakMessagesPerSecond = function () {
12647   return this.peakMessagesPerSecond;
12648 };
12649 
12650 // =============================================================================
12651 // TIMER LISTENER
12652 // =============================================================================
12653 
12654 /**
12655  * @private 
12656  */
12657 net.user1.orbiter.Statistics.prototype.statsTimerListener = function (e) {
12658   // Check elapsed time
12659   var now = new Date().getTime();
12660   var elapsed = now - lastTick;
12661   lastTick = now;
12662   
12663   // Calculate number of messages sent and received since last tick
12664   var totalMessages = this.getTotalMessages();
12665   var tickNumMsgs   = totalMessages - this.lastTotalMessages;
12666   this.lastTotalMessages        = totalMessages;
12667   this.messagesPerSecond        = Math.round((1000/elapsed) * tickNumMsgs);
12668   if (this.messagesPerSecond > this.peakMessagesPerSecond) {
12669     this.peakMessagesPerSecond = this.messagesPerSecond;
12670   }
12671 };
12672 //==============================================================================
12673 //  STATUS CONSTANTS
12674 //==============================================================================
12675 /** @class */
12676 net.user1.orbiter.Status = new Object();
12677 /** @constant */
12678 net.user1.orbiter.Status.ACCOUNT_EXISTS         = "ACCOUNT_EXISTS";
12679 /** @constant */
12680 net.user1.orbiter.Status.ACCOUNT_NOT_FOUND      = "ACCOUNT_NOT_FOUND";
12681 /** @constant */
12682 net.user1.orbiter.Status.AUTHORIZATION_REQUIRED = "AUTHORIZATION_REQUIRED";
12683 /** @constant */
12684 net.user1.orbiter.Status.AUTHORIZATION_FAILED   = "AUTHORIZATION_FAILED";
12685 /** @constant */
12686 net.user1.orbiter.Status.ALREADY_ASSIGNED       = "ALREADY_ASSIGNED";
12687 /** @constant */
12688 net.user1.orbiter.Status.ALREADY_BANNED         = "ALREADY_BANNED";
12689 /** @constant */
12690 net.user1.orbiter.Status.ALREADY_IN_ROOM        = "ALREADY_IN_ROOM";
12691 /** @constant */
12692 net.user1.orbiter.Status.ALREADY_LOGGED_IN      = "ALREADY_LOGGED_IN";
12693 /** @constant */
12694 net.user1.orbiter.Status.ALREADY_OBSERVING      = "ALREADY_OBSERVING";
12695 /** @constant */
12696 net.user1.orbiter.Status.ALREADY_SYNCHRONIZED   = "ALREADY_SYNCHRONIZED";
12697 /** @constant */
12698 net.user1.orbiter.Status.ALREADY_WATCHING       = "ALREADY_WATCHING";  
12699 /** @constant */
12700 net.user1.orbiter.Status.ATTR_NOT_FOUND         = "ATTR_NOT_FOUND";
12701 /** @constant */
12702 net.user1.orbiter.Status.CLIENT_NOT_FOUND       = "CLIENT_NOT_FOUND";
12703 /** @constant */
12704 net.user1.orbiter.Status.ERROR                  = "ERROR";
12705 /** @constant */
12706 net.user1.orbiter.Status.EVALUATION_FAILED      = "EVALUATION_FAILED";
12707 /** @constant */
12708 net.user1.orbiter.Status.DUPLICATE_VALUE        = "DUPLICATE_VALUE";
12709 /** @constant */
12710 net.user1.orbiter.Status.IMMUTABLE              = "IMMUTABLE";
12711 /** @constant */
12712 net.user1.orbiter.Status.INVALID_QUALIFIER      = "INVALID_QUALIFIER";
12713 /** @constant */
12714 net.user1.orbiter.Status.NAME_NOT_FOUND         = "NAME_NOT_FOUND";
12715 /** @constant */
12716 net.user1.orbiter.Status.NAME_EXISTS            = "NAME_EXISTS";
12717 /** @constant */
12718 net.user1.orbiter.Status.NOT_ASSIGNED           = "NOT_ASSIGNED";
12719 /** @constant */
12720 net.user1.orbiter.Status.NOT_BANNED             = "NOT_BANNED";
12721 /** @constant */
12722 net.user1.orbiter.Status.NOT_IN_ROOM            = "NOT_IN_ROOM";
12723 /** @constant */
12724 net.user1.orbiter.Status.NOT_LOGGED_IN          = "NOT_LOGGED_IN";
12725 /** @constant */
12726 net.user1.orbiter.Status.NOT_OBSERVING          = "NOT_OBSERVING";
12727 /** @constant */
12728 net.user1.orbiter.Status.NOT_WATCHING           = "NOT_WATCHING";
12729 /** @constant */
12730 net.user1.orbiter.Status.PERMISSION_DENIED      = "PERMISSION_DENIED";
12731 /** @constant */
12732 net.user1.orbiter.Status.REMOVED                = "REMOVED";
12733 /** @constant */
12734 net.user1.orbiter.Status.ROLE_NOT_FOUND         = "ROLE_NOT_FOUND";
12735 /** @constant */
12736 net.user1.orbiter.Status.ROOM_EXISTS            = "ROOM_EXISTS";
12737 /** @constant */
12738 net.user1.orbiter.Status.ROOM_FULL              = "ROOM_FULL";
12739 /** @constant */
12740 net.user1.orbiter.Status.ROOM_NOT_FOUND         = "ROOM_NOT_FOUND";
12741 /** @constant */
12742 net.user1.orbiter.Status.SERVER_ONLY            = "SERVER_ONLY";
12743 /** @constant */
12744 net.user1.orbiter.Status.SUCCESS                = "SUCCESS";
12745 //==============================================================================
12746 //  STATUS CONSTANTS
12747 //==============================================================================
12748 /** @class */
12749 net.user1.orbiter.SynchronizationState = new Object();
12750 /** @constant */
12751 net.user1.orbiter.SynchronizationState.SYNCHRONIZED     = "SYNCHRONIZED";
12752 /** @constant */
12753 net.user1.orbiter.SynchronizationState.NOT_SYNCHRONIZED = "NOT_SYNCHRONIZED";
12754 /** @constant */
12755 net.user1.orbiter.SynchronizationState.SYNCHRONIZING    = "SYNCHRONIZING";
12756 //==============================================================================
12757 // CLASS DECLARATION
12758 //==============================================================================
12759 /** 
12760  * @class
12761  */
12762 net.user1.orbiter.UPCProcessingRecord = function () {
12763   /**
12764    * @field
12765    */
12766   this.fromClientID = null;
12767   /**
12768    * @field
12769    */
12770   this.fromUserID = null;
12771   /**
12772    * @field
12773    */
12774   this.fromClientAddress = null;
12775   /**
12776    * @field
12777    */
12778   this.processingStartedAt = NaN;
12779   /**
12780    * @field
12781    */
12782   this.processingFinishedAt = NaN;
12783   /**
12784    * @field
12785    */
12786   this.processingDuration = NaN;
12787   /**
12788    * @field
12789    */
12790   this.queuedAt = NaN;
12791   /**
12792    * @field
12793    */
12794   this.queueDuration = NaN;
12795   /**
12796    * @field
12797    */
12798   this.UPCSource = null;
12799 };
12800 
12801 /** 
12802  * @private
12803  */
12804 net.user1.orbiter.UPCProcessingRecord.prototype.deserialize = function (serializedRecord) {
12805   var recordParts = [];
12806   var numSignificantSeparators = 6;
12807   var separatorIndices = [];
12808   var thisSeparatorIndex = 0;
12809   var previousSeparatorIndex = -1;
12810   
12811   // Don't use split because the source might contain the record separator
12812   for (var i = 0; i < numSignificantSeparators; i++) {
12813     thisSeparatorIndex = serializedRecord.indexOf(net.user1.orbiter.Tokens.RS, previousSeparatorIndex+1);
12814     recordParts.push(serializedRecord.substring(previousSeparatorIndex+1, thisSeparatorIndex));
12815     previousSeparatorIndex = thisSeparatorIndex;
12816   }
12817   recordParts.push(serializedRecord.substring(thisSeparatorIndex+1));
12818   
12819   this.deserializeParts(recordParts[0],
12820                         recordParts[1],
12821                         recordParts[2],
12822                         recordParts[3],
12823                         recordParts[4],
12824                         recordParts[5],
12825                         recordParts[6]);
12826 };
12827 
12828 /** 
12829  * @private
12830  */
12831 net.user1.orbiter.UPCProcessingRecord.prototype.deserializeParts = function (fromClientID,
12832                                                                              fromUserID,
12833                                                                              fromClientAddress,
12834                                                                              queuedAt,
12835                                                                              processingStartedAt,
12836                                                                              processingFinishedAt,
12837                                                                              source) {
12838   this.fromClientID = fromClientID;
12839   this.fromUserID = fromUserID;
12840   this.fromClientAddress = fromClientAddress;
12841   this.processingStartedAt = parseFloat(processingStartedAt);
12842   this.processingFinishedAt = parseFloat(processingFinishedAt);
12843   this.processingDuration = this.processingFinishedAt - this.processingStartedAt;
12844   this.queuedAt = parseFloat(queuedAt);
12845   this.queueDuration = this.processingStartedAt - this.queuedAt;
12846   this.UPCSource = source;
12847   var escapedCDStart = /<!\(\[CDATA\[/gi; 
12848   var escapedCDEnd = /\]\]\)>/gi; 
12849   this.UPCSource = this.UPCSource.replace(escapedCDStart, "<![CDATA[");
12850   this.UPCSource = this.UPCSource.replace(escapedCDEnd, "]]>");
12851 }
12852 //==============================================================================
12853 // CLASS DECLARATION
12854 //==============================================================================
12855 /** 
12856  * @class
12857  * @extends net.user1.orbiter.snapshot.Snapshot
12858  */
12859 net.user1.orbiter.snapshot.UPCStatsSnapshot = function () {
12860   // Call superconstructor
12861   net.user1.orbiter.snapshot.Snapshot.call(this);
12862   this.totalUPCsProcessed;
12863   this.numUPCsInQueue;
12864   this.lastQueueWaitTime;
12865   this.longestUPCProcesses;
12866   this.method = net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT;
12867   this.hasStatus = true;
12868 };
12869 
12870 //==============================================================================
12871 // INHERITANCE
12872 //==============================================================================
12873 net.user1.utils.extend(net.user1.orbiter.snapshot.UPCStatsSnapshot, net.user1.orbiter.snapshot.Snapshot);
12874 
12875 //==============================================================================
12876 // INSTANCE METHODS
12877 //==============================================================================         
12878 /**
12879  * @private
12880  */
12881 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setTotalUPCsProcessed = function (value) {
12882   this.totalUPCsProcessed = value;
12883 };
12884     
12885 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getTotalUPCsProcessed = function () {
12886   return this.totalUPCsProcessed;
12887 };
12888         
12889 /**
12890  * @private
12891  */
12892 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setNumUPCsInQueue = function (value) {
12893   this.numUPCsInQueue = value;
12894 };
12895 
12896 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getNumUPCsInQueue = function () {
12897   return this.numUPCsInQueue;
12898 };
12899     
12900 /**
12901  * @private
12902  */
12903 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setLastQueueWaitTime = function (value) {
12904   this.lastQueueWaitTime = value;
12905 };
12906 
12907 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getLastQueueWaitTime = function () {
12908   return this.lastQueueWaitTime;
12909 };
12910     
12911 /**
12912  * @private
12913  */
12914 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.setLongestUPCProcesses = function (value) {
12915   this.longestUPCProcesses = value;
12916 };
12917 
12918 net.user1.orbiter.snapshot.UPCStatsSnapshot.prototype.getLongestUPCProcesses = function () {
12919   if (!this.longestUPCProcesses) {
12920     return null;
12921   }
12922   return this.longestUPCProcesses.slice();
12923 };
12924 //==============================================================================
12925 // CLASS DECLARATION
12926 //==============================================================================
12927 net.user1.orbiter.UpdateLevels = function () {
12928   this.restoreDefaults();
12929 };
12930 
12931 //==============================================================================    
12932 // STATIC VARIABLES
12933 //==============================================================================
12934 net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES     = 1;
12935 net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES = 1 << 1;
12936 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT = 1 << 2;
12937 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT = 1 << 3;
12938 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST = 1 << 4;
12939 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST = 1 << 5;
12940 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM = 1 << 6;
12941 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM = 1 << 7;
12942 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL = 1 << 8;
12943 net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL = 1 << 9;
12944 net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF = 1 << 10;
12945 net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF = 1 << 11;
12946 net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES = 1 << 12;
12947 
12948 //==============================================================================    
12949 // INSTANCE METHODS
12950 //==============================================================================
12951 net.user1.orbiter.UpdateLevels.prototype.clearAll = function () {
12952   this.roomMessages = false;
12953   this.sharedRoomAttributes = false;
12954   this.occupantCount = false;
12955   this.observerCount = false;
12956   this.occupantList = false;
12957   this.observerList = false;
12958   this.sharedOccupantAttributesRoom = false;
12959   this.sharedOccupantAttributesGlobal = false;
12960   this.sharedObserverAttributesRoom = false;
12961   this.sharedObserverAttributesGlobal = false;
12962   this.occupantLoginLogoff = false;
12963   this.observerLoginLogoff = false;
12964   this.allRoomAttributes = false;
12965 };
12966 
12967 net.user1.orbiter.UpdateLevels.prototype.restoreDefaults = function () {
12968   this.roomMessages = true;
12969   this.sharedRoomAttributes = true;
12970   this.occupantCount = false;
12971   this.observerCount = false;
12972   this.occupantList = true;
12973   this.observerList = false;
12974   this.sharedOccupantAttributesRoom = true;
12975   this.sharedOccupantAttributesGlobal = true;
12976   this.sharedObserverAttributesRoom = false;
12977   this.sharedObserverAttributesGlobal = false;
12978   this.occupantLoginLogoff = true;
12979   this.observerLoginLogoff = false;
12980   this.allRoomAttributes = false;
12981 };
12982     
12983 net.user1.orbiter.UpdateLevels.prototype.toInt = function () {
12984   var levels = (this.roomMessages ? net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES : 0)
12985    | (this.sharedRoomAttributes ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES : 0)
12986    | (this.occupantCount ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT : 0)
12987    | (this.observerCount ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT : 0)
12988    | (this.occupantList ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST : 0)
12989    | (this.observerList ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST : 0)
12990    | (this.sharedOccupantAttributesRoom ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM : 0)
12991    | (this.sharedOccupantAttributesGlobal ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL : 0)
12992    | (this.sharedObserverAttributesRoom ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM : 0)
12993    | (this.sharedObserverAttributesGlobal ? net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL : 0)
12994    | (this.occupantLoginLogoff ? net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF : 0)
12995    | (this.observerLoginLogoff ? net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF : 0)
12996    | (this.allRoomAttributes ? net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES : 0);
12997   
12998   return levels;
12999 };
13000     
13001 net.user1.orbiter.UpdateLevels.prototype.fromInt = function (levels) {
13002   roomMessages                   = levels & net.user1.orbiter.UpdateLevels.FLAG_ROOM_MESSAGES;
13003   sharedRoomAttributes           = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_ROOM_ATTRIBUTES;
13004   occupantCount                  = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_COUNT;
13005   observerCount                  = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_COUNT;
13006   occupantList                   = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LIST;
13007   observerList                   = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LIST;
13008   sharedOccupantAttributesRoom   = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_ROOM;
13009   sharedOccupantAttributesGlobal = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OCCUPANT_ATTRIBUTES_GLOBAL;
13010   sharedObserverAttributesRoom   = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_ROOM;
13011   sharedObserverAttributesGlobal = levels & net.user1.orbiter.UpdateLevels.FLAG_SHARED_OBSERVER_ATTRIBUTES_GLOBAL;
13012   occupantLoginLogoff            = levels & net.user1.orbiter.UpdateLevels.FLAG_OCCUPANT_LOGIN_LOGOFF;
13013   observerLoginLogoff            = levels & net.user1.orbiter.UpdateLevels.FLAG_OBSERVER_LOGIN_LOGOFF;
13014   allRoomAttributes              = levels & net.user1.orbiter.UpdateLevels.FLAG_ALL_ROOM_ATTRIBUTES;
13015 };
13016 //==============================================================================
13017 // CLASS DECLARATION
13018 //==============================================================================
13019 /** @class
13020 
13021 The UserAccount class dispatches the following events:
13022 
13023 <ul class="summary">
13024   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGIN}</li>    
13025   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF_RESULT}</li>    
13026   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.LOGOFF}</li>    
13027   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT}</li>    
13028   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.CHANGE_PASSWORD}</li>    
13029   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.SYNCHRONIZE}</li>    
13030   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE}</li> 
13031   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.OBSERVE_RESULT}</li>  
13032   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING}</li>    
13033   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT}</li>    
13034   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT}</li>    
13035   <li class="fixedFont">{@link net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT}</li>    
13036 </ul>
13037 
13038 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
13039 
13040     @extends net.user1.events.EventDispatcher
13041 */
13042 
13043 net.user1.orbiter.UserAccount = function (userID, 
13044                                           log,
13045                                           accountManager,
13046                                           clientManager,
13047                                           roomManager) {
13048   net.user1.events.EventDispatcher.call(this);
13049   
13050   this.userID = userID;
13051   this.attributeManager = null;
13052   this.connectionState = 0;
13053   this.password = null;
13054   this.lastAttemptedPassword = null;
13055   this._client = null;
13056   this._accountManager = null;
13057   this._clientManager = null;
13058   this._roomManager = null;
13059   this._log = null;
13060   
13061   this.setLog(log);
13062   this.setAccountManager(accountManager);
13063   this.setClientManager(clientManager);
13064   this.setRoomManager(roomManager);
13065 };
13066 
13067 //==============================================================================    
13068 // INHERITANCE
13069 //============================================================================== 
13070 net.user1.utils.extend(net.user1.orbiter.UserAccount, net.user1.events.EventDispatcher);
13071 
13072 //==============================================================================
13073 // STATIC VARIABLES
13074 //==============================================================================
13075 /** @private */    
13076 net.user1.orbiter.UserAccount.FLAG_MODERATOR = 1 << 1;
13077     
13078 //==============================================================================
13079 // DEPENDENCIES
13080 //==============================================================================
13081 
13082 /**
13083  * @private
13084  */        
13085 net.user1.orbiter.UserAccount.prototype.getAttributeCollection = function () {
13086   return this.attributeManager.getAttributeCollection();
13087 };
13088 
13089 /**
13090  * @private
13091  */        
13092 net.user1.orbiter.UserAccount.prototype.setAttributeManager = function (value) {
13093   this.attributeManager = value;
13094 };
13095 
13096 /**
13097  * @private
13098  */        
13099 net.user1.orbiter.UserAccount.prototype.getAttributeManager = function () {
13100   return this.attributeManager;
13101 };
13102 
13103 /**
13104  * @private
13105  */        
13106 net.user1.orbiter.UserAccount.prototype.getClientManager = function () {
13107   return this._clientManager;
13108 };
13109 
13110 /**
13111  * @private
13112  */        
13113 net.user1.orbiter.UserAccount.prototype.setClientManager = function (value) {
13114   this._clientManager = value;
13115 };
13116 
13117 /**
13118  * @private
13119  */        
13120 net.user1.orbiter.UserAccount.prototype.getRoomManager = function () {
13121   return this._roomManager;
13122 };
13123 
13124 /**
13125  * @private
13126  */        
13127 net.user1.orbiter.UserAccount.prototype.setRoomManager = function (value) {
13128   this._roomManager = value;
13129 };
13130 
13131 /**
13132  * @private
13133  */        
13134 net.user1.orbiter.UserAccount.prototype.getLog = function () {
13135   return this._log;
13136 };
13137 
13138 /**
13139  * @private
13140  */        
13141 net.user1.orbiter.UserAccount.prototype.setLog = function (value) {
13142   this._log = value;
13143 };
13144 
13145 /**
13146  * @private
13147  */        
13148 net.user1.orbiter.UserAccount.prototype.getAccountManager = function () {
13149   return this._accountManager;
13150 };
13151 
13152 /**
13153  * @private
13154  */        
13155 net.user1.orbiter.UserAccount.prototype.setAccountManager = function (value) {
13156   this._accountManager = value;
13157 };
13158 
13159 net.user1.orbiter.UserAccount.prototype.getClient = function () {
13160   var customClient;
13161   this.validateClientReference();
13162   if (this._client != null) {
13163     customClient = this._client.getCustomClient(null);
13164     return customClient == null ? this._client : customClient;
13165   } else {
13166     return null;
13167   }
13168 };
13169 
13170 net.user1.orbiter.UserAccount.prototype.getInternalClient = function () {
13171   this.validateClientReference();
13172   return this._client;
13173 }
13174 
13175 /**
13176  * @private
13177  */        
13178 net.user1.orbiter.UserAccount.prototype.setClient = function (value) {
13179   if (value == null) {
13180     this._client = null;
13181   } else {
13182     if (this._client != value) {
13183       this._client = value;
13184       this._client.setAccount(this);
13185     }
13186   }
13187 };
13188 
13189 /**
13190  * @private
13191  */
13192 net.user1.orbiter.UserAccount.prototype.validateClientReference = function () {
13193   if (this._client != null) {
13194     if (!this._client.isSelf()
13195         && !this._clientManager.isWatchingForClients()
13196         && !this._accountManager.isObservingAccount(this.getUserID()) 
13197         && !this._clientManager.isObservingClient(this._client.getClientID())
13198         && !this._roomManager.clientIsKnown(this._client.getClientID())) {
13199       this.setClient(null);
13200     }
13201   }
13202 };
13203 
13204 //==============================================================================
13205 // IS SELF
13206 //==============================================================================
13207 
13208 net.user1.orbiter.UserAccount.prototype.isSelf = function () {
13209   return this._client == null ? false : this._client.isSelf();
13210 };
13211 
13212 //==============================================================================
13213 // CONNECTION STATE
13214 //==============================================================================
13215 
13216 net.user1.orbiter.UserAccount.prototype.getConnectionState = function () {
13217   if (this.getInternalClient() != null) {
13218     return net.user1.orbiter.ConnectionState.LOGGED_IN;
13219   } else if (!this._accountManager.isObservingAccount(this.getUserID())) {
13220       return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
13221   } else if (this._clientManager.isWatchingForClients()) {
13222     return net.user1.orbiter.ConnectionState.NOT_CONNECTED;
13223   } else {
13224     // Not observing this user, not watching for clients, and no client means
13225     // this account's state is unknown. (This happens when watching for user
13226     // accounts).
13227     return net.user1.orbiter.ConnectionState.UNKNOWN;
13228   }
13229 };
13230 
13231 net.user1.orbiter.UserAccount.prototype.isLoggedIn = function () {
13232   return this.getConnectionState() == net.user1.orbiter.ConnectionState.LOGGED_IN;
13233 };
13234 
13235 //==============================================================================
13236 // USER ID
13237 //==============================================================================
13238 
13239 net.user1.orbiter.UserAccount.prototype.getUserID = function () {
13240   return this.userID;
13241 };
13242 
13243 /**
13244  * @private
13245  */ 
13246 net.user1.orbiter.UserAccount.prototype.setUserID = function (userID) {
13247   if (this.userID != userID) {
13248     this.userID = userID;
13249   }
13250 };
13251 
13252 // =============================================================================
13253 // LOGOFF
13254 // =============================================================================
13255 
13256 net.user1.orbiter.UserAccount.prototype.logoff = function (password) {
13257   this._accountManager.logoff(this.getUserID(), password);
13258 };
13259 
13260 // =============================================================================
13261 // CHANGE PASSWORD
13262 // =============================================================================
13263 
13264 net.user1.orbiter.UserAccount.prototype.changePassword = function (newPassword, oldPassword) {
13265   this._accountManager.changePassword(this.getUserID(), newPassword, oldPassword);
13266 };
13267 
13268 // =============================================================================
13269 // ROLES
13270 // =============================================================================
13271 
13272 net.user1.orbiter.UserAccount.prototype.addRole = function (role) {
13273   this._accountManager.addRole(this.getUserID(), role);
13274 };
13275 
13276 net.user1.orbiter.UserAccount.prototype.removeRole = function (userID, role) {
13277   this._accountManager.removeRole(this.getUserID(), role);
13278 };
13279 
13280 net.user1.orbiter.UserAccount.prototype.isModerator = function () {
13281   var rolesAttr = this.getAttribute(net.user1.orbiter.Tokens.ROLES_ATTR);
13282   var roles;
13283   if (rolesAttr != null) {
13284     return (parseInt(rolesAttr) & UserAccount.FLAG_MODERATOR) > 0;
13285   } else {
13286     this.getLog().warn(this.toString() + " Could not determine moderator status because the account is not synchronized.");
13287     return false;
13288   }
13289 };
13290 
13291 // =============================================================================
13292 // LOGIN/LOGOFF TASKS
13293 // =============================================================================
13294 
13295 /**
13296  * @private
13297  */ 
13298 net.user1.orbiter.UserAccount.prototype.doLoginTasks = function () {
13299   this.fireLogin();
13300 };
13301 
13302 /**
13303  * @private
13304  */ 
13305 net.user1.orbiter.UserAccount.prototype.doLogoffTasks = function () {
13306   this.setClient(null);
13307   this.fireLogoff();
13308 };
13309 
13310 // =============================================================================
13311 // OBSERVATION
13312 // =============================================================================
13313 
13314 net.user1.orbiter.UserAccount.prototype.observe = function () {
13315   this._accountManager.observeAccount(this.getUserID());
13316 };
13317 
13318 net.user1.orbiter.UserAccount.prototype.stopObserving = function () {
13319   this._accountManager.stopObservingAccount(this.getUserID());
13320 };
13321 
13322 // =============================================================================
13323 // ATTRIBUTES: PUBLIC API
13324 // =============================================================================
13325 
13326 net.user1.orbiter.UserAccount.prototype.setAttribute = function (attrName, 
13327                                                                  attrValue, 
13328                                                                  attrScope, 
13329                                                                  isShared, 
13330                                                                  evaluate) {
13331   // Create an integer to hold the attribute options
13332   var attrOptions = net.user1.orbiter.AttributeOptions.FLAG_PERSISTENT
13333                       | (isShared     ? net.user1.orbiter.AttributeOptions.FLAG_SHARED     : 0) 
13334                       | (evaluate     ? net.user1.orbiter.AttributeOptions.FLAG_EVALUATE   : 0);
13335   
13336   // Set the attribute on the server.
13337   this.attributeManager.setAttribute(new net.user1.orbiter.upc.SetClientAttr(attrName, attrValue, attrOptions, attrScope, null, this.getUserID()));
13338 };
13339 
13340 net.user1.orbiter.UserAccount.prototype.deleteAttribute = function (attrName, attrScope) {
13341   var deleteRequest = new net.user1.orbiter.upc.RemoveClientAttr(null, this.getUserID(), attrName, attrScope);
13342   this.attributeManager.deleteAttribute(deleteRequest);
13343 };
13344 
13345 net.user1.orbiter.UserAccount.prototype.getAttribute = function (attrName, attrScope) {
13346   return this.attributeManager.getAttribute(attrName, attrScope);
13347 };
13348 
13349 net.user1.orbiter.UserAccount.prototype.getAttributes = function () {
13350   return this.attributeManager.getAttributes();
13351 }; 
13352 
13353 net.user1.orbiter.UserAccount.prototype.getAttributesByScope = function (scope) {
13354   return this.attributeManager.getAttributesByScope(scope);
13355 };
13356 
13357 //==============================================================================
13358 // TOSTRING
13359 //==============================================================================
13360 
13361 net.user1.orbiter.UserAccount.prototype.toString = function () {
13362   return "[USER_ACCOUNT userid: " + this.getUserID() + ", clientid: " + (this._client == null ? "" : this._client.getClientID()) + "]";
13363 };
13364 
13365 //==============================================================================
13366 // EVENT DISPATCHING
13367 //==============================================================================
13368 
13369 /**
13370  * @private
13371  */
13372 net.user1.orbiter.UserAccount.prototype.fireLogin = function () {
13373   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGIN,
13374                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13375   this.dispatchEvent(e);
13376 };    
13377 
13378 /**
13379  * @private
13380  */
13381 net.user1.orbiter.UserAccount.prototype.fireLogoffResult = function (status) {
13382   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF_RESULT,
13383                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13384   this.dispatchEvent(e);
13385 };    
13386 
13387 /**
13388  * @private
13389  */
13390 net.user1.orbiter.UserAccount.prototype.fireLogoff = function () {
13391   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.LOGOFF,
13392                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13393   this.dispatchEvent(e);
13394 };    
13395 
13396 /**
13397  * @private
13398  */
13399 net.user1.orbiter.UserAccount.prototype.fireChangePasswordResult = function (status) {
13400   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD_RESULT,
13401                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13402   this.dispatchEvent(e);
13403 };    
13404 
13405 /**
13406  * @private
13407  */
13408 net.user1.orbiter.UserAccount.prototype.fireChangePassword = function () {
13409   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.CHANGE_PASSWORD,
13410                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13411   this.dispatchEvent(e);
13412 };    
13413 
13414 /**
13415  * @private
13416  */
13417 net.user1.orbiter.UserAccount.prototype.fireSynchronize = function () {
13418   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.SYNCHRONIZE, 
13419                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13420   this.dispatchEvent(e);
13421 };
13422 
13423 /**
13424  * @private
13425  */
13426 net.user1.orbiter.UserAccount.prototype.fireObserve = function () {
13427   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE, 
13428                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13429   this.dispatchEvent(e);
13430 };
13431 
13432 /**
13433  * @private
13434  */
13435 net.user1.orbiter.UserAccount.prototype.fireObserveResult = function (status) {
13436   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.OBSERVE_RESULT, 
13437                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13438   this.dispatchEvent(e);
13439 };
13440 
13441 /**
13442  * @private
13443  */
13444 net.user1.orbiter.UserAccount.prototype.fireStopObserving = function () {
13445   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING, 
13446                                              net.user1.orbiter.Status.SUCCESS, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13447   this.dispatchEvent(e);
13448 };
13449 
13450 /**
13451  * @private
13452  */
13453 net.user1.orbiter.UserAccount.prototype.fireStopObservingResult = function (status) {
13454   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.STOP_OBSERVING_RESULT, 
13455                                              status, this.getUserID(), (this._client == null ? null : this._client.getClientID()));
13456   this.dispatchEvent(e);
13457 };
13458 
13459 /**
13460  * @private
13461  */
13462 net.user1.orbiter.UserAccount.prototype.fireAddRoleResult = function (role, status) {
13463   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.ADD_ROLE_RESULT, 
13464                                              status, this.getUserID(), 
13465                                              (this._client == null ? null : this._client.getClientID()), role);
13466   this.dispatchEvent(e);
13467 }
13468 
13469 /**
13470  * @private
13471  */
13472 net.user1.orbiter.UserAccount.prototype.fireRemoveRoleResult = function (role, status) {
13473   var e = new net.user1.orbiter.AccountEvent(net.user1.orbiter.AccountEvent.REMOVE_ROLE_RESULT, 
13474                                              status, this.getUserID(), 
13475                                              (this._client == null ? null : this._client.getClientID()), role);
13476   this.dispatchEvent(e);
13477 }
13478 
13479 
13480 
13481 
13482 
13483 
13484 
13485 
13486 
13487 
13488 
13489 
13490 
13491 //==============================================================================
13492 // VALIDATION UTILITIES
13493 //==============================================================================
13494 net.user1.orbiter.Validator = new Object();
13495 
13496 net.user1.orbiter.Validator.isValidRoomID = function (value) {
13497   // Can't be null, nor the empty string
13498   if (value == null || value == "") {
13499     return false;
13500   }
13501   // Can't contain "."
13502   if (value.indexOf(".") != -1) {
13503     return false;
13504   }
13505   // Can't contain RS
13506   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13507     return false;
13508   }
13509   // Can't contain WILDCARD
13510   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13511     return false;
13512   }
13513   
13514   return true;
13515 };
13516 
13517 net.user1.orbiter.Validator.isValidRoomQualifier = function (value) {
13518   if (value == null || value == "") {
13519     return false;
13520   }
13521   // "*" is valid (it means the unnamed qualifier)
13522   if (value == "*") {
13523     return true;
13524   }
13525   
13526   // Can't contain RS
13527   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13528     return false;
13529   }
13530   // Can't contain WILDCARD
13531   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13532     return false;
13533   }
13534   
13535   return true;
13536 };
13537 
13538 net.user1.orbiter.Validator.isValidResolvedRoomID = function (value) {
13539   // Can't be null, nor the empty string
13540   if (value == null || value == "") {
13541     return false;
13542   }
13543   
13544   // Can't contain RS
13545   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13546     return false;
13547   }
13548   // Can't contain WILDCARD
13549   if (value.indexOf(net.user1.orbiter.Tokens.WILDCARD) != -1) {
13550     return false;
13551   }
13552   
13553   return true;
13554 };
13555 
13556 net.user1.orbiter.Validator.isValidAttributeName = function (value) {
13557   // Can't be empty 
13558   if (value == "" || value == null) {
13559     return false;
13560   }
13561   
13562   // Can't contain RS
13563   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13564     return false;
13565   }
13566         
13567   return true;
13568 };
13569 
13570 net.user1.orbiter.Validator.isValidAttributeValue = function (value) {
13571   // Can't contain RS
13572   if (typeof value != "string") {
13573     // Non-string attribute values are coerced to strings at send time
13574     value = value.toString();
13575   }
13576   if (value.indexOf(net.user1.orbiter.Tokens.RS) == -1) {
13577     return true;
13578   } else {
13579     return false;
13580   }
13581 };
13582 
13583 net.user1.orbiter.Validator.isValidAttributeScope = function (value) {
13584   // Can't contain RS
13585   if (value != null) {
13586     return this.isValidResolvedRoomID(value);
13587   } else {
13588     return true;
13589   }
13590 };
13591 
13592 net.user1.orbiter.Validator.isValidModuleName = function (value) {
13593   // Can't be empty (can be null)
13594   if (value == "") {
13595     return false;
13596   }
13597   
13598   // Can't contain RS
13599   if (value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13600     return false;
13601   }
13602   
13603   return true;
13604 };
13605 
13606 net.user1.orbiter.Validator.isValidPassword = function (value) {
13607   // Can't contain RS
13608   if (value != null && value.indexOf(net.user1.orbiter.Tokens.RS) != -1) {
13609     return false;
13610   }
13611   
13612   return true;
13613 };
13614 //==============================================================================
13615 // CLASS DECLARATION
13616 //==============================================================================
13617 /** @class
13618  * The Orbiter class is the root class of every Orbiter application.
13619  * It provides basic tools for connecting to Union server, and gives
13620  * the application access to the core Orbiter system modules.
13621  * Orbiter dispatches the following events:
13622 
13623 <ul class="summary">
13624 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.READY}</li>
13625 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CLOSE}</li>
13626 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED}</li>
13627 <li class="fixedFont">{@link net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE}</li>
13628 </ul>
13629 
13630  * To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
13631  *
13632  * @param configURL The URL of a connection-configuration file. When the file
13633  * finishes loading, the Orbiter client automatically attempts to connect to
13634  * Union Server at the specified address(es). Note that the configuration file
13635  * need not be loaded at construction time; it can be loaded later via Orbiter's
13636  * loadConfig() method. For configuration file details, see loadConfig().
13637  *
13638  * @param traceLogMessages A flag indicating whether to send log messages to the
13639  * JavaScript output console. Applies to environments that support the
13640  * console.log() function only.
13641  *
13642  * @extends net.user1.events.EventDispatcher
13643  */
13644 net.user1.orbiter.Orbiter = function (configURL,
13645                                       traceLogMessages) {
13646   // Call superconstructor
13647   net.user1.events.EventDispatcher.call(this);
13648 
13649   // Initialization. For non-browser environments, set window to null.
13650   this.window = typeof window == "undefined" ? null : window;
13651 
13652   traceLogMessages = traceLogMessages == null ? true : traceLogMessages; 
13653 
13654   this.useSecureConnect = false;
13655   this.statistics = null;
13656   this.sessionID = null;
13657 
13658   // Initialize system versions
13659   this.system = new net.user1.orbiter.System(this.window);
13660                            
13661   // Set up the this.log.
13662   this.log = new net.user1.logger.Logger();
13663 
13664   // Output host version information.
13665   if (typeof navigator != "undefined") {
13666     this.log.info("User Agent: " + navigator.userAgent + " " + navigator.platform);
13667   }
13668   this.log.info("Union Client Version: " + this.system.getClientType() + " " + this.system.getClientVersion().toStringVerbose());
13669   this.log.info("Client UPC Protocol Version: " + this.system.getUPCVersion().toString());
13670   this.consoleLogger = null;
13671 
13672   // Set up the connection manager.
13673   this.connectionMan = new net.user1.orbiter.ConnectionManager(this);
13674   
13675   // Set up the room manager.
13676   this.roomMan = new net.user1.orbiter.RoomManager(this);
13677   
13678   // Set up the message manager.
13679   this.messageMan = new net.user1.orbiter.MessageManager(this.log, this.connectionMan);
13680 
13681   // Set up the server
13682   this.server = new net.user1.orbiter.Server(this);
13683   
13684   // Make the account manager.
13685   this.accountMan = new net.user1.orbiter.AccountManager(this.log);
13686   
13687   // Set up the client manager.
13688   this.clientMan = new net.user1.orbiter.ClientManager(this.roomMan, this.accountMan, this.connectionMan, this.messageMan, this.server, this.log);
13689 
13690   // Set up the account manager.
13691   this.accountMan.setClientManager(this.clientMan);
13692   this.accountMan.setMessageManager(this.messageMan);
13693   this.accountMan.setRoomManager(this.roomMan);
13694 
13695   // Set up the snapshot manager.
13696   this.snapshotMan = new net.user1.orbiter.SnapshotManager(this.messageMan);
13697   
13698   // Set up the core message listener
13699   this.coreMsgListener = new net.user1.orbiter.CoreMessageListener(this);
13700   
13701   // Log the core Reactor events
13702   this.coreEventLogger = new net.user1.orbiter.CoreEventLogger(this.log, this.connectionMan, this.roomMan, 
13703                                              this.accountMan, this.server, this.clientMan,
13704                                              this);
13705 
13706   // Register for ConnectionManager events.
13707   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.READY, 
13708                                  this.readyListener, this);
13709   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.CONNECT_FAILURE, 
13710                                  this.connectFailureListener, this);
13711   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.DISCONNECT, 
13712                                  this.disconnectListener, this);
13713   
13714   // Set up the connection monitor
13715   this.connectionMonitor = new net.user1.orbiter.ConnectionMonitor(this);
13716   this.connectionMonitor.restoreDefaults();
13717 
13718   // Register to be notified when a new connection is about to be opened 
13719   this.connectionMan.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION, 
13720                                       this.selectConnectionListener, this);
13721   
13722   // Enable HTTP failover connections
13723   this.httpFailoverEnabled = true;
13724 
13725   if (traceLogMessages) {
13726     this.enableConsole();
13727   }
13728   
13729   // If the Reactor wasn't constructed with a config argument...
13730   if (configURL == null || configURL == "") {
13731     this.log.info("[ORBITER] Initialization complete.");
13732   } else {
13733     // ...otherwise, retrieve system settings from specified config file.
13734     this.loadConfig(configURL);
13735   }
13736 };
13737 
13738 //==============================================================================
13739 // INHERITANCE
13740 //==============================================================================
13741 net.user1.utils.extend(net.user1.orbiter.Orbiter, net.user1.events.EventDispatcher);
13742  
13743 //==============================================================================
13744 // XML CONFIG METHODS
13745 //==============================================================================
13746 
13747 /**
13748  * Loads the client-configuration file. When the file load completes,
13749  * Orbiter automatically attempts to connect to Union Server using
13750  * the settings specified by the configuration file.
13751  *
13752  * The configuration file has the following format:
13753  *
13754  * <pre>
13755  * <?xml version="1.0"?>
13756  * <config>
13757  *   <connections>
13758  *     <connection host="hostNameOrIP1" port="portNumber1" type="connectionType1" senddelay="milliseconds1" secure="false" />
13759  *     <connection host="hostNameOrIP2" port="portNumber2" type="connectionType2" senddelay="milliseconds2" secure="false" />
13760  *     ...
13761  *     <connection host="hostNameOrIPn" port="portNumbern" type="connectionTypen" senddelay="millisecondsn" secure="false" />
13762  *   </connections>
13763  *   <autoreconnectfrequency>frequency</autoreconnectfrequency>
13764  *   <connectiontimeout>duration</connectiontimeout>
13765  *   <heartbeatfrequency>frequency</heartbeatfrequency>
13766  *   <readytimeout>timeout</readytimeout>
13767  *   <loglevel>level</loglevel>
13768  * </config>
13769  * </pre>
13770  *
13771  * When the <code>secure</code> attribute is true, communication is
13772  * conducted over WSS or HTTPS using the environment's TLS implementation.
13773  */
13774 net.user1.orbiter.Orbiter.prototype.loadConfig = function (configURL) {
13775   this.log.info("[ORBITER] Loading config from " + configURL +".");
13776   var request = new XMLHttpRequest();
13777   var self = this;
13778   
13779   request.onerror = function () {
13780     self.configErrorListener();
13781   };
13782   
13783   request.onreadystatechange = function (state) {
13784     if (request.readyState == 4) {
13785       self.configLoadCompleteListener(request);
13786     }
13787   }
13788   request.open("GET", configURL);
13789   request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
13790   request.send(null);
13791 };
13792 
13793 /**
13794  * @private
13795  */
13796 net.user1.orbiter.Orbiter.prototype.getTextForNode = function (tree, tagname) {
13797   var nodes = tree.getElementsByTagName(tagname);
13798   var node;
13799   if (nodes.length > 0) {
13800     node = nodes[0];
13801   }
13802   
13803   if (node != null 
13804       && node.firstChild != null
13805       && node.firstChild.nodeType == 3
13806       && node.firstChild.nodeValue.length > 0) {
13807     return node.firstChild.nodeValue;
13808   } else {
13809     return null;
13810   }
13811 };
13812 
13813 
13814 /**
13815  * @private
13816  */
13817 net.user1.orbiter.Orbiter.prototype.configLoadCompleteListener = function (request) {
13818   var config = request.responseXML;
13819   if ((request.status != 200 && request.status != 0) || config == null) {
13820     this.log.error("[ORBITER] Configuration file failed to load.");
13821     return;
13822   }
13823   this.log.error("[ORBITER] Configuration file loaded.");
13824   try {
13825     var loglevel = this.getTextForNode(config, "logLevel");
13826     if (loglevel != null) {
13827       this.log.setLevel(loglevel);
13828     }
13829 
13830     var autoreconnectfrequencyNodes = config.getElementsByTagName("autoreconnectfrequency");
13831     var autoreconnectfrequencyNode = null;
13832     if (autoreconnectfrequencyNodes.length == 1) {
13833       autoreconnectfrequencyNode = autoreconnectfrequencyNodes[0];
13834       var nodetext = this.getTextForNode(config, "autoreconnectfrequency");
13835       if (nodetext != null && !isNaN(parseInt(nodetext))) {
13836         this.connectionMonitor.setAutoReconnectFrequency(
13837             parseInt(nodetext),
13838             parseInt(nodetext),
13839             autoreconnectfrequencyNode.getAttribute("delayfirstattempt") == null ? false :
13840             autoreconnectfrequencyNode.getAttribute("delayfirstattempt").toLowerCase() == "true"
13841           );
13842       } else {
13843         this.connectionMonitor.setAutoReconnectFrequency(
13844             parseInt(autoreconnectfrequencyNode.getAttribute("minms")),
13845             parseInt(autoreconnectfrequencyNode.getAttribute("maxms")),
13846             autoreconnectfrequencyNode.getAttribute("delayfirstattempt") == null ? false :
13847               autoreconnectfrequencyNode.getAttribute("delayfirstattempt").toLowerCase() == "true"
13848           );
13849       }
13850       if (autoreconnectfrequencyNode.getAttribute("maxattempts") != null
13851           && autoreconnectfrequencyNode.getAttribute("maxattempts").length > 0) {
13852         this.connectionMonitor.setAutoReconnectAttemptLimit(
13853             parseInt(autoreconnectfrequencyNode.getAttribute("maxattempts"))
13854           );
13855       }
13856     }
13857 
13858     var connectiontimeout = this.getTextForNode(config, "connectionTimeout"); 
13859     if (connectiontimeout != null) {
13860       this.connectionMonitor.setConnectionTimeout(parseInt(connectiontimeout));
13861     }
13862     
13863     var heartbeatfrequency = this.getTextForNode(config, "heartbeatFrequency"); 
13864     if (heartbeatfrequency != null) {
13865       this.connectionMonitor.setHeartbeatFrequency(parseInt(heartbeatfrequency));
13866     }
13867     
13868     var readytimeout = this.getTextForNode(config, "readyTimeout"); 
13869     if (readytimeout != null) {
13870       this.connectionMan.setReadyTimeout(parseInt(readytimeout));
13871     }
13872     
13873     var connections = config.getElementsByTagName("connection");
13874     if (connections.length == 0) {      
13875       this.log.error("[ORBITER] No connections specified in Orbiter configuration file.");
13876       return;
13877     }
13878     
13879     // Make connections
13880     var connection;
13881     var host;
13882     var port;
13883     var type;
13884     var secure;
13885     var sendDelay;
13886     
13887     for (var i = 0; i < connections.length; i++) {
13888       connection = connections[i];
13889       host = connection.getAttribute("host");
13890       port = connection.getAttribute("port");
13891       type = connection.getAttribute("type");
13892       if (type != null) {
13893         type = type.toUpperCase();
13894       }
13895       secure = connection.getAttribute("secure");
13896       sendDelay = connection.getAttribute("senddelay");
13897   
13898       switch (type) {
13899         // No type means make a socket connection with an http backup
13900         case null:
13901           if (secure === "true") {
13902             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET, -1);
13903             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP, sendDelay);
13904           } else {
13905             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.WEBSOCKET, -1);
13906             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.HTTP, sendDelay);
13907           }
13908           break;
13909 
13910         case net.user1.orbiter.ConnectionType.WEBSOCKET:
13911           if (secure === "true") {
13912             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET, -1);
13913           } else {
13914             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.WEBSOCKET, -1);
13915           }
13916           break;
13917 
13918         case net.user1.orbiter.ConnectionType.HTTP:
13919           if (secure === "true") {
13920             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP, sendDelay);
13921           } else {
13922             this.buildConnection(host, port, net.user1.orbiter.ConnectionType.HTTP, sendDelay);
13923           }
13924           break;
13925         
13926         default:
13927           this.log.error("[ORBITER] Unrecognized connection type in Orbiter configuration file: [" + type + "]. Connection ignored.");
13928       }
13929     }
13930   } catch (error) {
13931     this.log.error("[ORBITER] Error parsing connection in Orbiter configuration file: \n" 
13932                    + request.responseText + "\n" + error.toString());
13933   }
13934   
13935   this.connect();
13936 };
13937 
13938 /** @private */   
13939 net.user1.orbiter.Orbiter.prototype.buildConnection = function (host, port, type, sendDelay) {
13940   var connection;
13941   
13942   switch (type) {
13943     case net.user1.orbiter.ConnectionType.HTTP:
13944       if (this.system.hasHTTPDirectConnection()) {
13945         connection = new net.user1.orbiter.HTTPDirectConnection();
13946       } else {
13947         connection = new net.user1.orbiter.HTTPIFrameConnection();
13948       }
13949       break;
13950 
13951     case net.user1.orbiter.ConnectionType.SECURE_HTTP:
13952       if (this.system.hasHTTPDirectConnection()) {
13953         connection = new net.user1.orbiter.SecureHTTPDirectConnection();
13954       } else {
13955         connection = new net.user1.orbiter.SecureHTTPIFrameConnection();
13956       }
13957       break;
13958 
13959     case net.user1.orbiter.ConnectionType.WEBSOCKET:
13960       connection = new net.user1.orbiter.WebSocketConnection();
13961       break;
13962 
13963     case net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET:
13964       connection = new net.user1.orbiter.SecureWebSocketConnection();
13965       break;
13966 
13967     default:
13968       throw new Error("[ORBITER] Error at buildConnection(). Invalid type specified: [" + type + "]");
13969   }
13970   
13971   try {
13972     connection.setServer(host, port);
13973   } catch (e) {
13974     this.log.error("[CONNECTION] " + connection.toString() + " " + e);
13975   } finally {
13976     this.connectionMan.addConnection(connection);
13977     if (connection instanceof net.user1.orbiter.HTTPConnection) {
13978       // Set delay after adding connection so the connection object has
13979       // access to this Orbiter object
13980       if (sendDelay != null && sendDelay != "") {
13981         connection.setSendDelay(sendDelay);
13982       }
13983     }
13984   }
13985 };
13986 
13987 /**
13988  * @private
13989  */
13990 net.user1.orbiter.Orbiter.prototype.configErrorListener = function (e) {
13991   this.log.fatal("[ORBITER] Configuration file could not be loaded.");
13992 };
13993 
13994 //==============================================================================
13995 // CONNECTION METHODS
13996 //==============================================================================
13997 
13998 /**
13999  * <p>
14000  * The connect() method attempts to connect to Union Server at the specified
14001  * host and ports. If no host and ports are specified, Orbiter attempts to
14002  * connect using the ConnectionManager's current list of hosts and ports.
14003  * </p>
14004  *
14005  * @param host
14006  * @param port1
14007  * @param port2
14008  * @param ...
14009  * @param portn
14010  */
14011 net.user1.orbiter.Orbiter.prototype.connect = function (host) {
14012   this.useSecureConnect = false;
14013   this.doConnect.apply(this, arguments);
14014 };
14015 
14016 /**
14017  * <p>
14018  * The secureConnect() method is identical to the connect() method, except that
14019  * it uses an encrypted connection (TLS or SSL) rather than an
14020  * unencrypted connection. Before secureConnect() can be used, Union Server
14021  * must be configured to accept client communications over a secure gateway,
14022  * which includes the installation of a server-side security certificate. For
14023  * instructions on configuring Union Server for secure communications, see
14024  * Union Server's documentation at http://unionplatform.com.
14025  * </p>
14026  *
14027  * @see net.user1.orbiter.Orbiter#connect
14028  */
14029 net.user1.orbiter.Orbiter.prototype.secureConnect = function (host) {
14030   this.useSecureConnect = true;
14031   this.doConnect.apply(this, arguments);
14032 };
14033 
14034 /**
14035  * @private
14036  */
14037 net.user1.orbiter.Orbiter.prototype.doConnect = function (host) {
14038   var ports = Array.prototype.slice.call(arguments).slice(1);
14039   if (host != null) {
14040     this.setServer.apply(this, [host].concat(ports));
14041   }
14042   this.log.info("[ORBITER] Connecting to Union...");
14043   this.connectionMan.connect();
14044 };
14045 
14046 net.user1.orbiter.Orbiter.prototype.disconnect = function () {
14047   this.connectionMan.disconnect();
14048 };
14049 
14050 net.user1.orbiter.Orbiter.prototype.setServer = function (host) {
14051   var ports = Array.prototype.slice.call(arguments).slice(1);
14052   if (host != null && ports.length > 0) {
14053     if (this.connectionMan.getConnections().length > 0) {
14054       this.connectionMan.removeAllConnections();
14055     }
14056     // Where possible, create WebSocket connections for the specified
14057     // host and its ports.
14058     var connectionType;
14059     if (this.system.hasWebSocket()) {
14060       for (var i = 1; i < arguments.length; i++) {
14061         connectionType = this.useSecureConnect
14062                          ? net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET
14063                          : net.user1.orbiter.ConnectionType.WEBSOCKET;
14064         this.buildConnection(host, arguments[i], connectionType, -1);
14065       }
14066     } else {
14067       this.log.info("[ORBITER] WebSocket not found in host environment. Trying HTTP.");
14068     }
14069     // Next, if failover is enabled or WebSocket is not supported, create HTTPConnections
14070     if (this.isHTTPFailoverEnabled() || !this.system.hasWebSocket()) {
14071       for (i = 1; i < arguments.length; i++) {
14072         connectionType = this.useSecureConnect
14073                          ? net.user1.orbiter.ConnectionType.SECURE_HTTP
14074                          : net.user1.orbiter.ConnectionType.HTTP;
14075         this.buildConnection(host,
14076                              arguments[i], 
14077                              connectionType,
14078                              net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY);
14079       }
14080     }
14081   } else {
14082     this.log.error("[ORBITER] setServer() failed. Invalid host [" + host + "] or port [" + ports.join(",") + "].");
14083   }
14084 };
14085 
14086 net.user1.orbiter.Orbiter.prototype.isReady = function () {
14087   return this.connectionMan.isReady();
14088 };
14089 
14090 //==============================================================================
14091 // HTTP FAILOVER CONFIGURATION
14092 //==============================================================================
14093 
14094 net.user1.orbiter.Orbiter.prototype.enableHTTPFailover = function () {
14095   this.httpFailoverEnabled = true;
14096 };
14097 
14098 net.user1.orbiter.Orbiter.prototype.disableHTTPFailover = function () {
14099   this.httpFailoverEnabled = false;
14100 };
14101 
14102 net.user1.orbiter.Orbiter.prototype.isHTTPFailoverEnabled = function () {
14103   return this.httpFailoverEnabled;
14104 };
14105 
14106 //==============================================================================
14107 // STATISTICS MANAGEMENT
14108 //==============================================================================
14109 
14110 net.user1.orbiter.Orbiter.prototype.enableStatistics = function () {
14111   if (this.statistics == null) {
14112     this.statistics = new net.user1.orbiter.Statistics(this);
14113   }
14114 }
14115 
14116 net.user1.orbiter.Orbiter.prototype.disableStatistics = function () {
14117   if (this.statistics != null) {
14118     this.statistics.stop();
14119   }
14120 }
14121 
14122 net.user1.orbiter.Orbiter.prototype.getStatistics = function () {
14123   return this.statistics;
14124 }
14125 
14126 //==============================================================================  
14127 // MANAGER AND SERVICE RETRIEVAL
14128 //==============================================================================
14129 net.user1.orbiter.Orbiter.prototype.getSystem = function () {
14130   return this.system;
14131 };
14132 
14133 net.user1.orbiter.Orbiter.prototype.getRoomManager = function () {
14134   return this.roomMan;
14135 };
14136 
14137 net.user1.orbiter.Orbiter.prototype.getConnectionManager = function () {
14138   return this.connectionMan;
14139 };
14140 
14141 net.user1.orbiter.Orbiter.prototype.getClientManager = function () {
14142   return this.clientMan;
14143 };
14144 
14145 net.user1.orbiter.Orbiter.prototype.getAccountManager = function () {
14146   return this.accountMan;
14147 };
14148 
14149 net.user1.orbiter.Orbiter.prototype.getMessageManager = function () {
14150   return this.messageMan;
14151 };
14152 
14153 net.user1.orbiter.Orbiter.prototype.getServer = function () {
14154   return this.server;
14155 };
14156 
14157 net.user1.orbiter.Orbiter.prototype.getConnectionMonitor = function () {
14158   return this.connectionMonitor;
14159 };
14160 
14161 /**
14162  * @private
14163  */
14164 net.user1.orbiter.Orbiter.prototype.getCoreMessageListener = function () {
14165   return this.coreMsgListener;
14166 }
14167 
14168 net.user1.orbiter.Orbiter.prototype.getLog = function () {
14169   return this.log;
14170 }
14171 
14172 net.user1.orbiter.Orbiter.prototype.self = function () {
14173   var customGlobalClient;
14174   
14175   if (this.clientMan == null || !this.isReady()) {
14176     return null;
14177   } else {
14178     customGlobalClient = this.clientMan.self().getCustomClient(null);
14179     if (customGlobalClient != null) {
14180       return customGlobalClient;
14181     } else {
14182       return this.clientMan.self();
14183     }
14184   } 
14185 };
14186 
14187 /**
14188  * @private
14189  */    
14190 net.user1.orbiter.Orbiter.prototype.getSnapshotManager = function () {
14191   return this.snapshotMan;
14192 };
14193 
14194 //==============================================================================
14195 // SNAPSHOT API
14196 //==============================================================================
14197 
14198 net.user1.orbiter.Orbiter.prototype.updateSnapshot = function (snapshot) {
14199   this.snapshotMan.updateSnapshot(snapshot);
14200 }
14201 
14202 //==============================================================================
14203 // CONNECTION EVENT LISTENERS
14204 //==============================================================================
14205 
14206 /**
14207  * @private 
14208  * Responds to a connection failure. 
14209  */
14210 net.user1.orbiter.Orbiter.prototype.connectFailureListener = function (e) {
14211   // Tell listeners that the connection is now closed.
14212   this.fireClose();
14213 };
14214 
14215 /**
14216  * @private 
14217  * Triggers a CLOSE event when the connection is lost. 
14218  */
14219 net.user1.orbiter.Orbiter.prototype.disconnectListener = function (e) {
14220   this.accountMan.cleanup();
14221   this.roomMan.cleanup();
14222   this.clientMan.cleanup();
14223   this.server.cleanup();
14224   this.fireClose();
14225 };
14226 
14227 /**
14228  * @private 
14229  * Triggers a READY event when the connection is ready. 
14230  */
14231 net.user1.orbiter.Orbiter.prototype.readyListener = function (e) {
14232   this.fireReady();
14233 };
14234 
14235 net.user1.orbiter.Orbiter.prototype.selectConnectionListener = function (e) {
14236   this.messageMan.addMessageListener(net.user1.orbiter.UPC.SERVER_HELLO, this.u66, this);
14237   this.messageMan.addMessageListener(net.user1.orbiter.UPC.CONNECTION_REFUSED, this.u164, this);
14238 }
14239 
14240 //==============================================================================
14241 // CLIENT ID
14242 //==============================================================================
14243 net.user1.orbiter.Orbiter.prototype.getClientID = function () {
14244   return this.self() ? this.self().getClientID() : "";
14245 };
14246 
14247 //==============================================================================
14248 // EVENT DISPATCHING
14249 //==============================================================================
14250 
14251 /**
14252  * @private
14253  * Notifies listeners that this Orbiter object's connection to the server
14254  * was lost or could not be established.
14255  */
14256 net.user1.orbiter.Orbiter.prototype.fireClose = function () {
14257   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CLOSE));
14258 };
14259 
14260 /**
14261  * @private
14262  * Notifies listeners that the Orbiter is fully initialized and 
14263  * ready to transact with the server.
14264  */
14265 net.user1.orbiter.Orbiter.prototype.fireReady = function () {
14266   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.READY));
14267 };
14268 
14269 /**
14270  * @private
14271  * Notifies listeners that the Orbiter is fully initialized and 
14272  * ready to transact with the server.
14273  */
14274 net.user1.orbiter.Orbiter.prototype.fireProtocolIncompatible = function (serverUPCVersion) {
14275   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.PROTOCOL_INCOMPATIBLE,
14276                                  serverUPCVersion));
14277 };
14278 
14279 /**
14280  * @private
14281  * Notifies listeners that the server refused the connection.
14282  */
14283 net.user1.orbiter.Orbiter.prototype.dispatchConnectRefused = function (refusal) {
14284   this.dispatchEvent(new net.user1.orbiter.OrbiterEvent(net.user1.orbiter.OrbiterEvent.CONNECT_REFUSED,
14285                                  null, refusal));
14286 };
14287 
14288 //==============================================================================
14289 // UPC Listeners
14290 //==============================================================================
14291 
14292 /**
14293  * @private
14294  * SERVER_HELLO
14295  */
14296 net.user1.orbiter.Orbiter.prototype.u66 = function (serverVersion, 
14297                                                     sessionID,
14298                                                     serverUPCVersionString,
14299                                                     protocolCompatible,
14300                                                     affinityAddress,
14301                                                     affinityDuration) {
14302   var serverUPCVersion = new net.user1.orbiter.VersionNumber();
14303   serverUPCVersion.fromVersionString(serverUPCVersionString);
14304   if (protocolCompatible == "false") {
14305     this.fireProtocolIncompatible(serverUPCVersion);
14306   }
14307 };
14308 
14309 /**
14310  * @private
14311  * CONNECTION_REFUSED
14312  */
14313 net.user1.orbiter.Orbiter.prototype.u164 = function (reason, description) {
14314   this.connectionMonitor.setAutoReconnectFrequency(-1);
14315   this.dispatchConnectRefused(new net.user1.orbiter.ConnectionRefusal(reason, description));
14316 };
14317 
14318 //==============================================================================
14319 // SESSION ID
14320 //==============================================================================
14321     
14322 net.user1.orbiter.Orbiter.prototype.getSessionID = function () {
14323   return this.sessionID == null ? "" : this.sessionID;
14324 };
14325     
14326 /**
14327  * @private
14328  */        
14329 net.user1.orbiter.Orbiter.prototype.setSessionID = function (id) {
14330   this.sessionID = id;
14331 };
14332 
14333 //==============================================================================    
14334 // CONSOLE LOGGING
14335 //==============================================================================
14336 net.user1.orbiter.Orbiter.prototype.enableConsole = function () {
14337   if (this.consoleLogger == null) {
14338     this.consoleLogger = new net.user1.logger.ConsoleLogger(this.log);
14339   }
14340 };
14341 
14342 net.user1.orbiter.Orbiter.prototype.disableConsole = function () {
14343   if (this.consoleLogger != null) {
14344     this.consoleLogger.dispose();
14345     this.consoleLogger = null;
14346   }
14347 };
14348 
14349 //==============================================================================
14350 // CLEANUP
14351 //==============================================================================
14352 
14353 /**
14354  * Permanently disables this object and releases all of its
14355  * resources. Once dispose() is called, the object can never
14356  * be used again. Use dispose() only when purging an object
14357  * from memory, as is required when unloading an iframe. 
14358  * 
14359  * To simply disconnect an Orbiter object, use disconnect().
14360  */
14361 net.user1.orbiter.Orbiter.prototype.dispose = function () {
14362   this.log.info("[ORBITER] Beginning disposal of all resources...");
14363   this.connectionMan.dispose();
14364   this.roomMan.dispose();
14365   this.connectionMonitor.dispose();
14366   this.clientMan.dispose();
14367   this.messageMan.dispose();
14368   if (this.statistics != null) {
14369     this.statistics.stop();
14370   }
14371   this.log.info("[ORBITER] Disposal complete.");
14372 }
14373 //==============================================================================
14374 // CONNECTION STATE CONSTANTS
14375 //==============================================================================
14376 /** @class */
14377 net.user1.orbiter.ConnectionState = new Object();
14378 /** @constant */
14379 net.user1.orbiter.ConnectionState.UNKNOWN                    = -1;
14380 /** @constant */
14381 net.user1.orbiter.ConnectionState.NOT_CONNECTED              = 0;
14382 /** @constant */
14383 net.user1.orbiter.ConnectionState.READY                      = 1;
14384 /** @constant */
14385 net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS     = 2;
14386 /** @constant */
14387 net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS  = 3;
14388 /** @constant */
14389 net.user1.orbiter.ConnectionState.LOGGED_IN                  = 4;
14390 //==============================================================================
14391 // CLASS DECLARATION
14392 //==============================================================================
14393 /** @class
14394     @extends net.user1.events.Event
14395 */
14396 net.user1.orbiter.ConnectionEvent = function (type, upc, data, connection, status) {
14397   net.user1.events.Event.call(this, type);
14398   
14399   this.upc = upc;
14400   this.data = data;
14401   this.connection = connection
14402   this.status = status;
14403 };
14404 
14405 //==============================================================================
14406 // INHERITANCE
14407 //==============================================================================
14408 net.user1.utils.extend(net.user1.orbiter.ConnectionEvent, net.user1.events.Event);
14409 
14410 //==============================================================================
14411 // STATIC VARIABLES
14412 //==============================================================================
14413 
14414 /** @constant */
14415 net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT = "BEGIN_CONNECT";
14416 /** @constant */
14417 net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE = "BEGIN_HANDSHAKE";
14418 /** @constant */
14419 net.user1.orbiter.ConnectionEvent.READY = "READY";
14420 /** @constant */
14421 net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE = "CONNECT_FAILURE";
14422 /** @constant */
14423 net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT = "CLIENT_KILL_CONNECT";
14424 /** @constant */
14425 net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT = "SERVER_KILL_CONNECT";
14426 /** @constant */
14427 net.user1.orbiter.ConnectionEvent.DISCONNECT = "DISCONNECT";
14428 /** @constant */
14429 net.user1.orbiter.ConnectionEvent.RECEIVE_UPC = "RECEIVE_UPC";
14430 /** @constant */
14431 net.user1.orbiter.ConnectionEvent.SEND_DATA = "SEND_DATA";
14432 /** @constant */
14433 net.user1.orbiter.ConnectionEvent.RECEIVE_DATA = "RECEIVE_DATA";
14434 /** @constant */
14435 net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED = "SESSION_TERMINATED";
14436 /** @constant */
14437 net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND = "SESSION_NOT_FOUND";
14438   
14439 //==============================================================================
14440 // INSTANCE METHODS
14441 //==============================================================================
14442 
14443 net.user1.orbiter.ConnectionEvent.prototype.getUPC = function () {
14444   return this.upc;
14445 }
14446 
14447 net.user1.orbiter.ConnectionEvent.prototype.getData = function () {
14448   return this.data;
14449 }
14450 
14451 net.user1.orbiter.ConnectionEvent.prototype.getStatus = function () {
14452   return this.status;
14453 }
14454 
14455 net.user1.orbiter.ConnectionEvent.prototype.toString = function () {
14456   return "[object ConnectionEvent]";
14457 }  
14458 
14459 //==============================================================================
14460 // HTTP REQUEST MODE CONSTANTS
14461 //==============================================================================
14462 /** @class */
14463 net.user1.orbiter.ConnectionType = new Object();
14464 /** @constant */
14465 net.user1.orbiter.ConnectionType.HTTP =  "HTTP";
14466 /** @constant */
14467 net.user1.orbiter.ConnectionType.SECURE_HTTP =  "SECURE_HTTP";
14468 /** @constant */
14469 net.user1.orbiter.ConnectionType.WEBSOCKET =  "WEBSOCKET";
14470 /** @constant */
14471 net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET =  "SECURE_WEBSOCKET";
14472 //==============================================================================
14473 // CLASS DECLARATION
14474 //==============================================================================
14475 /** @class
14476  * Connection is the abstract superclass of HTTPConnection and WebSocketConnection;
14477  * it is used internally by Orbiter, and is not intended for direct use by Orbiter
14478  * developers. For information on communication with Union Server, see
14479  * Orbiter's connect() method, the WebSocketConnection class and the
14480  * HTTPDirectConnection and HTTPIFrameConnection classes.
14481  *
14482  * The Connection class dispatches the following events:
14483 
14484 <ul class="summary">
14485 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT}</li>
14486 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE}</li>
14487 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.READY}</li>
14488 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE}</li>
14489 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT}</li>
14490 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT}</li>
14491 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.DISCONNECT}</li>
14492 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_UPC}</li>
14493 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SEND_DATA}</li>
14494 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.RECEIVE_DATA}</li>
14495 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED}</li>
14496 <li class="fixedFont">{@link net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND}</li>
14497 </ul>
14498 
14499 To register for events, use {@link net.user1.events.EventDispatcher#addEventListener}.
14500 
14501     @extends net.user1.events.EventDispatcher
14502 
14503  *
14504  * @see net.user1.orbiter.Orbiter#connect
14505  * @see net.user1.orbiter.Orbiter#secureConnect
14506  * @see net.user1.orbiter.HTTPDirectConnection
14507  * @see net.user1.orbiter.HTTPIFrameConnection
14508  * @see net.user1.orbiter.WebSocketConnection
14509  * @see net.user1.orbiter.SecureHTTPDirectConnection
14510  * @see net.user1.orbiter.SecureHTTPIFrameConnection
14511  * @see net.user1.orbiter.SecureWebSocketConnection
14512  */
14513 net.user1.orbiter.Connection = function (host, port, type) {
14514   // Call superconstructor
14515   net.user1.events.EventDispatcher.call(this);
14516 
14517   // Variables
14518   this.mostRecentConnectAchievedReady = false;
14519   this.mostRecentConnectTimedOut = false;
14520   this.readyCount = 0;
14521   this.connectAttemptCount = 0;
14522   this.connectAbortCount = 0;
14523   this.readyTimeoutID = 0;
14524   this.readyTimeout = 0;
14525   this.orbiter = null;
14526   this.disposed = false;
14527   this.requestedHost = null;
14528   
14529   // Initialization
14530   this.setServer(host, port);
14531   this.connectionType = type;
14532   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
14533 };
14534 
14535 //==============================================================================    
14536 // INHERITANCE
14537 //============================================================================== 
14538 net.user1.utils.extend(net.user1.orbiter.Connection, net.user1.events.EventDispatcher);
14539 
14540 //==============================================================================    
14541 // DEPENDENCIES
14542 //============================================================================== 
14543 /** @private */
14544 net.user1.orbiter.Connection.prototype.setOrbiter = function (orbiter) {
14545   if (this.orbiter != null) {
14546     this.orbiter.getMessageManager().removeMessageListener("u63", this.u63);
14547     this.orbiter.getMessageManager().removeMessageListener("u66", this.u66);
14548     this.orbiter.getMessageManager().removeMessageListener("u84", this.u84);
14549     this.orbiter.getMessageManager().removeMessageListener("u85", this.u85);
14550   }
14551   this.orbiter = orbiter;
14552 }
14553   
14554 //==============================================================================    
14555 // CONNECT/DISCONNECT
14556 //============================================================================== 
14557 net.user1.orbiter.Connection.prototype.connect = function () {
14558   this.disconnect();
14559   this.applyAffinity();
14560   this.orbiter.getLog().info(this.toString() + " Attempting connection...");
14561   this.connectAttemptCount++;
14562   this.mostRecentConnectAchievedReady = false;
14563   this.mostRecentConnectTimedOut = false;
14564   this.connectionState = net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS;
14565   // Start the ready timer. Ready state must be achieved before the timer
14566   // completes or the connection will auto-disconnect.
14567   this.startReadyTimer();
14568   this.dispatchBeginConnect();
14569 }
14570   
14571 net.user1.orbiter.Connection.prototype.disconnect = function () {
14572   var state = this.connectionState;
14573  
14574   if (state != net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
14575     this.deactivateConnection();
14576  
14577     if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
14578       this.connectAbortCount++;
14579       this.dispatchConnectFailure("Client closed connection before READY state was achieved.");
14580     } else {
14581       this.dispatchClientKillConnect();
14582     }
14583   }
14584 }
14585     
14586 /** @private */
14587 net.user1.orbiter.Connection.prototype.deactivateConnection = function () {
14588   this.connectionState = net.user1.orbiter.ConnectionState.NOT_CONNECTED;
14589   this.stopReadyTimer();
14590   this.orbiter.setSessionID("");
14591 }
14592   
14593 //==============================================================================    
14594 // CONNECTION CONFIGURATION
14595 //==============================================================================    
14596 net.user1.orbiter.Connection.prototype.setServer = function (host,
14597                                                              port) {
14598   this.requestedHost = host;
14599       
14600   // Check for valid ports
14601   if (port < 1 || port > 65536) {
14602     throw new Error("Illegal port specified [" + port + "]. Must be greater than 0 and less than 65537.");
14603   }
14604   this.port  = port;
14605 }
14606 
14607 net.user1.orbiter.Connection.prototype.getRequestedHost = function () {
14608   return this.requestedHost;
14609 };
14610 
14611 net.user1.orbiter.Connection.prototype.getHost = function () {
14612   if (this.host == null) {
14613     return this.getRequestedHost();
14614   } else {
14615     return this.host;
14616   }
14617 };
14618 
14619 net.user1.orbiter.Connection.prototype.getPort = function () {
14620   return this.port;
14621 };
14622 
14623 net.user1.orbiter.Connection.prototype.getType = function () {
14624   return this.connectionType;
14625 };
14626     
14627 //==============================================================================
14628 // READY HANDSHAKE
14629 //==============================================================================
14630 /** @private */
14631 net.user1.orbiter.Connection.prototype.beginReadyHandshake = function () {
14632   this.dispatchBeginHandshake();
14633   
14634   if (!this.orbiter.getMessageManager().hasMessageListener("u63", this.u63)) {
14635     this.orbiter.getMessageManager().addMessageListener("u63", this.u63, this);
14636     this.orbiter.getMessageManager().addMessageListener("u66", this.u66, this);
14637     this.orbiter.getMessageManager().addMessageListener("u84", this.u84, this);
14638     this.orbiter.getMessageManager().addMessageListener("u85", this.u85, this);
14639   }
14640   
14641   this.sendHello();
14642 }
14643 
14644 /** @private */
14645 net.user1.orbiter.Connection.prototype.sendHello = function() {
14646   var helloString = this.buildHelloMessage();
14647   this.orbiter.getLog().debug(this.toString() + " Sending CLIENT_HELLO: " + helloString);
14648   this.transmitHelloMessage(helloString);
14649 }
14650 
14651 /** @private */
14652 net.user1.orbiter.Connection.prototype.buildHelloMessage = function () {
14653   var helloString = "<U><M>u65</M>"
14654     + "<L>"
14655     + "<A>" + this.orbiter.getSystem().getClientType() + "</A>"
14656     + "<A>" + (typeof navigator != "undefined" ? navigator.userAgent + ";" : "") 
14657     + this.orbiter.getSystem().getClientVersion().toStringVerbose() + "</A>"
14658     + "<A>" + this.orbiter.getSystem().getUPCVersion().toString() + "</A></L></U>";
14659   return helloString;
14660 }
14661 
14662 /** @private */
14663 net.user1.orbiter.Connection.prototype.transmitHelloMessage = function (helloString) {
14664   this.send(helloString);
14665 }
14666     
14667 //==============================================================================
14668 // READY TIMER
14669 //==============================================================================
14670 /** @private */
14671 net.user1.orbiter.Connection.prototype.readyTimerListener = function () {
14672   this.stopReadyTimer();
14673   if (this.connectionState == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
14674     this.orbiter.getLog().warn("[CONNECTION] " + this.toString() + " Failed to achieve" + 
14675             " ready state after " + this.readyTimeout + "ms. Aborting connection...");
14676     this.mostRecentConnectTimedOut = true;
14677     this.disconnect();
14678   }
14679 }
14680 
14681 /** @private */
14682 net.user1.orbiter.Connection.prototype.stopReadyTimer = function () {
14683   if (this.readyTimeoutID != -1) {
14684     clearTimeout(this.readyTimeoutID);
14685   }
14686 }
14687 
14688 /** @private */
14689 net.user1.orbiter.Connection.prototype.startReadyTimer = function () {
14690   var currentObj = this;
14691   var callback   = this.readyTimerListener;
14692   this.stopReadyTimer();
14693   this.readyTimeout = this.orbiter.getConnectionManager().getReadyTimeout();
14694   var self = this;
14695   this.readyTimeoutID = setTimeout (function () {
14696     callback.call(currentObj);
14697   }, self.readyTimeout);
14698 }
14699 
14700 //==============================================================================
14701 // READY STATE ACCESS
14702 //==============================================================================
14703 /** @private */
14704 net.user1.orbiter.Connection.prototype.getReadyCount = function () {
14705   return this.readyCount;
14706 }
14707     
14708 net.user1.orbiter.Connection.prototype.isReady = function () {
14709   return this.connectionState == net.user1.orbiter.ConnectionState.READY;
14710 }
14711 
14712 /** @private */
14713 net.user1.orbiter.Connection.prototype.isValid = function () {
14714   if (this.mostRecentConnectAchievedReady) {
14715     this.orbiter.getLog().debug(this.toString() + " Connection is"
14716       + " valid because its last connection attempt succeeded.");
14717     return true;
14718   }
14719   
14720   if (this.connectAttemptCount == 0) {
14721     this.orbiter.getLog().debug(this.toString() + " Connection is"
14722       + " valid because it has either never attempted to connect, or has not"
14723       + " attempted to connect since its last successful connection.");
14724     return true;
14725   }
14726   
14727   if ((this.connectAttemptCount > 0) && 
14728       (this.connectAttemptCount == this.connectAbortCount)
14729       && !this.mostRecentConnectTimedOut) {
14730     this.orbiter.getLog().debug(this.toString() + " Connection is"
14731       + " valid because either all connection attempts ever or all"
14732       + " connection attempts since its last successful connection were"
14733       + " aborted before the ready timeout was reached.");
14734     return true;
14735   }
14736   
14737   this.orbiter.getLog().debug(this.toString() + " Connection is not"
14738     + " valid; its most recent connection failed to achieve a ready state.");
14739   return false;
14740 }
14741 
14742     
14743 //==============================================================================
14744 // UPC LISTENERS
14745 //==============================================================================
14746 /** @private */
14747 net.user1.orbiter.Connection.prototype.u63 = function () {
14748   this.stopReadyTimer();
14749   this.connectionState = net.user1.orbiter.ConnectionState.READY;
14750   this.mostRecentConnectAchievedReady = true;
14751   this.readyCount++;
14752   this.connectAttemptCount = 0;
14753   this.connectAbortCount   = 0;
14754   this.dispatchReady();
14755 }    
14756 
14757 /** @private */
14758 net.user1.orbiter.Connection.prototype.u66 = function (serverVersion, 
14759                                                        sessionID, 
14760                                                        upcVersion, 
14761                                                        protocolCompatible,
14762                                                        affinityAddress,
14763                                                        affinityDuration) {
14764   this.orbiter.setSessionID(sessionID);
14765 };
14766 
14767 /** @private */
14768 net.user1.orbiter.Connection.prototype.u84 = function () {
14769   this.dispatchSessionTerminated();
14770 }    
14771 
14772 /** @private */
14773 net.user1.orbiter.Connection.prototype.u85 = function () {
14774   this.dispatchSessionNotFound();
14775 }    
14776 
14777 //==============================================================================    
14778 // SERVER AFFINITY
14779 //==============================================================================
14780 /** @private */
14781 net.user1.orbiter.Connection.prototype.applyAffinity = function () {
14782   var affinityAddress = this.orbiter.getConnectionManager().getAffinity(this.requestedHost);
14783   if (affinityAddress == this.requestedHost) {
14784     this.orbiter.getLog().info(this.toString() + " No affinity address found for requested host [" 
14785                                + this.requestedHost + "]. Using requested host for next connection attempt.");
14786   } else {
14787     this.orbiter.getLog().info(this.toString() + " Applying affinity address [" + affinityAddress + "] for supplied host [" + this.requestedHost + "].");
14788   }
14789   this.host = affinityAddress;
14790 }
14791 
14792 //==============================================================================    
14793 // TOSTRING
14794 //==============================================================================     
14795 net.user1.orbiter.Connection.prototype.toString = function () {
14796   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
14797           + ", host: " + (this.host == null ? "" : this.host) 
14798           + ", port: " + this.port + "]";
14799   return s;
14800 }
14801     
14802 //==============================================================================    
14803 // EVENT DISPATCHING
14804 //==============================================================================  
14805 /** @private */
14806 net.user1.orbiter.Connection.prototype.dispatchSendData = function (data) {
14807   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SEND_DATA,
14808                                     null, data, this));
14809 }
14810 
14811 /** @private */
14812 net.user1.orbiter.Connection.prototype.dispatchReceiveData = function (data) {
14813   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_DATA,
14814                                     null, data, this));
14815 }
14816 
14817 /** @private */
14818 net.user1.orbiter.Connection.prototype.dispatchConnectFailure = function (status) {
14819   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
14820                                     null, null, this, status));
14821 }
14822 
14823 /** @private */
14824 net.user1.orbiter.Connection.prototype.dispatchBeginConnect = function () {
14825   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_CONNECT,
14826                                     null, null, this));
14827 }
14828 
14829 /** @private */
14830 net.user1.orbiter.Connection.prototype.dispatchBeginHandshake = function () {
14831   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.BEGIN_HANDSHAKE,
14832                                     null, null, this));
14833 }
14834 
14835 /** @private */
14836 net.user1.orbiter.Connection.prototype.dispatchReady = function () {
14837   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.READY,
14838                                     null, null, this));
14839 }
14840 
14841 /** @private */
14842 net.user1.orbiter.Connection.prototype.dispatchServerKillConnect  = function () {
14843   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SERVER_KILL_CONNECT,
14844                                     null, null, this));
14845   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
14846                                     null, null, this));
14847 }
14848 
14849 /** @private */
14850 net.user1.orbiter.Connection.prototype.dispatchClientKillConnect = function () {
14851     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.CLIENT_KILL_CONNECT,
14852                                       null, null, this));
14853     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.DISCONNECT,
14854                                       null, null, this));
14855 }
14856 
14857 /** @private */
14858 net.user1.orbiter.Connection.prototype.dispatchSessionTerminated = function () {
14859   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED,
14860                                     null, null, this));
14861 }
14862 
14863 /** @private */
14864 net.user1.orbiter.Connection.prototype.dispatchSessionNotFound = function () {
14865   this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND,
14866                                     null, null, this));
14867 }
14868 
14869 //==============================================================================    
14870 // DISPOSAL
14871 //==============================================================================  
14872 /** @private */
14873 net.user1.orbiter.Connection.prototype.dispose = function () {
14874   this.disposed = true;
14875   this.messageManager.removeMessageListener("u63", this.u63);
14876   this.messageManager.removeMessageListener("u66", this.u66);
14877   this.messageManager.removeMessageListener("u84", this.u84);
14878   this.messageManager.removeMessageListener("u85", this.u85);
14879   this.stopReadyTimer();
14880   this.readyTimer = null;
14881   this.orbiter = null;
14882 }
14883 //==============================================================================    
14884 // CLASS DECLARATION
14885 //==============================================================================
14886 /**
14887  * @class
14888  *
14889  * <p>
14890  * The WebSocketConnection class is used by Orbiter to communicate with
14891  * Union Server over a persistent TCP/IP socket. Normally, developers need not
14892  * use the WebSocketConnection class directly, and should instead make connections
14893  * via the Orbiter class's connect() method. However, the
14894  * WebSocketConnection class is required for fine-grained connection configuration,
14895  * such as defining failover socket connections for multiple Union Servers
14896  * running at different host addresses.
14897  * </p>
14898  *
14899  * <p>
14900  * By default, Orbiter uses WebSocketConnection connections to communicate
14901  * with Union Server. WebSocketConnection connections offer faster response times than
14902  * HTTP connections, but occupy an operating-system-level socket continuously
14903  * for the duration of the connection. If a WebSocketConnection connection
14904  * cannot be established (due to, say, a restrictive firewall), Orbiter
14905  * automatically attempts to communicate using HTTP requests sent via an
14906  * HTTPDirectConnection or HTTPIFrameConnection. Developers can override
14907  * Orbiter's default connection failover system by manually configuring
14908  * connections using the ConnectionManager class and Orbiter's
14909  * disableHTTPFailover() method.</p>
14910  *
14911  * <p>
14912  * For secure WebSocket and HTTP communications, see SecureWebSocketConnection,
14913  * SecureHTTPDirectConnection, and SecureHTTPIFrameConnection.
14914  * </p>
14915  *
14916  * For a list of events dispatched by WebSocketConnection, see
14917  * WebSocketConnection's superclass, {@link net.user1.orbiter.Connection}.
14918  *
14919  * @extends net.user1.orbiter.Connection
14920  *
14921  * @see net.user1.orbiter.Orbiter#connect
14922  * @see net.user1.orbiter.Orbiter#secureConnect
14923  * @see net.user1.orbiter.SecureWebSocketConnection
14924  * @see net.user1.orbiter.SecureHTTPDirectConnection
14925  * @see net.user1.orbiter.SecureHTTPIFrameConnection
14926  */
14927 net.user1.orbiter.WebSocketConnection = function (host, port, type) {
14928   // Invoke superclass constructor
14929   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.WEBSOCKET);
14930   
14931   this.socket = null;
14932 };
14933 
14934 //==============================================================================
14935 // INHERITANCE
14936 //==============================================================================
14937 net.user1.utils.extend(net.user1.orbiter.WebSocketConnection, net.user1.orbiter.Connection);
14938     
14939 //==============================================================================    
14940 // SOCKET OBJECT MANAGEMENT
14941 //==============================================================================
14942 /** @private */     
14943 net.user1.orbiter.WebSocketConnection.prototype.getNewSocket = function () {
14944   // Deactivate the old socket
14945   this.deactivateSocket(this.socket);
14946   
14947   // Create the new socket
14948   if (typeof MozWebSocket != "undefined") {
14949     // Firefox 6
14950     this.socket = new MozWebSocket(this.buildURL());
14951   } else {
14952     // Other browsers
14953     this.socket = new WebSocket(this.buildURL());
14954   }
14955 
14956   // Register for socket events
14957   var self = this;
14958   this.socket.onopen = function (e) {self.connectListener(e)};
14959   this.socket.onmessage = function (e) {self.dataListener(e)};
14960   this.socket.onclose = function (e) {self.closeListener(e)};
14961   this.socket.onerror = function (e) {self.ioErrorListener(e)};
14962 };
14963 
14964 /** @private */
14965 net.user1.orbiter.WebSocketConnection.prototype.buildURL = function () {
14966   return "ws://" + this.host + ":" + this.port;
14967 };
14968 
14969 /** @private */ 
14970 net.user1.orbiter.WebSocketConnection.prototype.deactivateSocket = function (oldSocket) {
14971   if (oldSocket == null) {
14972     return;
14973   }
14974   
14975   this.socket.onopen = null;
14976   this.socket.onmessage = null;
14977   this.socket.onclose = null;
14978   this.socket.onerror = null;
14979   
14980   try {
14981     oldSocket.close()
14982   } catch (e) {
14983     // Do nothing
14984   }
14985   
14986   this.socket = null;
14987 };
14988     
14989 //==============================================================================    
14990 // CONNECTION AND DISCONNECTION
14991 //==============================================================================    
14992   
14993 net.user1.orbiter.WebSocketConnection.prototype.connect = function () {
14994   net.user1.orbiter.Connection.prototype.connect.call(this);
14995       
14996   // Attempt to connect
14997   try {
14998     this.getNewSocket();
14999   } catch (e) {
15000     // Socket could not be opened
15001     this.deactivateConnection();
15002     this.dispatchConnectFailure(e.toString());
15003   }
15004 };
15005 
15006 /** @private */ 
15007 net.user1.orbiter.WebSocketConnection.prototype.deactivateConnection = function () {
15008   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
15009   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
15010   this.deactivateSocket(this.socket);
15011   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
15012 };    
15013 
15014 //==============================================================================    
15015 // SOCKET CONNECTION LISTENERS
15016 //==============================================================================
15017 /** @private */     
15018 net.user1.orbiter.WebSocketConnection.prototype.connectListener = function (e) {
15019   if (this.disposed) return;
15020   
15021   this.orbiter.getLog().debug(this.toString() + " Socket connected.");
15022   this.beginReadyHandshake();
15023 }
15024   
15025 /** @private */ 
15026 net.user1.orbiter.WebSocketConnection.prototype.closeListener = function (e) {
15027   if (this.disposed) return;
15028   
15029   var state = this.connectionState;
15030   this.deactivateConnection();
15031   
15032   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15033     this.dispatchConnectFailure("WebSocket onclose: Server closed connection before READY state was achieved.");
15034   } else {
15035     this.dispatchServerKillConnect();
15036   }
15037 };
15038 
15039 /** @private */ 
15040 net.user1.orbiter.WebSocketConnection.prototype.ioErrorListener = function (e) {
15041   if (this.disposed) return;
15042   
15043   var state = this.connectionState;
15044   this.deactivateConnection();
15045   
15046   // Note: when Union closes the connection, Firefox 7 dispatches onerror, not 
15047   // onclose, so treat onerror like an onclose event
15048   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15049     this.dispatchConnectFailure("WebSocket onerror: Server closed connection before READY state was achieved.");
15050   } else {
15051     this.dispatchServerKillConnect();
15052   }
15053 };
15054 
15055 //==============================================================================    
15056 // DATA RECEIVING AND SENDING
15057 //==============================================================================  
15058 /** @private */ 
15059 net.user1.orbiter.WebSocketConnection.prototype.dataListener = function (dataEvent) {
15060   if (this.disposed) return;
15061 
15062   var data = dataEvent.data;
15063   this.dispatchReceiveData(data);
15064 
15065   if (data.indexOf("<U>") == 0) {
15066     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(
15067                                       net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
15068                                       data));
15069   } else {
15070     // The message isn't UPC. Must be an error...
15071     this.orbiter.getLog().error(this.toString() + " Received invalid message" 
15072                                + " (not UPC or malformed UPC): " + data);
15073   }
15074 };
15075 
15076 /** @private */ 
15077 net.user1.orbiter.WebSocketConnection.prototype.send = function (data) {
15078   this.dispatchSendData(data);
15079   this.socket.send(data);
15080 };
15081     
15082 // =============================================================================
15083 // DISPOSAL
15084 // =============================================================================
15085 /** @private */ 
15086 net.user1.orbiter.WebSocketConnection.prototype.dispose = function () {
15087   net.user1.orbiter.Connection.prototype.dispose.call(this);
15088   this.deactivateSocket(this.socket);
15089 };
15090 //==============================================================================    
15091 // CLASS DECLARATION
15092 //==============================================================================
15093 /** @class
15094  *
15095  * <p>
15096  * The SecureWebSocketConnection class is identical to WebSocketConnection
15097  * except that it performs communications over WSS (i.e., an encrypted TLS or
15098  * SSL socket connection) rather than plain WebSocket.</p>
15099  *
15100  * For a list of events dispatched by SecureWebSocketConnection, see
15101  * {@link net.user1.orbiter.Connection}.
15102  *
15103  * @extends net.user1.orbiter.WebSocketConnection
15104  *
15105  * @see net.user1.orbiter.WebSocketConnection
15106  */
15107 net.user1.orbiter.SecureWebSocketConnection = function (host, port) {
15108   // Invoke superclass constructor
15109   net.user1.orbiter.WebSocketConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_WEBSOCKET);
15110 };
15111 
15112 //==============================================================================
15113 // INHERITANCE
15114 //==============================================================================
15115 net.user1.utils.extend(net.user1.orbiter.SecureWebSocketConnection, net.user1.orbiter.WebSocketConnection);
15116     
15117 /** @private */
15118 net.user1.orbiter.SecureWebSocketConnection.prototype.buildURL = function () {
15119   return "wss://" + this.host + ":" + this.port;
15120 };
15121 //==============================================================================    
15122 // CLASS DECLARATION
15123 //==============================================================================
15124 /**
15125  * @class
15126  *
15127  * HTTPConnection is the abstract superclass of HTTPDirectConnection and
15128  * HTTPIFrameConnection; it is used internally by Orbiter, and is not intended
15129  * for direct use by Orbiter developers. For information on HTTP communication
15130  * with Union Server, see the HTTPDirectConnection and HTTPIFrameConnection classes.
15131  *
15132  * For a list of events dispatched by HTTPConnection, see HTTPConnection's
15133  * superclass, {@link net.user1.orbiter.Connection}.
15134  *
15135  * @extends net.user1.orbiter.Connection
15136  *
15137  *
15138  * @see net.user1.orbiter.HTTPDirectConnection
15139  * @see net.user1.orbiter.HTTPIFrameConnection
15140  */
15141 net.user1.orbiter.HTTPConnection = function (host, port, type) {
15142   // Invoke superclass constructor
15143   net.user1.orbiter.Connection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15144 
15145   // Instance variables
15146   this.url = "";
15147   this.sendDelayTimerEnabled = true;
15148   this.sendDelayTimeoutID = -1;
15149   this.sendDelayTimerRunning = false;
15150   this.sendDelay = net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY;
15151   
15152   this.messageQueue = new Array();
15153   
15154   this.retryDelay = 500;
15155   this.retryHelloTimeoutID = -1;
15156   this.retryIncomingTimeoutID = -1;
15157   this.retryOutgoingTimeoutID = -1;
15158 
15159   this.helloResponsePending = false;
15160   this.outgoingResponsePending = false;
15161   
15162   // Initialization
15163   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_TERMINATED, this.sessionTerminatedListener, this);
15164   this.addEventListener(net.user1.orbiter.ConnectionEvent.SESSION_NOT_FOUND, this.sessionNotFoundListener, this);
15165 };
15166 
15167 //==============================================================================
15168 // INHERITANCE
15169 //==============================================================================
15170 net.user1.utils.extend(net.user1.orbiter.HTTPConnection, net.user1.orbiter.Connection);
15171 
15172 //==============================================================================    
15173 // STATIC VARIABLES
15174 //==============================================================================    
15175 /** @constant */
15176 net.user1.orbiter.HTTPConnection.DEFAULT_SEND_DELAY = 300;
15177     
15178 //==============================================================================    
15179 // ABSTRACT METHODS (MUST BE IMPLEMENTED BY SUBCLASSES)
15180 //==============================================================================    
15181     
15182 net.user1.orbiter.HTTPConnection.prototype.doRequestDeactivation = net.user1.utils.abstractError;
15183 net.user1.orbiter.HTTPConnection.prototype.doSendHello = net.user1.utils.abstractError;
15184 net.user1.orbiter.HTTPConnection.prototype.doRetryHello = net.user1.utils.abstractError;
15185 net.user1.orbiter.HTTPConnection.prototype.doSendOutgoing = net.user1.utils.abstractError;
15186 net.user1.orbiter.HTTPConnection.prototype.doRetryOutgoing = net.user1.utils.abstractError;
15187 net.user1.orbiter.HTTPConnection.prototype.doSendIncoming = net.user1.utils.abstractError;
15188 net.user1.orbiter.HTTPConnection.prototype.doRetryIncoming = net.user1.utils.abstractError;
15189 net.user1.orbiter.HTTPConnection.prototype.doDispose = net.user1.utils.abstractError;
15190     
15191 //==============================================================================    
15192 // CONNECTION AND DISCONNECTION
15193 //==============================================================================    
15194 net.user1.orbiter.HTTPConnection.prototype.connect = function () {
15195   net.user1.orbiter.Connection.prototype.connect.call(this);
15196 };
15197     
15198 /** @private */
15199 net.user1.orbiter.HTTPConnection.prototype.deactivateConnection = function () {
15200   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Deactivating...");
15201   this.connectionState = net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS;
15202   this.stopSendDelayTimer();
15203   if (this.retryHelloTimeoutID != -1) {
15204     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled hello-request retry.");
15205     clearTimeout(this.retryHelloTimeoutID);
15206     this.retryHelloTimeoutID = -1
15207   }
15208   if (this.retryIncomingTimeoutID != -1) {
15209     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled incoming-request retry.");
15210     clearTimeout(this.retryIncomingTimeoutID);
15211     this.retryIncomingTimeoutID = -1
15212   }
15213   if (this.retryOutgoingTimeoutID != -1) {
15214     this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Cancelling scheduled outgoing-request retry.");
15215     clearTimeout(this.retryOutgoingTimeoutID);
15216     this.retryOutgoingTimeoutID = -1
15217   }
15218   this.deactivateHTTPRequests();
15219   net.user1.orbiter.Connection.prototype.deactivateConnection.call(this);
15220 };
15221     
15222 /** @private */
15223 net.user1.orbiter.HTTPConnection.prototype.deactivateHTTPRequests = function () {
15224   this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Closing all pending HTTP requests.");
15225   this.doRequestDeactivation();
15226   this.helloResponsePending = false;
15227   this.outgoingResponsePending = false;
15228 };
15229 
15230 //==============================================================================    
15231 // SESSION MANAGEMENT
15232 //==============================================================================     
15233 
15234 /** @private */
15235 net.user1.orbiter.HTTPConnection.prototype.sessionTerminatedListener = function (e) {
15236   var state = this.connectionState;
15237   this.deactivateConnection();
15238   
15239   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15240     this.dispatchConnectFailure("Server terminated session before READY state was achieved.");
15241   } else {
15242     this.dispatchServerKillConnect();
15243   }
15244 };
15245 
15246 /** @private */
15247 net.user1.orbiter.HTTPConnection.prototype.sessionNotFoundListener = function (e) {
15248   var state = this.connectionState;
15249   
15250   this.deactivateConnection();
15251   
15252   if (state == net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15253     this.dispatchConnectFailure("Client attempted to reestablish an expired session"
15254                                 + " or establish an unknown session.");
15255   } else {
15256     this.dispatchServerKillConnect();
15257   }
15258 }
15259 
15260     
15261 //==============================================================================    
15262 // SERVER ASSIGNMENT
15263 //==============================================================================    
15264 /** @private */
15265 net.user1.orbiter.HTTPConnection.prototype.setServer = function (host, port) {
15266   try {
15267     net.user1.orbiter.Connection.prototype.setServer.call(this, host, port);
15268   } finally {
15269     this.buildURL();
15270   }
15271 }
15272 
15273 /** @private */
15274 net.user1.orbiter.HTTPConnection.prototype.buildURL = function () {
15275   this.url = "http://" + this.host + ":" + this.port;
15276 }
15277 
15278 //==============================================================================    
15279 // OUTGOING DELAY TIMER
15280 //==============================================================================    
15281 /** @private */
15282 net.user1.orbiter.HTTPConnection.prototype.sendDelayTimerListener = function () {
15283   this.sendDelayTimerRunning = false;
15284   if (this.messageQueue.length > 0) {
15285     this.flushMessageQueue();
15286   } else {
15287     // No messages in queue, so take no action.
15288   }
15289 }
15290     
15291 /** @private */
15292 net.user1.orbiter.HTTPConnection.prototype.stopSendDelayTimer = function () {
15293   this.sendDelayTimerRunning = false;
15294   if (this.sendDelayTimeoutID != -1) {
15295     clearTimeout(this.sendDelayTimeoutID);
15296   }
15297   this.sendDelayTimeoutID = -1;
15298 }
15299     
15300 /** @private */
15301 net.user1.orbiter.HTTPConnection.prototype.startSendDelayTimer = function () {
15302   this.stopSendDelayTimer();
15303   var currentObj = this;
15304   var callback   = this.sendDelayTimerListener;
15305   this.sendDelayTimerRunning = true;
15306   this.sendDelayTimeoutID = setTimeout(function () {
15307     callback.call(currentObj);
15308   }, this.sendDelay);
15309 }
15310     
15311 net.user1.orbiter.HTTPConnection.prototype.setSendDelay = function (milliseconds) {
15312   if (milliseconds > 0) {
15313     if ((milliseconds != this.sendDelay)) {
15314       this.sendDelay = milliseconds;
15315       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Send delay set to: ["
15316                              + milliseconds + "]."); 
15317     }
15318     this.sendDelayTimerEnabled = true;
15319   } else if (milliseconds == -1) {
15320     this.orbiter.getLog().debug("[CONNECTION] " + toString() + " Send delay disabled.");
15321     this.sendDelayTimerEnabled = false;
15322     this.stopSendDelayTimer();
15323   } else {
15324     throw new Error("[CONNECTION]" + this.toString() + " Invalid send-delay specified: [" 
15325                     + milliseconds + "]."); 
15326   }
15327 }
15328     
15329 net.user1.orbiter.HTTPConnection.prototype.getSendDelay = function () {
15330   return this.sendDelay;
15331 }
15332 
15333 //==============================================================================    
15334 // RETRY DELAY
15335 //============================================================================== 
15336 net.user1.orbiter.HTTPConnection.prototype.setRetryDelay = function (milliseconds) {
15337   if (milliseconds > -1) {
15338     if (milliseconds != this.retryDelay) {
15339       this.retryDelay = milliseconds;
15340       this.orbiter.getLog().debug("[CONNECTION] " + this.toString() + " Retry delay set to: ["
15341                                   + milliseconds + "]."); 
15342     }
15343   } else {
15344     throw new Error("[CONNECTION]" + this.toString() + " Invalid retry delay specified: [" 
15345                     + milliseconds + "]."); 
15346   }
15347 }
15348 
15349 //==============================================================================    
15350 // DATA SENDING AND QUEUING
15351 //==============================================================================  
15352     
15353 net.user1.orbiter.HTTPConnection.prototype.send = function (data) {
15354   // If the timer isn't running...
15355   if (!this.sendDelayTimerRunning) {
15356     // ...it is either disabled or expired. Either way, it's time to 
15357     // attempt to flush the queue.
15358     this.messageQueue.push(data);
15359     this.flushMessageQueue();
15360   } else {
15361     // The send-delay timer is running, so we can't send yet. Just queue the message.
15362     this.messageQueue.push(data);
15363   }
15364 }
15365     
15366 /** @private */
15367 net.user1.orbiter.HTTPConnection.prototype.flushMessageQueue = function () {
15368   if (!this.outgoingResponsePending) {
15369     this.openNewOutgoingRequest(this.messageQueue.join(""));
15370     this.messageQueue = new Array();
15371   } else {
15372     // AN OUTGOING RESPONSE IS STILL PENDING, SO DON'T SEND A NEW ONE
15373   }
15374 }
15375 
15376 //==============================================================================    
15377 // HELLO REQUEST MANAGEMENT
15378 //==============================================================================  
15379 
15380 /** @private */
15381 net.user1.orbiter.HTTPConnection.prototype.transmitHelloMessage = function (helloString) {
15382   this.dispatchSendData(helloString);
15383   this.helloResponsePending = true;
15384   this.doSendHello(helloString);
15385 }    
15386 
15387 /** @private */
15388 net.user1.orbiter.HTTPConnection.prototype.helloCompleteListener = function (data) {
15389   if (this.disposed) return;
15390   
15391   if (this.helloResponsePending) {
15392     this.helloResponsePending = false;
15393     this.processIncomingData(data);
15394     
15395     // Don't immediately open a request in the complete handler due to Win IE bug
15396     var self = this;
15397     setTimeout(function () {
15398       self.openNewIncomingRequest();
15399     }, 0);
15400   } else {
15401     if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED) {
15402       this.orbiter.getLog().error("[CONNECTION]" + toString() + " u66 (SERVER_HELLO) received, but client is not connected. Ignoring.");
15403     } else {
15404       this.orbiter.getLog().error("[CONNECTION]" + toString() + " Redundant u66 (SERVER_HELLO) received. Ignoring.");
15405     }
15406   }
15407 }
15408 
15409 /** @private */
15410 net.user1.orbiter.HTTPConnection.prototype.helloErrorListener = function () {
15411   if (this.disposed) return;
15412   // There's already a retry scheduled
15413   if (this.retryHelloTimeoutID != -1) return;  
15414   // The connection attempt has been aborted
15415   if (this.connectionState != net.user1.orbiter.ConnectionState.CONNECTION_IN_PROGRESS) {
15416     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
15417                                 + " Connection is no longer in progress, so no retry scheduled."); 
15418     return;
15419   }
15420   
15421   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " u65 (CLIENT_HELLO) request failed."
15422                               + " Retrying in " +  this.retryDelay + "ms."); 
15423   
15424   // Retry
15425   var self = this;
15426   this.retryHelloTimeoutID = setTimeout(function () {
15427     self.retryHelloTimeoutID = -1;
15428     self.doRetryHello();
15429   }, this.retryDelay);
15430 }
15431 
15432 //==============================================================================    
15433 // OUTGOING REQUEST MANAGEMENT
15434 //==============================================================================
15435 
15436 /** @private */
15437 net.user1.orbiter.HTTPConnection.prototype.openNewOutgoingRequest = function (data) {
15438   this.dispatchSendData(data);
15439   this.outgoingResponsePending = true;
15440   this.doSendOutgoing(data);
15441   if (this.sendDelayTimerEnabled == true) {
15442     this.startSendDelayTimer();
15443   }
15444 }
15445 
15446 /** @private */
15447 net.user1.orbiter.HTTPConnection.prototype.outgoingCompleteListener = function () {
15448   if (this.disposed) return;
15449   
15450   this.outgoingResponsePending = false;
15451   
15452   if (!this.sendDelayTimerRunning && this.messageQueue.length > 0) {
15453     // Don't immediately open a request in the complete handler due to Win IE bug
15454     var self = this;
15455     setTimeout(function () {
15456       self.flushMessageQueue();
15457     }, 0);
15458   }
15459 }
15460 
15461 /** @private */
15462 net.user1.orbiter.HTTPConnection.prototype.outgoingErrorListener = function () {
15463   if (this.disposed) return;
15464   // There's already a retry scheduled
15465   if (this.retryOutgoingTimeoutID != -1) return;  
15466   // The connection has been closed
15467   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15468       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15469     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
15470                                 + " Connection is closed, so no retry scheduled."); 
15471     return;
15472   } 
15473   
15474   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Outgoing request failed."
15475                               + " Retrying in " +  this.retryDelay + "ms.");  
15476       
15477   // Retry
15478   var self = this;
15479   this.retryOutgoingTimeoutID = setTimeout(function () {
15480     self.retryOutgoingTimeoutID = -1;
15481     if (self.disposed
15482         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15483         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15484       return;
15485     }
15486     self.doRetryOutgoing();
15487   }, this.retryDelay);
15488 }
15489 
15490 //==============================================================================    
15491 // INCOMING REQUEST MANAGEMENT
15492 //==============================================================================  
15493 
15494 /** @private */
15495 net.user1.orbiter.HTTPConnection.prototype.openNewIncomingRequest = function () {
15496   this.doSendIncoming();
15497 }
15498 
15499 /** @private */
15500 net.user1.orbiter.HTTPConnection.prototype.incomingCompleteListener = function (data) {
15501   if (this.disposed
15502       || this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15503       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15504     // Incoming request complete, but connection is closed. Ignore content.
15505     return;
15506   }
15507   
15508   // Don't immediately open a request in the complete handler due to Win IE bug
15509   var self = this;
15510   setTimeout(function () {
15511     self.processIncomingData(data);
15512     // A message listener might have closed this connection in response to an incoming
15513     // message. Do not open a new incoming request unless the connection is still open.
15514     if (self.disposed
15515         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15516         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15517       return;
15518     }
15519     self.openNewIncomingRequest();
15520   }, 0);
15521 }
15522 
15523 /** @private */
15524 net.user1.orbiter.HTTPConnection.prototype.incomingErrorListener = function () {
15525   if (this.disposed) return;
15526   // There's already a retry scheduled
15527   if (this.retryIncomingTimeoutID != -1) return;  
15528   // The connection has been closed
15529   if (this.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15530       || this.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15531     this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed."
15532                                 + " Connection is closed, so no retry scheduled."); 
15533     return;
15534   } 
15535 
15536   this.orbiter.getLog().error("[CONNECTION]" + this.toString() + " Incoming request failed." 
15537                               + " Retrying in " +  this.retryDelay + "ms."); 
15538       
15539   // Retry
15540   var self = this;
15541   this.retryIncomingTimeoutID = setTimeout(function () {
15542     self.retryIncomingTimeoutID = -1;
15543     if (self.disposed
15544         || self.connectionState == net.user1.orbiter.ConnectionState.NOT_CONNECTED
15545         || self.connectionState == net.user1.orbiter.ConnectionState.DISCONNECTION_IN_PROGRESS) {
15546       return;
15547     }
15548     self.doRetryIncoming();
15549   }, this.retryDelay);
15550 }
15551     
15552 //==============================================================================    
15553 // PROCESS DATA FROM THE SERVER
15554 //==============================================================================
15555  
15556 /** @private */
15557 net.user1.orbiter.HTTPConnection.prototype.processIncomingData = function (data) {
15558   if (this.disposed) return;
15559   var listeners;
15560   
15561   this.dispatchReceiveData(data);
15562   
15563   var upcs = new Array();
15564   var upcEndTagIndex = data.indexOf("</U>");
15565   // Empty responses are valid.
15566   if (upcEndTagIndex == -1 && data.length > 0) {
15567     this.orbiter.getLog().error("Invalid message received. No UPC found: [" + data + "]");
15568     if (!this.isReady()) {
15569       // If invalid XML is received prior to achieving ready, then this
15570       // probably isn't a Union server, so disconnect.
15571       this.disconnect();
15572       return;
15573     }
15574   }
15575   
15576   while (upcEndTagIndex != -1) {
15577     upcs.push(data.substring(0, upcEndTagIndex+4));
15578     data = data.substring(upcEndTagIndex+4);
15579     upcEndTagIndex = data.indexOf("</U>");
15580   }
15581   for (var i = 0; i < upcs.length; i++) {
15582     this.dispatchEvent(new net.user1.orbiter.ConnectionEvent(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, upcs[i]));
15583   }
15584 }
15585 
15586 //==============================================================================    
15587 // SERVER AFFINITY
15588 //==============================================================================
15589 /** @private */ 
15590 net.user1.orbiter.HTTPConnection.prototype.applyAffinity = function (data) {
15591   net.user1.orbiter.Connection.prototype.applyAffinity.call(this);
15592   this.buildURL();
15593 };
15594 
15595 //==============================================================================    
15596 // TOSTRING
15597 //==============================================================================     
15598 net.user1.orbiter.HTTPConnection.prototype.toString = function () {
15599   var s = "[" + this.connectionType + ", requested host: " + this.requestedHost 
15600           + ", host: " + (this.host == null ? "" : this.host) 
15601           + ", port: " + this.port 
15602           + ", send-delay: " + this.getSendDelay() + "]";
15603   return s;
15604 }
15605     
15606 // =============================================================================
15607 // DISPOSAL
15608 // =============================================================================
15609 /** @private */ 
15610 net.user1.orbiter.HTTPConnection.prototype.dispose = function () {
15611   this.doDispose();
15612   this.stopSendDelayTimer();
15613   net.user1.orbiter.Connection.prototype.dispose.call(this);
15614 }
15615 //==============================================================================    
15616 // CLASS DECLARATION
15617 //==============================================================================
15618 /**
15619  * @class
15620  *
15621  * The HTTPIFrameConnection class is used by Orbiter to communicate with
15622  * Union Server over HTTP in browsers that do not support CORS.
15623  * Rather than using CORS, HTTPIFrameConnection bypasses cross-origin restrictions
15624  * by proxying communications through a hidden HTML iframe.
15625  *
15626  * For a list of events dispatched by HTTPDirectConnection,
15627  * {@link net.user1.orbiter.Connection}.
15628  *
15629  * For more information on HTTP communication with Union Server, see
15630  * the HTTPDirectConnection class.
15631  *
15632  * @extends net.user1.orbiter.HTTPConnection
15633  *
15634  * @see net.user1.orbiter.HTTPDirectConnection
15635  * @see net.user1.orbiter.WebSocketConnection
15636  * @see net.user1.orbiter.SecureHTTPDirectConnection
15637  * @see net.user1.orbiter.SecureHTTPIFrameConnection
15638  * @see net.user1.orbiter.SecureWebSocketConnection
15639  */
15640 net.user1.orbiter.HTTPIFrameConnection = function (host, port, type) {
15641   // Invoke superclass constructor
15642   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15643   this.postMessageInited = false;
15644   this.iFrameReady = false;
15645 };
15646 
15647 //==============================================================================
15648 // INHERITANCE
15649 //==============================================================================
15650 net.user1.utils.extend(net.user1.orbiter.HTTPIFrameConnection, net.user1.orbiter.HTTPConnection);
15651 
15652 //==============================================================================    
15653 // POSTMESSAGE INITIALIZATION
15654 //==============================================================================   
15655 /** @private */ 
15656 net.user1.orbiter.HTTPIFrameConnection.prototype.initPostMessage = function () {
15657   if (this.postMessageInited) {
15658     throw new Error("[HTTPIFrameConnection] Illegal duplicate initialization attempt.");
15659   }
15660   var self = this;
15661   var win = this.orbiter.window;
15662   var errorMsg = null;
15663   
15664   if (win == null) {
15665     errorMsg = "[HTTPIFrameConnection] Unable to create connection." 
15666                + " No window object found.";
15667   } else {
15668     if (typeof win.addEventListener != "undefined") {
15669       // ...the standard way 
15670       win.addEventListener("message", postMessageListener, false);
15671     } else if (typeof win.attachEvent != "undefined") {
15672       // ...the IE-specific way 
15673       win.attachEvent("onmessage", postMessageListener);
15674     } else {
15675       errorMsg = "[HTTPIFrameConnection] Unable to create connection."
15676                + " No event listener registration method found on window object.";
15677     }
15678   }
15679   
15680   if (errorMsg != null) {
15681     this.orbiter.getLog().error(errorMsg);
15682     throw new Error(errorMsg);
15683   }
15684 
15685   /** @private */
15686   function postMessageListener (e) {
15687     // The connection's host might have been reassigned (normally to an ip) due
15688     // to server affinity in a clustered deployment, so allow for posts from both the
15689     // requestedHost and the host.
15690     if (e.origin.indexOf("//" + self.host + (self.port == 80 ? "" : (":" + self.port))) == -1
15691         && e.origin.indexOf("//" + self.requestedHost + (self.port == 80 ? "" : (":" + self.port))) == -1) {
15692       self.orbiter.getLog().error("[CONNECTION] " + self.toString()
15693         + " Ignored message from unknown origin: " + e.origin);
15694       return;
15695     }
15696     
15697     self.processPostMessage(e.data);
15698   }
15699   
15700   this.postMessageInited = true;
15701 };
15702 
15703 //==============================================================================    
15704 // IFRAME MANAGEMENT
15705 //==============================================================================    
15706 /** @private */
15707 net.user1.orbiter.HTTPIFrameConnection.prototype.makeIFrame = function () {
15708   if (typeof this.orbiter.window.document == "undefined") {
15709     var errorMsg = "[HTTPIFrameConnection] Unable to create connection."
15710                  + " No document object found.";
15711     this.orbiter.getLog().error(errorMsg);
15712     throw new Error(errorMsg);
15713   }
15714   var doc = this.orbiter.window.document;
15715   
15716   this.iFrameReady = false;
15717   if (this.iframe != null) {
15718     this.postToIFrame("dispose");
15719     doc.body.removeChild(this.iframe);
15720   }
15721   this.iframe = doc.createElement('iframe');
15722   this.iframe.width = "0px";
15723   this.iframe.height = "0px";
15724   this.iframe.border = "0px";
15725   this.iframe.frameBorder = "0";
15726   this.iframe.style.visibility = "hidden";
15727   this.iframe.style.display = "none";
15728   this.iframe.src = this.url + "/orbiter";
15729   doc.body.appendChild(this.iframe);
15730 }
15731 
15732 /** @private */
15733 net.user1.orbiter.HTTPIFrameConnection.prototype.onIFrameReady = function () {
15734   this.beginReadyHandshake();
15735 }
15736 
15737 /** @private */
15738 net.user1.orbiter.HTTPIFrameConnection.prototype.postToIFrame = function (cmd, data) {
15739   if (this.iframe && this.iFrameReady) {
15740     data = data == undefined ? "" : data;
15741     // In order to post to the iframe, the targetOrigin must match the iframe's origin
15742     this.iframe.contentWindow.postMessage(cmd + "," + data, this.iframe.contentWindow.location.href);
15743   }  
15744 }
15745 
15746 /** @private */
15747 net.user1.orbiter.HTTPIFrameConnection.prototype.processPostMessage = function (postedData) {
15748   var delimiterIndex = postedData.indexOf(",");
15749   var cmd  = postedData.substring(0, delimiterIndex);
15750   var data = postedData.substring(delimiterIndex+1);
15751   
15752   switch (cmd) {
15753     case"ready":
15754       this.iFrameReady = true;
15755       this.onIFrameReady();
15756       break;
15757       
15758     case "hellocomplete":
15759       this.helloCompleteListener(data);
15760       break;
15761     
15762     case "helloerror":
15763       this.helloErrorListener();
15764       break;
15765     
15766     case "outgoingcomplete":
15767       this.outgoingCompleteListener();
15768       break;
15769     
15770     case "outgoingerror":
15771       this.outgoingErrorListener();
15772       break;
15773     
15774     case "incomingcomplete":
15775       this.incomingCompleteListener(data);
15776       break;
15777     
15778     case "incomingerror":
15779       this.incomingErrorListener();
15780       break;
15781   }
15782 }
15783 
15784 //==============================================================================    
15785 // CONNECTION AND DISCONNECTION
15786 //==============================================================================    
15787 net.user1.orbiter.HTTPIFrameConnection.prototype.connect = function () {
15788   if (!this.postMessageInited) {
15789     this.initPostMessage();
15790   }
15791   
15792   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
15793   this.makeIFrame();
15794 };
15795 
15796 /** @private */
15797 net.user1.orbiter.HTTPIFrameConnection.prototype.doRequestDeactivation = function() {
15798   this.postToIFrame("deactivate");
15799 };
15800 
15801 //==============================================================================    
15802 // UPC LISTENERS (IFRAME-SPECIFIC IMPLEMENTATION)
15803 //==============================================================================
15804 
15805 /** @private */
15806 net.user1.orbiter.HTTPIFrameConnection.prototype.u66 = function (serverVersion, 
15807                                                            sessionID, 
15808                                                            upcVersion, 
15809                                                            protocolCompatible) {
15810   net.user1.orbiter.Connection.prototype.u66.call(this,
15811                                                   serverVersion,
15812                                                   sessionID,
15813                                                   upcVersion,
15814                                                   protocolCompatible);
15815   if (this.iframe != null) {
15816     this.postToIFrame("sessionid", sessionID);
15817   }
15818 }
15819 
15820 //==============================================================================    
15821 // HELLO REQUEST MANAGEMENT
15822 //==============================================================================  
15823 
15824 /** @private */
15825 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendHello = function (helloString) {
15826   this.postToIFrame("sendhello", helloString);
15827 };
15828 
15829 /** @private */
15830 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryHello = function () {
15831   this.postToIFrame("retryhello");
15832 }
15833 
15834 //==============================================================================    
15835 // OUTGOING REQUEST MANAGEMENT
15836 //==============================================================================
15837 
15838 /** @private */
15839 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendOutgoing = function (data) {
15840   this.postToIFrame("sendoutgoing", data);
15841 };
15842 
15843 /** @private */
15844 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryOutgoing = function () {
15845   this.postToIFrame("retryoutgoing");
15846 };
15847 
15848 //==============================================================================    
15849 // INCOMING REQUEST MANAGEMENT
15850 //==============================================================================  
15851 
15852 /** @private */
15853 net.user1.orbiter.HTTPIFrameConnection.prototype.doSendIncoming = function () {
15854   this.postToIFrame("sendincoming");
15855 };
15856 
15857 /** @private */
15858 net.user1.orbiter.HTTPIFrameConnection.prototype.doRetryIncoming = function () {
15859   this.postToIFrame("retryincoming");
15860 };
15861     
15862 //==============================================================================    
15863 // TOSTRING
15864 //==============================================================================     
15865 net.user1.orbiter.HTTPIFrameConnection.prototype.toString = function () {
15866   var s = "[HTTPIFrameConnection, requested host: " + this.requestedHost 
15867           + ", host: " + (this.host == null ? "" : this.host) 
15868           + ", port: " + this.port 
15869           + ", send-delay: " + this.getSendDelay() + "]";
15870   return s;
15871 };
15872     
15873 //==============================================================================
15874 // DISPOSAL
15875 //==============================================================================
15876 /** @private */ 
15877 net.user1.orbiter.HTTPIFrameConnection.prototype.doDispose = function () {
15878   this.postToIFrame("dispose");
15879 };
15880 //==============================================================================    
15881 // CLASS DECLARATION
15882 //==============================================================================
15883 /**
15884  * @class
15885  * <p>
15886  * The HTTPDirectConnection class is used by Orbiter to communicate with
15887  * Union Server over HTTP; it uses CORS to bypass cross-origin restrictions
15888  * when Union Server is hosted on a domain that does not match the domain at
15889  * which the Orbiter client is hosted. Normally, developers need not use the
15890  * HTTPDirectConnection class directly, and should instead make connections
15891  * via the Orbiter class's connect() method. However, the
15892  * HTTPDirectConnection class is required for fine-grained connection configuration,
15893  * such as defining failover connections for multiple Union Servers
15894  * running at different host addresses.
15895  * </p>
15896  *
15897  * <p>
15898  * By default, Orbiter uses the WebSocketConnection class, not the
15899  * HTTPDirectConnection class, to communicate with Union Server. The
15900  * HTTPDirectConnection class is used as a backup connection
15901  * when the primary WebSocketConnection connection is blocked by a firewall.
15902  * However, on a very heavily loaded server with limited persistent socket
15903  * connections available, communicating with Union Server over HTTP--which uses
15904  * short-lived socket connections--can improve performance at the
15905  * expense of realtime responsiveness. To reduce server load when communicating
15906  * over HTTP, use HTTPDirectConnection's setSendDelay() method to decrease the
15907  * frequency of Orbiter's requests for updates from Union Server. Developers
15908  * that wish to use HTTP connections as the primary form of communication with
15909  * Union Server must do so by manually configuring connections via the
15910  * ConnectionManager class's addConnection() method.</p>
15911  *
15912  * <p>
15913  * In environments that do not support CORS (such as IE8 on Windows), Orbiter
15914  * conducts HTTP communications using HTTPIFrameConnection instead of HTTPDirectConnection.
15915  * </p>
15916  *
15917  * <p>
15918  * For secure HTTP and WebSocket communications, see SecureHTTPDirectConnection,
15919  * SecureHTTPIFrameConnection, and SecureWebSocketConnection.
15920  * </p>
15921  *
15922  *
15923  * For a list of events dispatched by HTTPDirectConnection,
15924  * {@link net.user1.orbiter.Connection}.
15925  *
15926  * @extends net.user1.orbiter.HTTPConnection
15927  *
15928  * @see net.user1.orbiter.Orbiter#connect
15929  * @see net.user1.orbiter.Orbiter#secureConnect
15930  *
15931  * @see net.user1.orbiter.SecureHTTPDirectConnection
15932  * @see net.user1.orbiter.SecureHTTPIFrameConnection
15933  * @see net.user1.orbiter.SecureWebSocketConnection
15934  */
15935 net.user1.orbiter.HTTPDirectConnection = function (host, port, type) {
15936   // Invoke superclass constructor
15937   net.user1.orbiter.HTTPConnection.call(this, host, port, type || net.user1.orbiter.ConnectionType.HTTP);
15938   
15939   this.outgoingRequestID = 0;
15940   this.incomingRequestID = 0;
15941   
15942   this.lastOutgoingPostData = null;
15943   this.lastIncomingPostData = null;
15944   this.lastHelloPostData    = null;
15945   
15946   this.pendingRequests = [];
15947 };
15948 
15949 //==============================================================================
15950 // INHERITANCE
15951 //==============================================================================
15952 net.user1.utils.extend(net.user1.orbiter.HTTPDirectConnection, net.user1.orbiter.HTTPConnection);
15953 
15954 
15955 //==============================================================================    
15956 // CONNECTION AND DISCONNECTION
15957 //==============================================================================    
15958 net.user1.orbiter.HTTPDirectConnection.prototype.connect = function () {
15959   net.user1.orbiter.HTTPConnection.prototype.connect.call(this);
15960   this.beginReadyHandshake();
15961 };
15962 
15963 //==============================================================================    
15964 // HELLO REQUEST MANAGEMENT
15965 //==============================================================================  
15966 
15967 /** @private Abstract method implementation */
15968 net.user1.orbiter.HTTPDirectConnection.prototype.doSendHello = function (helloString) {
15969   this.newHelloRequest(helloString);
15970 };
15971 
15972 /** @private Abstract method implementation */
15973 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryHello = function () {
15974   this.retryHello();
15975 }
15976 
15977 /** @private */
15978 net.user1.orbiter.HTTPDirectConnection.prototype.newHelloRequest = function (data) {
15979   this.lastHelloPostData = this.createHelloPostData(encodeURIComponent(data));
15980   this.transmitRequest(this.lastHelloPostData, 
15981                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
15982                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
15983 }
15984 
15985 /** @private */
15986 net.user1.orbiter.HTTPDirectConnection.prototype.createHelloPostData = function (data) {
15987   return "mode=d" + "&data=" + data;
15988 }
15989 
15990 /** @private */
15991 net.user1.orbiter.HTTPDirectConnection.prototype.retryHello = function () {
15992   this.transmitRequest(this.lastHelloPostData, 
15993                        net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener,
15994                        net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener);
15995 }
15996 
15997 /** @private */
15998 net.user1.orbiter.HTTPDirectConnection.helloRequestReadystatechangeListener = function (xhr, connection) {
15999   if (xhr.readyState == 4) {
16000     connection.removePendingRequest(xhr);
16001     if (xhr.status >= 200 && xhr.status <= 299) {
16002       connection.helloCompleteListener(xhr.responseText);
16003     } else {
16004       connection.helloErrorListener();
16005     }
16006   }
16007 }
16008 
16009 /** @private */
16010 net.user1.orbiter.HTTPDirectConnection.helloRequestErrorListener = function (xhr, connection) {
16011   connection.removePendingRequest(xhr);
16012   connection.helloErrorListener();
16013 }
16014 
16015 //==============================================================================    
16016 // OUTGOING REQUEST MANAGEMENT
16017 //==============================================================================
16018 
16019 /** @private Abstract method implementation */
16020 net.user1.orbiter.HTTPDirectConnection.prototype.doSendOutgoing = function (data) {
16021   this.newOutgoingRequest(data);
16022 };
16023 
16024 /** @private Abstract method implementation */
16025 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryOutgoing = function () {
16026   this.retryOutgoing();
16027 };
16028 
16029 /** @private */
16030 net.user1.orbiter.HTTPDirectConnection.prototype.newOutgoingRequest = function (data) {
16031   this.lastOutgoingPostData = this.createOutgoingPostData(encodeURIComponent(data));
16032   this.transmitRequest(this.lastOutgoingPostData, 
16033                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
16034                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
16035 }
16036 
16037 /** @private */
16038 net.user1.orbiter.HTTPDirectConnection.prototype.createOutgoingPostData = function (data) {
16039   this.outgoingRequestID++;
16040   return "rid=" + this.outgoingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=s" + "&data=" + data;
16041 }
16042 
16043 /** @private */
16044 net.user1.orbiter.HTTPDirectConnection.prototype.retryOutgoing = function () {
16045   this.transmitRequest(this.lastOutgoingPostData, 
16046                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener,
16047                        net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener);
16048 }
16049 
16050 /** @private */
16051 net.user1.orbiter.HTTPDirectConnection.outgoingRequestReadystatechangeListener = function (xhr, connection) {
16052   if (xhr.readyState == 4) {
16053     connection.removePendingRequest(xhr);
16054     if (xhr.status >= 200 && xhr.status <= 299) {
16055       connection.outgoingCompleteListener();
16056     } else {
16057       connection.outgoingErrorListener();
16058     }
16059   }
16060 }
16061 
16062 /** @private */
16063 net.user1.orbiter.HTTPDirectConnection.outgoingRequestErrorListener = function (xhr, connection) {
16064   connection.removePendingRequest(xhr);
16065   connection.outgoingErrorListener();
16066 }
16067 
16068 //==============================================================================    
16069 // INCOMING REQUEST MANAGEMENT
16070 //==============================================================================  
16071 
16072 /** @private Abstract method implementation */
16073 net.user1.orbiter.HTTPDirectConnection.prototype.doSendIncoming = function () {
16074   this.newIncomingRequest();
16075 };
16076 
16077 /** @private Abstract method implementation */
16078 net.user1.orbiter.HTTPDirectConnection.prototype.doRetryIncoming = function () {
16079   this.retryIncoming();
16080 };
16081 
16082 /** @private */ 
16083 net.user1.orbiter.HTTPDirectConnection.prototype.newIncomingRequest = function () {
16084   this.lastIncomingPostData = this.createIncomingPostData();
16085   this.transmitRequest(this.lastIncomingPostData,
16086                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
16087                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
16088 }
16089 
16090 /** @private */ 
16091 net.user1.orbiter.HTTPDirectConnection.prototype.createIncomingPostData = function () {
16092   this.incomingRequestID++;
16093   return "rid=" + this.incomingRequestID + "&sid=" + this.orbiter.getSessionID() + "&mode=c";
16094 }
16095 
16096 /** @private */ 
16097 net.user1.orbiter.HTTPDirectConnection.prototype.retryIncoming = function () {
16098   this.transmitRequest(this.lastIncomingPostData,
16099                        net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener,
16100                        net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener);
16101 }
16102 
16103 /** @private */ 
16104 net.user1.orbiter.HTTPDirectConnection.incomingRequestReadystatechangeListener = function (xhr, connection) {
16105   if (xhr.readyState == 4) {
16106     connection.removePendingRequest(xhr);
16107     if (xhr.status >= 200 && xhr.status <= 299) {
16108       connection.incomingCompleteListener(xhr.responseText);
16109     } else {
16110       connection.incomingErrorListener();
16111     }
16112   }
16113 }
16114 
16115 /** @private */ 
16116 net.user1.orbiter.HTTPDirectConnection.incomingRequestErrorListener = function (xhr, connection) {
16117   connection.removePendingRequest(xhr);
16118   connection.incomingErrorListener();
16119 }
16120 
16121 //==============================================================================
16122 // XHR MANAGEMENT
16123 //==============================================================================
16124 /** @private */
16125 net.user1.orbiter.HTTPDirectConnection.prototype.transmitRequest = function (data, 
16126                                                       readystatechangeListener, 
16127                                                       errorListener) {
16128   var self = this;
16129   var request;
16130   
16131   if (typeof XDomainRequest != "undefined") {
16132     // IE
16133     request = new XDomainRequest();
16134     request.onload = function () {
16135       request.readyState = 4;  // Emulate standards-based API
16136       request.status = 200;
16137       readystatechangeListener(this, self)
16138     };
16139     request.onerror = function () {
16140       errorListener(this, self);
16141     };
16142     request.ontimeout = function () {
16143       errorListener(this, self);
16144     };
16145     request.onprogress = function () {}; // Do nothing (required)
16146   } else {
16147     // All other standards-based browsers
16148     var request = new XMLHttpRequest();
16149     this.pendingRequests.push(request);
16150     request.onreadystatechange = function () {
16151       readystatechangeListener(this, self);
16152     };
16153     request.onerror = function () {
16154       errorListener(this, self);
16155     };
16156   }
16157   // Call open before setting header
16158   request.open("POST", this.url);
16159   // Standards-based browsers (IE doesn't allow the setting of headers)
16160   if (typeof request.setRequestHeader != "undefined") {
16161     request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
16162   }
16163   request.send(data);
16164 }
16165 
16166 /** @private */
16167 net.user1.orbiter.HTTPDirectConnection.prototype.removePendingRequest = function (request) {
16168   for (var i = this.pendingRequests.length; --i >= 0; ) {
16169     if (this.pendingRequests[i] === request) {
16170       this.pendingRequests.splice(i, 1);
16171     }
16172   }
16173 }
16174 
16175 /** @private Abstract method implementation */
16176 net.user1.orbiter.HTTPDirectConnection.prototype.doRequestDeactivation = function () {
16177   for (var i = this.pendingRequests.length; --i >= 0;) {
16178     try {
16179       this.pendingRequests[i].abort();
16180     } catch (e) {
16181       // Do nothing
16182     }
16183   }
16184   this.pendingRequests = [];
16185 }
16186     
16187 //==============================================================================    
16188 // TOSTRING
16189 //==============================================================================     
16190 net.user1.orbiter.HTTPDirectConnection.prototype.toString = function () {
16191   var s = "[HTTPDirectConnection, requested host: " + this.requestedHost 
16192           + ", host: " + (this.host == null ? "" : this.host) 
16193           + ", port: " + this.port 
16194           + ", send-delay: " + this.getSendDelay() + "]";
16195   return s;
16196 };
16197     
16198 //==============================================================================
16199 // DISPOSAL
16200 //==============================================================================
16201 /** @private */ 
16202 net.user1.orbiter.HTTPDirectConnection.prototype.doDispose = function () {
16203   this.deactivateHTTPRequests();
16204 };
16205 //==============================================================================    
16206 // CLASS DECLARATION
16207 //==============================================================================
16208 /** @class
16209  * <p>
16210  * The SecureHTTPDirectConnection class is identical to HTTPDirectConnection
16211  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
16212  * SSL connection) rather than plain HTTP.</p>
16213  *
16214  * For a list of events dispatched by SecureHTTPDirectConnection, see
16215  * {@link net.user1.orbiter.Connection}.
16216  *
16217  * @extends net.user1.orbiter.HTTPDirectConnection
16218  *
16219  * @see net.user1.orbiter.HTTPDirectConnection
16220  */
16221 net.user1.orbiter.SecureHTTPDirectConnection = function (host, port) {
16222   // Invoke superclass constructor
16223   net.user1.orbiter.HTTPDirectConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
16224 };
16225 
16226 //==============================================================================
16227 // INHERITANCE
16228 //==============================================================================
16229 net.user1.utils.extend(net.user1.orbiter.SecureHTTPDirectConnection, net.user1.orbiter.HTTPDirectConnection);
16230 
16231 /** @private */
16232 net.user1.orbiter.SecureHTTPDirectConnection.prototype.buildURL = function () {
16233   this.url = "https://" + this.host + ":" + this.port;
16234 };
16235 
16236 //==============================================================================
16237 // TOSTRING
16238 //==============================================================================
16239 net.user1.orbiter.SecureHTTPDirectConnection.prototype.toString = function () {
16240   var s = "[SecureHTTPDirectConnection, requested host: " + this.requestedHost
16241           + ", host: " + (this.host == null ? "" : this.host)
16242           + ", port: " + this.port
16243           + ", send-delay: " + this.getSendDelay() + "]";
16244   return s;
16245 };
16246 //==============================================================================    
16247 // CLASS DECLARATION
16248 //==============================================================================
16249 /**
16250  * @class
16251  *
16252  * <p>
16253  * The SecureHTTPIFrameConnection class is identical to HTTPIFrameConnection
16254  * except that it performs communications over HTTPS (i.e., an encrypted TLS or
16255  * SSL connection) rather than plain HTTP.</p>
16256  *
16257  * For a list of events dispatched by SecureHTTPIFrameConnection,
16258  * see {@link net.user1.orbiter.Connection}.
16259  *
16260  * @extends net.user1.orbiter.HTTPIFrameConnection
16261  *
16262  * @see net.user1.orbiter.HTTPIFrameConnection
16263  */
16264 net.user1.orbiter.SecureHTTPIFrameConnection = function (host, port) {
16265   // Invoke superclass constructor
16266   net.user1.orbiter.HTTPIFrameConnection.call(this, host, port, net.user1.orbiter.ConnectionType.SECURE_HTTP);
16267 };
16268 
16269 //==============================================================================
16270 // INHERITANCE
16271 //==============================================================================
16272 net.user1.utils.extend(net.user1.orbiter.SecureHTTPIFrameConnection, net.user1.orbiter.HTTPIFrameConnection);
16273 
16274 /** @private */
16275 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.buildURL = function () {
16276   this.url = "https://" + this.host + ":" + this.port;
16277 };
16278 
16279 //==============================================================================
16280 // TOSTRING
16281 //==============================================================================
16282 net.user1.orbiter.SecureHTTPIFrameConnection.prototype.toString = function () {
16283   var s = "[SecureHTTPIFrameConnection, requested host: " + this.requestedHost
16284           + ", host: " + (this.host == null ? "" : this.host)
16285           + ", port: " + this.port
16286           + ", send-delay: " + this.getSendDelay() + "]";
16287   return s;
16288 };
16289 //==============================================================================
16290 // CLASS DECLARATION
16291 //==============================================================================
16292 /** @private */
16293 net.user1.orbiter.MessageListener = function (listener,
16294                                               forRoomIDs,
16295                                               thisArg) {
16296   this.listener   = listener;
16297   this.forRoomIDs = forRoomIDs;
16298   this.thisArg    = thisArg;
16299 };
16300 
16301 //==============================================================================
16302 // INSTANCE METHODS
16303 //==============================================================================
16304 /** @private */
16305 net.user1.orbiter.MessageListener.prototype.getListenerFunction = function () {
16306   return this.listener;
16307 };
16308     
16309 /** @private */
16310 net.user1.orbiter.MessageListener.prototype.getForRoomIDs = function () {
16311   return this.forRoomIDs;
16312 };
16313     
16314 /** @private */
16315 net.user1.orbiter.MessageListener.prototype.getThisArg = function () {
16316   return this.thisArg;
16317 };
16318 
16319 /** @private */
16320 net.user1.orbiter.MessageListener.prototype.toString = function () {
16321   return "[object MessageListener]";
16322 };
16323 //==============================================================================
16324 // CLASS DECLARATION
16325 //==============================================================================
16326 /** @class */
16327 net.user1.orbiter.MessageManager = function (log, connectionManager) {
16328   this.log = log;
16329   this.messageListeners = new Object();
16330   this.removeListenersOnDisconnect = true;
16331   this.numMessagesSent = 0;
16332   this.numMessagesReceived = 0;
16333   this.currentConnection = null;
16334   this.connectionManager = connectionManager;
16335   this.connectionManager.addEventListener(net.user1.orbiter.ConnectionManagerEvent.SELECT_CONNECTION,
16336                                           this.selectConnectionListener, this);
16337 };
16338 
16339 //==============================================================================
16340 // INSTANCE METHODS
16341 //==============================================================================
16342 net.user1.orbiter.MessageManager.prototype.getNumMessagesReceived = function () {
16343   return this.numMessagesReceived;
16344 }
16345   
16346 net.user1.orbiter.MessageManager.prototype.getNumMessagesSent = function () {
16347   return this.numMessagesSent;
16348 }
16349   
16350 net.user1.orbiter.MessageManager.prototype.getTotalMessages = function () {
16351   return this.numMessagesSent + this.numMessagesReceived;
16352 }
16353   
16354 /** @private */
16355 net.user1.orbiter.MessageManager.prototype.selectConnectionListener = function (e) {
16356   if (this.currentConnection != null) {
16357     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
16358                                           this.upcReceivedListener, this);
16359     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
16360                                           this.disconnectListener, this);
16361     this.currentConnection.removeEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
16362                                           this.connectFailureListener, this);
16363   }
16364 
16365   this.currentConnection = e.getConnection(); 
16366 
16367   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.RECEIVE_UPC, 
16368                                         this.upcReceivedListener, this);
16369   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.DISCONNECT,
16370                                         this.disconnectListener, this);
16371   this.currentConnection.addEventListener(net.user1.orbiter.ConnectionEvent.CONNECT_FAILURE,
16372                                         this.connectFailureListener, this);
16373 }
16374   
16375 /** @private */
16376 net.user1.orbiter.MessageManager.prototype.disconnectListener = function (e) {
16377   this.cleanupAfterClosedConnection(e.target);
16378 }
16379     
16380 /** @private */
16381 net.user1.orbiter.MessageManager.prototype.connectFailureListener = function (e) {
16382   this.cleanupAfterClosedConnection(e.target);
16383 }
16384     
16385 /** @private */
16386 net.user1.orbiter.MessageManager.prototype.cleanupAfterClosedConnection = function (connection) {
16387   var listenerList;
16388   if (this.removeListenersOnDisconnect) {
16389     this.log.info("[MESSAGE_MANAGER] Removing registered message listeners.");
16390     for (var message in this.messageListeners) {
16391       listenerList = this.messageListeners[message];
16392       for (var p in listenerList) {
16393         this.removeMessageListener(message, listenerList[p].getListenerFunction());
16394       } 
16395     }
16396   } else {
16397     this.log.warn("[MESSAGE_MANAGER] Leaving message listeners registered. \n"
16398       + "Be sure to remove any unwanted message listeners manually.");
16399   }
16400   
16401   this.numMessagesReceived = 0;
16402   this.numMessagesSent = 0;
16403 }
16404   
16405 net.user1.orbiter.MessageManager.prototype.sendUPC = function (message) {
16406   // Quit if the connection isn't ready...
16407   if (!this.connectionManager.isReady()) {
16408     this.log.warn("[MESSAGE_MANAGER] Connection not ready. UPC not sent. Message: " 
16409     + message);
16410     return;
16411   }
16412 
16413   // Build the UPC to send.
16414   var theUPC = "<U><M>" + message + "</M>";
16415   var a;
16416   
16417   if (arguments.length > 1) {
16418     theUPC += "<L>";
16419     for (var i = 1; i < arguments.length; i++) {
16420       a = arguments[i];
16421       a = a == undefined ? "" : a.toString();
16422       // Wrap any non-filter argument that contains a start tag ("<") in CDATA
16423       if (a.indexOf("<") != -1) {
16424         if (a.indexOf('<f t=') != 0) {
16425           a = "<![CDATA[" + a + "]]>";
16426         }
16427       }
16428       theUPC += "<A>" + a + "</A>";
16429     }
16430     theUPC += "</L>";
16431   }
16432   theUPC += "</U>";
16433 
16434   // Count the message
16435   this.numMessagesSent++;
16436   
16437   // Send the UPC to the server
16438   this.log.debug("[MESSAGE_MANAGER] UPC sent: " + theUPC);
16439   this.connectionManager.getActiveConnection().send(theUPC);
16440 };
16441 
16442 /** @private */
16443 net.user1.orbiter.MessageManager.prototype.sendUPCObject = function (upc) {
16444   var args = upc.args.slice();
16445   args.unshift(upc.method);
16446   this.sendUPC.apply(this, args);
16447 };
16448 
16449 /** @private */
16450 net.user1.orbiter.MessageManager.prototype.upcReceivedListener = function (e) {
16451   this.numMessagesReceived++;
16452   
16453   var upc = e.getUPC();
16454   this.log.debug("[MESSAGE_MANAGER] UPC received: " + upc );
16455   
16456   var method;
16457   var upcArgs = new Array();
16458   
16459   var closeMTagIndex = upc.indexOf("</M>");
16460   method = upc.substring(6, closeMTagIndex);
16461   
16462   var searchBeginIndex = upc.indexOf("<A>", closeMTagIndex);
16463   var closeATagIndex;
16464   var arg;
16465   while (searchBeginIndex != -1) {
16466     closeATagIndex = upc.indexOf("</A>", searchBeginIndex);
16467     arg = upc.substring(searchBeginIndex+3, closeATagIndex);
16468     if (arg.indexOf("<![CDATA[") == 0) {
16469       arg = arg.substr(9, arg.length-12);
16470     }
16471     upcArgs.push(arg);
16472     searchBeginIndex = upc.indexOf("<A>", closeATagIndex);
16473   }     
16474   
16475   this.notifyMessageListeners(method, upcArgs);
16476 };
16477 
16478 net.user1.orbiter.MessageManager.prototype.addMessageListener = function (message, 
16479                                                                           listener,
16480                                                                           thisArg,
16481                                                                           forRoomIDs) {
16482   if (forRoomIDs != null) {
16483     var typeString = Object.prototype.toString.call(forRoomIDs);
16484     if (typeString != "[object Array]") {
16485       throw new Error("[MESSAGE_MANAGER] Illegal argument type " + typeString
16486                       + " supplied for addMessageListener()'s forRoomIDs"
16487                       + " parameter. Value must be an Array.");
16488     }
16489   }
16490   
16491   // Each message gets a list of MessageListener objects. 
16492   // If this message has no such list, make one.
16493   if (this.messageListeners[message] === undefined) {
16494     this.messageListeners[message] = new Array();
16495   } 
16496   var listenerArray = this.messageListeners[message];
16497   
16498   // Quit if the listener is already registered
16499   if (this.hasMessageListener(message, listener)) {
16500     return false;
16501   }
16502   
16503   // Add the listener
16504   var newListener = new net.user1.orbiter.MessageListener(listener,
16505                                             forRoomIDs === undefined ? null : forRoomIDs,
16506                                             thisArg);
16507   listenerArray.push(newListener);
16508   return true;      
16509 };
16510 
16511 net.user1.orbiter.MessageManager.prototype.removeMessageListener = function (message,
16512                                                            listener) {
16513   // Quit if the message has no listeners
16514   var listenerArray = this.messageListeners[message];
16515   if (listenerArray == null) {
16516     return false;
16517   } 
16518   
16519   // Remove the listener
16520   var foundListener;
16521   for (var i = 0; i < listenerArray.length; i++) {
16522     if (listenerArray[i].getListenerFunction() == listener) {
16523       foundListener = true;
16524       listenerArray.splice(i, 1);
16525       break;
16526     }
16527   }
16528   
16529   // Delete the listeners array if it's now empty
16530   if (listenerArray.length == 0) {
16531     delete this.messageListeners[message];
16532   }
16533   
16534   return foundListener;      
16535 };
16536     
16537 net.user1.orbiter.MessageManager.prototype.hasMessageListener = function (message, 
16538                                                         listener) {
16539   // Quit if the message has no listeners
16540   var listenerArray = this.messageListeners[message];
16541   if (listenerArray == null) {
16542     return false;
16543   } 
16544       
16545    // Check for the listener
16546   for (var i = 0; i < listenerArray.length; i++) {
16547     if (listenerArray[i].getListenerFunction() 
16548       == listener) {
16549       return true;
16550     }
16551   }
16552   return false;
16553 };
16554     
16555 net.user1.orbiter.MessageManager.prototype.getMessageListeners = function (message) {
16556   return this.messageListeners[message] != undefined ? this.messageListeners[message] : [];
16557 };
16558 
16559 /** @private */
16560 net.user1.orbiter.MessageManager.prototype.notifyMessageListeners = function (message, args) {
16561   // Retrieve the list of listeners for this message.
16562   var listeners = this.messageListeners[message];
16563   // If there are no listeners registered, then quit
16564   if (listeners === undefined) {
16565     // Log a warning if it's not a UPC
16566     if (!(message.charAt(0) == "u" && parseInt(message.substring(1)) > 1)) {
16567       this.log.warn("Message delivery failed. No listeners found. Message: " + 
16568                message + ". Arguments: " + args.join());
16569     }
16570     return;
16571   } else {
16572     listeners = listeners.slice(0);    
16573   }
16574   var numListeners = listeners.length; 
16575   for (var i = 0; i < numListeners; i++) {
16576     listeners[i].getListenerFunction().apply(listeners[i].getThisArg(), args);
16577   }
16578 };
16579 
16580 net.user1.orbiter.MessageManager.prototype.dispose = function () {
16581   this.log.info("[MESSAGE_MANAGER] Disposing resources.");
16582   this.log = null;
16583   this.orbiter = null;
16584   this.messageListeners = null;
16585   this.numMessagesSent = 0;
16586   this.numMessagesReceived = 0;
16587   this.currentConnection = null;
16588 }
16589   
16590 net.user1.orbiter.MessageManager.prototype.toString = function () {
16591   return "[object MessageManager]";
16592 };
16593 
16594 //==============================================================================
16595 // UPC CONSTANTS
16596 //==============================================================================
16597 /** @class */
16598 net.user1.orbiter.UPC = new Object();
16599 
16600 // CLIENT TO SERVER
16601 /** @constant */
16602 net.user1.orbiter.UPC.SEND_MESSAGE_TO_ROOMS = "u1";            
16603 /** @constant */
16604 net.user1.orbiter.UPC.SEND_MESSAGE_TO_CLIENTS = "u2";            
16605 /** @constant */
16606 net.user1.orbiter.UPC.SET_CLIENT_ATTR = "u3";       
16607 /** @constant */
16608 net.user1.orbiter.UPC.JOIN_ROOM = "u4";             
16609 /** @constant */
16610 net.user1.orbiter.UPC.SET_ROOM_ATTR = "u5";         
16611 /** @constant */
16612 net.user1.orbiter.UPC.LEAVE_ROOM = "u10";           
16613 /** @constant */
16614 net.user1.orbiter.UPC.CREATE_ACCOUNT = "u11"; 
16615 /** @constant */
16616 net.user1.orbiter.UPC.REMOVE_ACCOUNT = "u12";
16617 /** @constant */
16618 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD = "u13";
16619 /** @constant */
16620 net.user1.orbiter.UPC.LOGIN = "u14";            
16621 /** @constant */
16622 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT = "u18";                
16623 /** @constant */
16624 net.user1.orbiter.UPC.SYNC_TIME = "u19";
16625 /** @constant */
16626 net.user1.orbiter.UPC.GET_ROOMLIST_SNAPSHOT = "u21";
16627 /** @constant */
16628 net.user1.orbiter.UPC.CREATE_ROOM = "u24";                       
16629 /** @constant */
16630 net.user1.orbiter.UPC.REMOVE_ROOM = "u25";                       
16631 /** @constant */
16632 net.user1.orbiter.UPC.WATCH_FOR_ROOMS = "u26";            
16633 /** @constant */
16634 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS = "u27"; 
16635 /** @constant */
16636 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT = "u55";
16637 /** @constant */
16638 net.user1.orbiter.UPC.SEND_MESSAGE_TO_SERVER = "u57"; 
16639 /** @constant */
16640 net.user1.orbiter.UPC.OBSERVE_ROOM = "u58"; 
16641 /** @constant */
16642 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM = "u61"; 
16643 /** @constant */
16644 net.user1.orbiter.UPC.SET_ROOM_UPDATE_LEVELS = "u64"; 
16645 /** @constant */
16646 net.user1.orbiter.UPC.CLIENT_HELLO = "u65"; 
16647 /** @constant */
16648 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR = "u67"; 
16649 /** @constant */
16650 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR = "u69"; 
16651 /** @constant */
16652 net.user1.orbiter.UPC.SEND_ROOMMODULE_MESSAGE = "u70"; 
16653 /** @constant */
16654 net.user1.orbiter.UPC.SEND_SERVERMODULE_MESSAGE = "u71"; 
16655 /** @constant */
16656 net.user1.orbiter.UPC.TERMINATE_SESSION = "u83";
16657 /** @constant */
16658 net.user1.orbiter.UPC.LOGOFF = "u86";  
16659 /** @constant */
16660 net.user1.orbiter.UPC.GET_CLIENTLIST_SNAPSHOT = "u91";  
16661 /** @constant */
16662 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS = "u92";  
16663 /** @constant */
16664 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS = "u93";  
16665 /** @constant */
16666 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT = "u94";  
16667 /** @constant */
16668 net.user1.orbiter.UPC.OBSERVE_CLIENT = "u95";  
16669 /** @constant */
16670 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT = "u96";  
16671 /** @constant */
16672 net.user1.orbiter.UPC.GET_ACCOUNTLIST_SNAPSHOT = "u97";  
16673 /** @constant */
16674 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS = "u98";  
16675 /** @constant */
16676 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS = "u99";  
16677 /** @constant */
16678 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT = "u100";  
16679 /** @constant */
16680 net.user1.orbiter.UPC.OBSERVE_ACCOUNT = "u121";  
16681 /** @constant */
16682 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT = "u122"; 
16683 /** @constant */
16684 net.user1.orbiter.UPC.ADD_ROLE = "u133";  
16685 /** @constant */
16686 net.user1.orbiter.UPC.REMOVE_ROLE = "u135";  
16687 /** @constant */
16688 net.user1.orbiter.UPC.KICK_CLIENT = "u149";  
16689 /** @constant */
16690 net.user1.orbiter.UPC.BAN = "u137";  
16691 /** @constant */
16692 net.user1.orbiter.UPC.UNBAN = "u139";  
16693 /** @constant */
16694 net.user1.orbiter.UPC.GET_BANNED_LIST_SNAPSHOT = "u141";  
16695 /** @constant */
16696 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES = "u143";  
16697 /** @constant */
16698 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES = "u145";  
16699 /** @constant */
16700 net.user1.orbiter.UPC.GET_NODELIST_SNAPSHOT = "u165";  
16701 /** @constant */
16702 net.user1.orbiter.UPC.GET_GATEWAYS_SNAPSHOT = "u167";
16703 
16704 // SERVER TO CLIENT
16705 /** @constant */
16706 net.user1.orbiter.UPC.JOINED_ROOM = "u6";
16707 /** @constant */
16708 net.user1.orbiter.UPC.RECEIVE_MESSAGE = "u7";
16709 /** @constant */
16710 net.user1.orbiter.UPC.CLIENT_ATTR_UPDATE = "u8";
16711 /** @constant */
16712 net.user1.orbiter.UPC.ROOM_ATTR_UPDATE = "u9";
16713 /** @constant */
16714 net.user1.orbiter.UPC.CLIENT_METADATA = "u29";
16715 /** @constant */
16716 net.user1.orbiter.UPC.CREATE_ROOM_RESULT = "u32";
16717 /** @constant */
16718 net.user1.orbiter.UPC.REMOVE_ROOM_RESULT = "u33";
16719 /** @constant */
16720 net.user1.orbiter.UPC.CLIENTCOUNT_SNAPSHOT = "u34";
16721 /** @constant */
16722 net.user1.orbiter.UPC.CLIENT_ADDED_TO_ROOM = "u36";
16723 /** @constant */
16724 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_ROOM = "u37";
16725 /** @constant */
16726 net.user1.orbiter.UPC.ROOMLIST_SNAPSHOT = "u38";
16727 /** @constant */
16728 net.user1.orbiter.UPC.ROOM_ADDED = "u39";
16729 /** @constant */
16730 net.user1.orbiter.UPC.ROOM_REMOVED = "u40";
16731 /** @constant */
16732 net.user1.orbiter.UPC.WATCH_FOR_ROOMS_RESULT = "u42";
16733 /** @constant */
16734 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ROOMS_RESULT = "u43";
16735 /** @constant */
16736 net.user1.orbiter.UPC.LEFT_ROOM = "u44";
16737 /** @constant */
16738 net.user1.orbiter.UPC.CHANGE_ACCOUNT_PASSWORD_RESULT = "u46";
16739 /** @constant */
16740 net.user1.orbiter.UPC.CREATE_ACCOUNT_RESULT = "u47";
16741 /** @constant */
16742 net.user1.orbiter.UPC.REMOVE_ACCOUNT_RESULT = "u48";
16743 /** @constant */
16744 net.user1.orbiter.UPC.LOGIN_RESULT = "u49";
16745 /** @constant */
16746 net.user1.orbiter.UPC.SERVER_TIME_UPDATE = "u50";
16747 /** @constant */
16748 net.user1.orbiter.UPC.ROOM_SNAPSHOT = "u54";
16749 /** @constant */
16750 net.user1.orbiter.UPC.OBSERVED_ROOM = "u59";
16751 /** @constant */
16752 net.user1.orbiter.UPC.GET_ROOM_SNAPSHOT_RESULT = "u60";
16753 /** @constant */
16754 net.user1.orbiter.UPC.STOPPED_OBSERVING_ROOM = "u62";
16755 /** @constant */
16756 net.user1.orbiter.UPC.CLIENT_READY = "u63";
16757 /** @constant */
16758 net.user1.orbiter.UPC.SERVER_HELLO = "u66";
16759 /** @constant */
16760 net.user1.orbiter.UPC.JOIN_ROOM_RESULT = "u72";
16761 /** @constant */
16762 net.user1.orbiter.UPC.SET_CLIENT_ATTR_RESULT = "u73";
16763 /** @constant */
16764 net.user1.orbiter.UPC.SET_ROOM_ATTR_RESULT = "u74";
16765 /** @constant */
16766 net.user1.orbiter.UPC.GET_CLIENTCOUNT_SNAPSHOT_RESULT = "u75";
16767 /** @constant */
16768 net.user1.orbiter.UPC.LEAVE_ROOM_RESULT = "u76";
16769 /** @constant */
16770 net.user1.orbiter.UPC.OBSERVE_ROOM_RESULT = "u77";
16771 /** @constant */
16772 net.user1.orbiter.UPC.STOP_OBSERVING_ROOM_RESULT = "u78";
16773 /** @constant */
16774 net.user1.orbiter.UPC.ROOM_ATTR_REMOVED = "u79";
16775 /** @constant */
16776 net.user1.orbiter.UPC.REMOVE_ROOM_ATTR_RESULT = "u80";
16777 /** @constant */
16778 net.user1.orbiter.UPC.CLIENT_ATTR_REMOVED = "u81";
16779 /** @constant */
16780 net.user1.orbiter.UPC.REMOVE_CLIENT_ATTR_RESULT = "u82";
16781 /** @constant */
16782 net.user1.orbiter.UPC.SESSION_TERMINATED = "u84";
16783 /** @constant */
16784 net.user1.orbiter.UPC.SESSION_NOT_FOUND = "u85";
16785 /** @constant */
16786 net.user1.orbiter.UPC.LOGOFF_RESULT = "u87";
16787 /** @constant */
16788 net.user1.orbiter.UPC.LOGGED_IN = "u88";
16789 /** @constant */
16790 net.user1.orbiter.UPC.LOGGED_OFF = "u89";
16791 /** @constant */
16792 net.user1.orbiter.UPC.ACCOUNT_PASSWORD_CHANGED = "u90";
16793 /** @constant */
16794 net.user1.orbiter.UPC.CLIENTLIST_SNAPSHOT = "u101";
16795 /** @constant */
16796 net.user1.orbiter.UPC.CLIENT_ADDED_TO_SERVER = "u102";
16797 /** @constant */
16798 net.user1.orbiter.UPC.CLIENT_REMOVED_FROM_SERVER = "u103";
16799 /** @constant */
16800 net.user1.orbiter.UPC.CLIENT_SNAPSHOT = "u104";
16801 /** @constant */
16802 net.user1.orbiter.UPC.OBSERVE_CLIENT_RESULT = "u105";
16803 /** @constant */
16804 net.user1.orbiter.UPC.STOP_OBSERVING_CLIENT_RESULT = "u106";
16805 /** @constant */
16806 net.user1.orbiter.UPC.WATCH_FOR_CLIENTS_RESULT = "u107";
16807 /** @constant */
16808 net.user1.orbiter.UPC.STOP_WATCHING_FOR_CLIENTS_RESULT = "u108";
16809 /** @constant */
16810 net.user1.orbiter.UPC.WATCH_FOR_ACCOUNTS_RESULT = "u109";
16811 /** @constant */
16812 net.user1.orbiter.UPC.STOP_WATCHING_FOR_ACCOUNTS_RESULT = "u110";
16813 /** @constant */
16814 net.user1.orbiter.UPC.ACCOUNT_ADDED = "u111";
16815 /** @constant */
16816 net.user1.orbiter.UPC.ACCOUNT_REMOVED = "u112";
16817 /** @constant */
16818 net.user1.orbiter.UPC.JOINED_ROOM_ADDED_TO_CLIENT = "u113";
16819 /** @constant */
16820 net.user1.orbiter.UPC.JOINED_ROOM_REMOVED_FROM_CLIENT = "u114";
16821 /** @constant */
16822 net.user1.orbiter.UPC.GET_CLIENT_SNAPSHOT_RESULT = "u115";
16823 /** @constant */
16824 net.user1.orbiter.UPC.GET_ACCOUNT_SNAPSHOT_RESULT = "u116";
16825 /** @constant */
16826 net.user1.orbiter.UPC.OBSERVED_ROOM_ADDED_TO_CLIENT = "u117";
16827 /** @constant */
16828 net.user1.orbiter.UPC.OBSERVED_ROOM_REMOVED_FROM_CLIENT = "u118";
16829 /** @constant */
16830 net.user1.orbiter.UPC.CLIENT_OBSERVED = "u119";
16831 /** @constant */
16832 net.user1.orbiter.UPC.STOPPED_OBSERVING_CLIENT = "u120";
16833 /** @constant */
16834 net.user1.orbiter.UPC.OBSERVE_ACCOUNT_RESULT = "u123";
16835 /** @constant */
16836 net.user1.orbiter.UPC.ACCOUNT_OBSERVED = "u124";
16837 /** @constant */
16838 net.user1.orbiter.UPC.STOP_OBSERVING_ACCOUNT_RESULT = "u125";
16839 /** @constant */
16840 net.user1.orbiter.UPC.STOPPED_OBSERVING_ACCOUNT = "u126";
16841 /** @constant */
16842 net.user1.orbiter.UPC.ACCOUNT_LIST_UPDATE = "u127";
16843 /** @constant */
16844 net.user1.orbiter.UPC.UPDATE_LEVELS_UPDATE = "u128";
16845 /** @constant */
16846 net.user1.orbiter.UPC.CLIENT_OBSERVED_ROOM = "u129";
16847 /** @constant */
16848 net.user1.orbiter.UPC.CLIENT_STOPPED_OBSERVING_ROOM = "u130";
16849 /** @constant */
16850 net.user1.orbiter.UPC.ROOM_OCCUPANTCOUNT_UPDATE = "u131";
16851 /** @constant */
16852 net.user1.orbiter.UPC.ROOM_OBSERVERCOUNT_UPDATE = "u132";
16853 /** @constant */
16854 net.user1.orbiter.UPC.ADD_ROLE_RESULT = "u134";
16855 /** @constant */
16856 net.user1.orbiter.UPC.REMOVE_ROLE_RESULT = "u136";
16857 /** @constant */
16858 net.user1.orbiter.UPC.BAN_RESULT = "u138";
16859 /** @constant */
16860 net.user1.orbiter.UPC.UNBAN_RESULT = "u140";
16861 /** @constant */
16862 net.user1.orbiter.UPC.BANNED_LIST_SNAPSHOT = "u142";
16863 /** @constant */
16864 net.user1.orbiter.UPC.WATCH_FOR_BANNED_ADDRESSES_RESULT = "u144";
16865 /** @constant */
16866 net.user1.orbiter.UPC.STOP_WATCHING_FOR_BANNED_ADDRESSES_RESULT = "u146";
16867 /** @constant */
16868 net.user1.orbiter.UPC.BANNED_ADDRESS_ADDED = "u147";
16869 /** @constant */
16870 net.user1.orbiter.UPC.BANNED_ADDRESS_REMOVED = "u148";
16871 /** @constant */
16872 net.user1.orbiter.UPC.KICK_CLIENT_RESULT = "u150";
16873 /** @constant */
16874 net.user1.orbiter.UPC.SERVERMODULELIST_SNAPSHOT = "u152";
16875 /** @constant */
16876 net.user1.orbiter.UPC.GET_UPC_STATS_SNAPSHOT_RESULT = "u155";
16877 /** @constant */
16878 net.user1.orbiter.UPC.UPC_STATS_SNAPSHOT = "u156";
16879 /** @constant */
16880 net.user1.orbiter.UPC.RESET_UPC_STATS_RESULT = "u158";
16881 /** @constant */
16882 net.user1.orbiter.UPC.WATCH_FOR_PROCESSED_UPCS_RESULT = "u160";
16883 /** @constant */
16884 net.user1.orbiter.UPC.PROCESSED_UPC_ADDED = "u161";
16885 /** @constant */
16886 net.user1.orbiter.UPC.STOP_WATCHING_FOR_PROCESSED_UPCS_RESULT = "u163";
16887 /** @constant */
16888 net.user1.orbiter.UPC.CONNECTION_REFUSED = "u164";
16889 /** @constant */
16890 net.user1.orbiter.UPC.NODELIST_SNAPSHOT = "u166";
16891 /** @constant */
16892 net.user1.orbiter.UPC.GATEWAYS_SNAPSHOT = "u168";
16893 //==============================================================================
16894 // LOADED FLAG
16895 //==============================================================================
16896 /** 
16897  * @constant 
16898  * 
16899  * Indicates that Orbiter has finished loading.
16900  */
16901 net.user1.orbiter.LOADED = true;
16902 
16903 })((typeof window == "undefined") ? this : window);
16904