mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-28 03:40:25 +00:00
315 lines
No EOL
9.9 KiB
JavaScript
315 lines
No EOL
9.9 KiB
JavaScript
/*
|
|
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.getSessionId( function( session ) {
|
|
if( !session || !session.id ) {
|
|
self.handle("useFallbackSessionId" );
|
|
} else {
|
|
self.session = session;
|
|
self.transition("identifying");
|
|
}
|
|
});
|
|
},
|
|
useFallbackSessionId : 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", { sessionId: this.session.id, lastSessionId: this.session.lastId } );
|
|
},
|
|
"postal.session.changed" : function() {
|
|
socketNamespace.socket.disconnect();
|
|
},
|
|
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.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");
|
|
},
|
|
"postal.session.changed" : function() {
|
|
socketNamespace.socket.disconnect();
|
|
},
|
|
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;
|
|
};
|
|
|
|
})); |