text: /*
   postal.socket
   Author: Jim Cowart
   License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
   Version 0.1.0
   */
  
  (function ( root, doc, factory ) {
  	if ( typeof define === "function" && define.amd ) {
  		// AMD. Register as an anonymous module.
  		define( [ "underscore", "machina", "postal" ], function ( _, machina, postal ) {
  			return factory( _, machina, postal, root, doc );
  		} );
  	} else {
  		// Browser globals
  		factory( root._, root.machina, root.postal, root, doc );
  	}
  }( this, document, function ( _, machina, postal, global, document, undefined ) {
  
  	/*
  	 adding a socket namespace to postal
  	 which provides the following members:
  	 1.) config - provides values used to manage the socket connection
  	 2.) goOffline() - tells the manager to close the connection intentionally
  	 3.) goOnline() - tells the manager to try to connect.
  	 4.) manifest - an array of objects describing the subscriptions that have been
  	 set up on the remote end.
  	 5.) publish() - takes a valid postal envelope and pushes it through the socket
  	 to be published to the server instance of postal.
  	 6.) socketMgr - the FSM managing the socket connection
  	 7.) socketNamespace - exposed currently for debugging only
  	 8.) subscribe() - passes an object through the socket to the server
  	 which contains data necessary to set up a remote subscription.  The
  	 options passed to the socket would look similar to this:
  	 {
  	 "channel":"SomeChannel",
  	 "topic":"my.topic",
  	 "correlationId":"2073383865318591267"
  	 }
  	 The "correlationId" is used on the remote side to apply a constraint to
  	 the subscription, enabling just this specific client to be targeted on
  	 and otherwise public channel.
  	 9.) unsubscribe() - passes an object through the socket to the server
  	 which contains data necessary to remove a remote subscription.  The options
  	 passed would look similar to the example above in #8.
  	 */
  	postal.connections = postal.connections || {};
  
  	var postalSocket = postal.connections.socket = (function () {
  		var socketNamespace,
  			fsm = new machina.Fsm( {
  				retryFn : undefined,
  
  				session : undefined,
  
  				wireUpSocketEvents : function () {
  					var self = this;
  					_.each( [ "connect", "connecting", "connect_failed", "disconnect", "reconnect", "reconnect_failed",
  					          "reconnecting", "postal.socket.remote", "postal.socket.identified", "postal.socket.migration" ],
  						function ( evnt ) {
  							socketNamespace.on( evnt, function ( data ) {
  								self.handle( evnt, data );
  							} );
  						} );
  				},
  
  				states : {
  					uninitialized : {
  						tryConnect : function () {
  							this.transition( "initializing" );
  						}
  					},
  					initializing : {
  						_onEnter : function () {
  							socketNamespace = io.connect( postalSocket.config.url, { "auto connect" : false } );
  							this.wireUpSocketEvents();
  							this.transition( "probing" )
  						},
  						socketTransmit : function () {
  							this.deferUntilTransition( "online" );
  						}
  					},
  					probing : {
  						_onEnter : function () {
  							clearTimeout( this.retryFn );
  							if ( !socketNamespace.socket.connecting && !socketNamespace.socket.reconnecting ) {
  								socketNamespace.socket.connect();
  							}
  							else {
  								this.transition( "settingSessionInfo" );
  							}
  						},
  						connect : function () {
  							this.transition( "settingSessionInfo" );
  						},
  						connect_failed : function () {
  							this.transition( "disconnected" );
  						},
  						maxAttempts : function () {
  							this.transition( "offline" );
  						},
  						"postal.socket.remote" : function () {
  							this.deferUntilTransition( "online" );
  						},
  						reconnect : function () {
  							this.transition( "settingSessionInfo" );
  						},
  						reconnect_failed : function () {
  							this.transition( "disconnected" );
  						},
  						socketTransmit : function () {
  							this.deferUntilTransition( "online" );
  						}
  					},
  					settingSessionInfo : {
  						_onEnter : function () {
  							var self = this;
  							postal.utils.setSessionId( socketNamespace.socket.sessionid, function ( session ) {
  								self.session = session;
  								self.transition( "identifying" );
  							} );
  						}
  					},
  					identifying : {
  						_onEnter : function () {
  							var self = this;
  							self.retryFn = setTimeout( function () {
  								self.handle( "timeout.identifying" );
  							}, postalSocket.config.reconnectInterval );
  							self.handle( "client.identifier" );
  						},
  						"client.identifier" : function () {
  							clearTimeout( this.retryFn );
  							socketNamespace.emit( "postal.clientId", { id : this.session.id, lastId : this.session.lastId } );
  						},
  						connect_failed : function () {
  							this.transition( "disconnected" );
  						},
  						disconnect : function () {
  							this.transition( "probing" );
  						},
  						"postal.socket.identified" : function ( data ) {
  							this.transition( "online" );
  						},
  						"postal.socket.migration" : function () {
  							_.each( postal.connections.socket.manifest, function ( sub ) {
  								fsm.handle( "socketTransmit", "postal.subscribe", sub );
  							} );
  							socketNamespace.emit( "postal.migrationComplete", {} );
  						},
  						"postal.socket.remote" : function () {
  							this.deferUntilTransition( "online" );
  						},
  						reconnect_failed : function () {
  							this.transition( "disconnected" );
  						},
  						socketTransmit : function ( evntName, envelope ) {
  							if ( evntName === "postal.subscribe" ) {
  								// we risk mutating the message here, so extend
  								// and add the correlationId to the extended copy
  								var socketEnv = _.extend( {}, envelope );
  								socketEnv.correlationId = this.session.id;
  								socketNamespace.emit( evntName, socketEnv );
  							}
  							else {
  								this.deferUntilTransition( "online" );
  							}
  						},
  						"timeout.identifying" : function () {
  							this.transition( "probing" );
  						}
  					},
  					online : {
  						disconnect : function () {
  							this.transition( "probing" );
  						},
  						goOffline : function () {
  							this.transition( "offline" );
  						},
  						"postal.socket.remote" : function ( envelope ) {
  							postal.publish( envelope );
  						},
  						socketTransmit : function ( evntName, envelope ) {
  							// we risk mutating the message here, so extend
  							// and add the correlationId to the extended copy
  							var socketEnv = _.extend( {}, envelope );
  							socketEnv.correlationId = this.session.id;
  							socketNamespace.emit( evntName, socketEnv );
  						}
  					},
  					offline : {
  						_onEnter : function () {
  							socketNamespace.socket.disconnect();
  						},
  						socketTransmit : function () {
  							this.deferUntilTransition( "online" );
  						},
  						"tryConnect" : function () {
  							this.transition( "probing" );
  						}
  					},
  					disconnected : {
  						_onEnter : function () {
  							var self = this;
  							self.retryFn = setTimeout( function () {
  								self.transition( "probing" );
  							}, postalSocket.config.reconnectInterval );
  						},
  						connecting : function () {
  							this.transition( "probing" );
  						},
  						reconnecting : function () {
  							this.transition( "probing" );
  						},
  						socketTransmit : function () {
  							this.deferUntilTransition( "online" );
  						}
  					}
  				}
  			} );
  		postal.subscribe( {
  			channel : "postal",
  			topic : "sessionId.changed",
  			callback : function () {
  				fsm.handle( "postal.session.changed" );
  			}
  		} );
  		return {
  			config : {
  				url : window.location.origin,
  				reconnectInterval : 4000
  			},
  			goOffline : function () {
  				fsm.handle( "goOffline" );
  			},
  			goOnline : function () {
  				fsm.handle( "tryConnect" );
  			},
  			manifest : [],
  			publish : function ( envelope ) {
  				fsm.handle( "socketTransmit", "postal.publish", envelope );
  			},
  			subscribe : function ( options ) {
  				options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
  				options.topic = options.topic || "*";
  				if ( !_.any( this.manifest, function ( item ) {
  					return item.channel === options.channel && item.topic === options.topic;
  				} ) ) {
  					this.manifest.push( options );
  					fsm.handle( "socketTransmit", "postal.subscribe", options );
  				}
  			},
  			socketMgr : fsm,
  			socketNamespace : socketNamespace,
  			unsubscribe : function ( options ) {
  				options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
  				options.topic = options.topic || "*";
  				if ( !postal.getSubscribersFor( options.channel, options.topic ).length ) {
  					fsm.handle( "socketTransmit", "postal.unsubscribe", options );
  				}
  			}
  		}
  	})();
  
  	postal.connections.socket.goOnline();
  	var SocketChannel = postal.channelTypes.websocket = function ( channelName, defaultTopic ) {
  		var channel = postal.channel( channelName, defaultTopic ),
  			localSubscribe = channel.subscribe,
  			localPublish = channel.publish,
  			localTopic = channel.topic;
  
  		channel.publish = function () {
  			postalSocket.publish( localPublish.apply( channel, arguments ) );
  		};
  
  		channel.subscribe = function () {
  			var sub = localSubscribe.apply( channel, arguments ),
  				origUnsubscribe;
  			origUnsubscribe = sub.unsubscribe;
  			sub.unsubscribe = function () {
  				origUnsubscribe.call( sub );
  				postalSocket.unsubscribe( { channel : sub.channel, topic : sub.topic } );
  			};
  			postalSocket.subscribe( { channel : sub.channel, topic : sub.topic } );
  			return sub;
  		};
  
  		channel.topic = function ( topic ) {
  			if ( topic === channel._topic ) {
  				return this;
  			}
  			return new SocketChannel( this.channel, topic );
  		};
  
  		return channel;
  	};
  
  } ));