2012-09-28 08:20:43 +00:00
// The wrapper around the web-socket connection to the server
2014-04-09 17:25:52 +00:00
// manages the connection, heartbeats, and reconnect logic.
// presents itself as a dialog, or in-situ banner (_jamServer.html.haml)
( function ( context , $ ) {
"use strict" ;
context . JK = context . JK || { } ;
var logger = context . JK . logger ;
var msg _factory = context . JK . MessageFactory ;
2014-05-19 21:57:08 +00:00
var rest = context . JK . Rest ( ) ;
2014-12-18 21:13:55 +00:00
var EVENTS = context . JK . EVENTS ;
2014-05-19 21:57:08 +00:00
2014-04-09 17:25:52 +00:00
// Let socket.io know where WebSocketMain.swf is
context . WEB _SOCKET _SWF _LOCATION = "assets/flash/WebSocketMain.swf" ;
2014-05-19 21:57:08 +00:00
context . JK . JamServer = function ( app , activeElementEvent ) {
2014-04-09 17:25:52 +00:00
2014-04-30 20:29:10 +00:00
// uniquely identify the websocket connection
var channelId = null ;
var clientType = null ;
2014-05-19 13:46:03 +00:00
var mode = null ;
2014-04-30 20:29:10 +00:00
2014-04-09 17:25:52 +00:00
// heartbeat
var heartbeatInterval = null ;
var heartbeatMS = null ;
2014-04-30 20:29:10 +00:00
var connection _expire _time = null ;
2014-04-28 19:47:24 +00:00
var lastHeartbeatSentTime = null ;
2014-04-09 17:25:52 +00:00
var lastHeartbeatAckTime = null ;
var lastHeartbeatFound = false ;
2014-04-30 03:01:28 +00:00
var lastDisconnectedReason = null ;
2014-04-09 17:25:52 +00:00
var heartbeatAckCheckInterval = null ;
var notificationLastSeenAt = undefined ;
var notificationLastSeen = undefined ;
2014-04-30 03:01:28 +00:00
var clientClosedConnection = false ;
2014-06-19 23:51:47 +00:00
var initialConnectAttempt = true ;
2014-04-09 17:25:52 +00:00
// reconnection logic
var connectDeferred = null ;
var freezeInteraction = false ;
var countdownInterval = null ;
var reconnectAttemptLookup = [ 2 , 2 , 2 , 4 , 8 , 15 , 30 ] ;
var reconnectAttempt = 0 ;
var reconnectingWaitPeriodStart = null ;
var reconnectDueTime = null ;
var connectTimeout = null ;
// elements
var $inSituBanner = null ;
var $inSituBannerHolder = null ;
var $messageContents = null ;
var $dialog = null ;
var $templateServerConnection = null ;
2014-09-26 04:13:34 +00:00
var $templateNoLogin = null ;
2014-04-09 17:25:52 +00:00
var $templateDisconnected = null ;
var $currentDisplay = null ;
2012-09-28 08:20:43 +00:00
2014-05-19 21:57:08 +00:00
var $self = $ ( this ) ;
2012-10-15 09:23:58 +00:00
var server = { } ;
2012-09-28 08:20:43 +00:00
server . socket = { } ;
2012-10-16 08:44:33 +00:00
server . signedIn = false ;
server . clientID = "" ;
server . publicIP = "" ;
2012-09-28 08:20:43 +00:00
server . dispatchTable = { } ;
2013-09-01 17:36:07 +00:00
server . socketClosedListeners = [ ] ;
2014-08-15 02:31:34 +00:00
server . connecting = false ; // is the websocket connection being opened?
server . connected = false ; // is the websocket connection opened AND logged in?
server . reconnecting = false ; // are we beginning the reconnect sequence (which includes an internet health check)
2012-09-28 08:20:43 +00:00
2014-04-30 03:01:28 +00:00
function heartbeatStateReset ( ) {
lastHeartbeatSentTime = null ;
lastHeartbeatAckTime = null ;
lastHeartbeatFound = false ;
}
2013-09-01 16:28:03 +00:00
2014-04-09 17:25:52 +00:00
// if activeElementVotes is null, then we are assuming this is the initial connect sequence
function initiateReconnect ( activeElementVotes , in _error ) {
var initialConnect = ! ! activeElementVotes ;
freezeInteraction = activeElementVotes && ( ( activeElementVotes . dialog && activeElementVotes . dialog . freezeInteraction === true ) || ( activeElementVotes . screen && activeElementVotes . screen . freezeInteraction === true ) ) ;
2014-04-28 19:51:27 +00:00
if ( in _error ) {
2014-04-09 17:25:52 +00:00
reconnectAttempt = 0 ;
$currentDisplay = renderDisconnected ( ) ;
beginReconnectPeriod ( ) ;
}
}
2014-01-06 21:08:37 +00:00
// handles logic if the websocket connection closes, and if it was in error then also prompt for reconnect
2013-09-01 16:28:03 +00:00
function closedCleanup ( in _error ) {
2014-12-18 21:13:55 +00:00
if ( server . connected ) {
$self . triggerHandler ( EVENTS . CONNECTION _DOWN ) ;
}
2014-06-19 01:59:52 +00:00
server . connected = false ;
2014-08-15 02:31:34 +00:00
server . connecting = false ;
2014-04-09 17:25:52 +00:00
// stop future heartbeats
if ( heartbeatInterval != null ) {
clearInterval ( heartbeatInterval ) ;
heartbeatInterval = null ;
}
// stop checking for heartbeat acks
if ( heartbeatAckCheckInterval != null ) {
clearTimeout ( heartbeatAckCheckInterval ) ;
heartbeatAckCheckInterval = null ;
}
2014-06-19 23:51:47 +00:00
clearConnectTimeout ( ) ;
// noReconnect is a global to suppress reconnect behavior, so check it first
// we don't show any reconnect dialog on the initial connect; so we have this one-time flag
// to cause reconnects in the case that the websocket is down on the initially connect
2014-09-26 04:13:34 +00:00
if ( server . noReconnect ) {
renderLoginRequired ( ) ;
}
else if ( ( initialConnectAttempt || ! server . reconnecting ) ) {
2014-08-15 02:31:34 +00:00
server . reconnecting = true ;
2014-06-19 23:51:47 +00:00
initialConnectAttempt = false ;
2014-04-09 17:25:52 +00:00
2014-05-19 21:57:08 +00:00
var result = activeElementEvent ( 'beforeDisconnect' ) ;
2014-04-09 17:25:52 +00:00
initiateReconnect ( result , in _error ) ;
2014-05-19 21:57:08 +00:00
activeElementEvent ( 'afterDisconnect' ) ;
2014-04-09 17:25:52 +00:00
// notify anyone listening that the socket closed
var len = server . socketClosedListeners . length ;
for ( var i = 0 ; i < len ; i ++ ) {
try {
server . socketClosedListeners [ i ] ( in _error ) ;
} catch ( ex ) {
logger . warn ( 'exception in callback for websocket closed event:' + ex ) ;
}
}
}
}
////////////////////
//// HEARTBEAT /////
////////////////////
function _heartbeatAckCheck ( ) {
// if we've seen an ack to the latest heartbeat, don't bother with checking again
// this makes us resilient to front-end hangs
if ( lastHeartbeatFound ) {
return ;
}
// check if the server is still sending heartbeat acks back down
// this logic equates to 'if we have not received a heartbeat within heartbeatMissedMS, then get upset
2014-04-30 20:29:10 +00:00
if ( new Date ( ) . getTime ( ) - lastHeartbeatAckTime . getTime ( ) > connection _expire _time ) {
logger . error ( "no heartbeat ack received from server after " , connection _expire _time , " seconds . giving up on socket connection" ) ;
2014-04-30 03:01:28 +00:00
lastDisconnectedReason = 'NO_HEARTBEAT_ACK' ;
2014-04-09 17:25:52 +00:00
context . JK . JamServer . close ( true ) ;
}
else {
lastHeartbeatFound = true ;
}
2013-09-01 16:28:03 +00:00
}
2013-09-01 17:36:07 +00:00
2014-04-09 17:25:52 +00:00
function _heartbeat ( ) {
if ( app . heartbeatActive ) {
var message = context . JK . MessageFactory . heartbeat ( notificationLastSeen , notificationLastSeenAt ) ;
notificationLastSeenAt = undefined ;
notificationLastSeen = undefined ;
2014-04-28 19:47:24 +00:00
// for debugging purposes, see if the last time we've sent a heartbeat is way off (500ms) of the target interval
var now = new Date ( ) ;
2014-04-28 19:47:24 +00:00
if ( lastHeartbeatSentTime ) {
2014-04-28 19:47:24 +00:00
var drift = new Date ( ) . getTime ( ) - lastHeartbeatSentTime . getTime ( ) - heartbeatMS ;
2014-05-19 13:46:03 +00:00
if ( drift > 500 ) {
logger . warn ( "significant drift between heartbeats: " + drift + 'ms beyond target interval' )
2014-04-28 19:47:24 +00:00
}
}
lastHeartbeatSentTime = now ;
2014-04-09 17:25:52 +00:00
context . JK . JamServer . send ( message ) ;
lastHeartbeatFound = false ;
}
}
2014-05-19 13:46:03 +00:00
function isClientMode ( ) {
return mode == "client" ;
}
2014-06-19 23:51:47 +00:00
function clearConnectTimeout ( ) {
2014-06-19 19:52:08 +00:00
if ( connectTimeout ) {
2014-04-09 17:25:52 +00:00
clearTimeout ( connectTimeout ) ;
connectTimeout = null ;
}
2014-06-19 23:51:47 +00:00
}
function loggedIn ( header , payload ) {
2015-07-15 15:04:45 +00:00
// reason for setTimeout:
// loggedIn causes an absolute ton of initialization to happen, and errors sometimes happen
// but because loggedIn(header,payload) is a callback from a websocket, the browser doesn't show a stack trace...
setTimeout ( function ( ) {
server . signedIn = true ;
server . clientID = payload . client _id ;
server . publicIP = payload . public _ip ;
if ( context . jamClient !== undefined ) {
context . jamClient . connected = true ;
context . jamClient . clientID = server . clientID ;
}
2014-08-15 14:32:47 +00:00
2015-07-15 15:04:45 +00:00
clearConnectTimeout ( ) ;
2014-08-15 14:32:47 +00:00
2015-07-15 15:04:45 +00:00
heartbeatStateReset ( ) ;
2014-04-09 17:25:52 +00:00
2015-07-15 15:04:45 +00:00
app . clientId = payload . client _id ;
2014-04-30 03:01:28 +00:00
2015-07-15 15:04:45 +00:00
if ( isClientMode ( ) ) {
// tell the backend that we have logged in
context . jamClient . OnLoggedIn ( payload . user _id , payload . token ) ; // ACTS AS CONTINUATION
$ . cookie ( 'client_id' , payload . client _id ) ;
}
2014-04-30 03:01:28 +00:00
2015-07-15 15:04:45 +00:00
// this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
// where there is no device on startup for the current profile.
// So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
// it to go through unless we've passed through .OnLoggedIn
server . connected = true ;
server . reconnecting = false ;
server . connecting = false ;
initialConnectAttempt = false ;
2014-06-19 19:52:08 +00:00
2015-07-15 15:04:45 +00:00
heartbeatMS = payload . heartbeat _interval * 1000 ;
connection _expire _time = payload . connection _expire _time * 1000 ;
logger . info ( "loggedIn(): clientId=" + app . clientId + " heartbeat=" + payload . heartbeat _interval + "s expire_time=" + payload . connection _expire _time + 's' ) ;
heartbeatInterval = context . setInterval ( _heartbeat , heartbeatMS ) ;
heartbeatAckCheckInterval = context . setInterval ( _heartbeatAckCheck , 1000 ) ;
lastHeartbeatAckTime = new Date ( new Date ( ) . getTime ( ) + heartbeatMS ) ; // add a little forgiveness to server for initial heartbeat
connectDeferred . resolve ( ) ;
$self . triggerHandler ( EVENTS . CONNECTION _UP )
2014-04-09 17:25:52 +00:00
2015-07-15 15:04:45 +00:00
activeElementEvent ( 'afterConnect' , payload ) ;
2014-08-31 15:30:59 +00:00
2015-07-15 15:04:45 +00:00
if ( payload . client _update && context . JK . ClientUpdateInstance ) {
context . JK . ClientUpdateInstance . runCheck ( payload . client _update . product , payload . client _update . version , payload . client _update . uri , payload . client _update . size )
}
} , 0 )
2013-09-01 17:36:07 +00:00
}
2014-04-09 17:25:52 +00:00
function heartbeatAck ( header , payload ) {
lastHeartbeatAckTime = new Date ( ) ;
}
function registerLoginAck ( ) {
logger . debug ( "register for loggedIn to set clientId" ) ;
context . JK . JamServer . registerMessageCallback ( context . JK . MessageType . LOGIN _ACK , loggedIn ) ;
}
function registerHeartbeatAck ( ) {
logger . debug ( "register for heartbeatAck" ) ;
context . JK . JamServer . registerMessageCallback ( context . JK . MessageType . HEARTBEAT _ACK , heartbeatAck ) ;
}
function registerSocketClosed ( ) {
logger . debug ( "register for socket closed" ) ;
context . JK . JamServer . registerOnSocketClosed ( socketClosed ) ;
}
2014-08-14 16:35:17 +00:00
function registerServerRejection ( ) {
logger . debug ( "register for server rejection" ) ;
context . JK . JamServer . registerMessageCallback ( context . JK . MessageType . SERVER _REJECTION _ERROR , serverRejection ) ;
}
2014-04-09 17:25:52 +00:00
/ * *
* Called whenever the websocket closes ; this gives us a chance to cleanup things that should be stopped / cleared
* @ param in _error did the socket close abnormally ?
* /
function socketClosed ( in _error ) {
// tell the backend that we have logged out
context . jamClient . OnLoggedOut ( ) ;
}
2014-08-14 16:35:17 +00:00
function serverRejection ( header , payload ) {
logger . warn ( "server rejected our websocket connection. reason=" + payload . error _msg )
2014-09-26 04:13:34 +00:00
if ( payload . error _code == 'max_user_connections' ) {
2014-08-14 16:35:17 +00:00
context . JK . Banner . showAlert ( "Too Many Connections" , "You have too many connections to the server. If you believe this is in error, please contact support." )
}
2014-09-26 04:13:34 +00:00
else if ( payload . error _code == 'invalid_login' || payload . error _code == 'empty_login' ) {
logger . debug ( payload . error _code + ": no longer reconnecting" )
server . noReconnect = true ; // stop trying to log in!!
}
2014-08-14 16:35:17 +00:00
}
2014-04-09 17:25:52 +00:00
///////////////////
/// RECONNECT /////
///////////////////
function internetUp ( ) {
var start = new Date ( ) . getTime ( ) ;
server . connect ( )
2014-04-28 19:51:27 +00:00
. done ( function ( ) {
2014-06-19 01:59:52 +00:00
guardAgainstRapidTransition ( start , finishReconnect ) ;
2014-04-09 17:25:52 +00:00
} )
2014-04-28 19:51:27 +00:00
. fail ( function ( ) {
2014-04-09 17:25:52 +00:00
guardAgainstRapidTransition ( start , closedOnReconnectAttempt ) ;
} ) ;
}
// websocket couldn't connect. let's try again soon
function closedOnReconnectAttempt ( ) {
failedReconnect ( ) ;
}
2014-06-19 01:59:52 +00:00
function finishReconnect ( ) {
2014-04-09 17:25:52 +00:00
2014-05-01 19:09:33 +00:00
if ( ! clientClosedConnection ) {
lastDisconnectedReason = 'WEBSOCKET_CLOSED_REMOTELY'
clientClosedConnection = false ;
}
else if ( ! lastDisconnectedReason ) {
// let's have at least some sort of type, however generci
lastDisconnectedReason = 'WEBSOCKET_CLOSED_LOCALLY'
}
2015-01-01 02:44:51 +00:00
if ( $currentDisplay . is ( '.no-websocket-connection' ) ) {
// this path is the 'not in session path'; so there is nothing else to do
$currentDisplay . removeClass ( 'active' ) ;
2014-04-09 17:25:52 +00:00
2015-01-01 02:44:51 +00:00
// TODO: tell certain elements that we've reconnected
}
else {
window . location . reload ( ) ;
}
2014-04-09 17:25:52 +00:00
}
function buildOptions ( ) {
return { } ;
}
2014-09-26 04:13:34 +00:00
function renderLoginRequired ( ) {
var $inSituContent = $ ( context . _ . template ( $templateNoLogin . html ( ) , buildOptions ( ) , { variable : 'data' } ) ) ;
$inSituContent . find ( 'a.disconnected-login' ) . click ( function ( ) {
var redirectPath = '?redirect-to=' + encodeURIComponent ( context . JK . locationPath ( ) ) ;
if ( gon . isNativeClient ) {
window . location . href = '/signin' + redirectPath ;
}
else {
window . location . href = '/' + redirectPath ;
}
return false ;
} )
$messageContents . empty ( ) ;
$messageContents . append ( $inSituContent ) ;
$inSituBannerHolder . addClass ( 'active' ) ;
return $inSituBannerHolder ;
}
2014-04-09 17:25:52 +00:00
function renderDisconnected ( ) {
var content = null ;
2014-04-28 19:51:27 +00:00
if ( freezeInteraction ) {
2014-04-09 17:25:52 +00:00
var template = $templateDisconnected . html ( ) ;
var templateHtml = $ ( context . JK . fillTemplate ( template , buildOptions ( ) ) ) ;
templateHtml . find ( '.reconnect-countdown' ) . html ( formatDelaySecs ( reconnectDelaySecs ( ) ) ) ;
content = context . JK . Banner . show ( {
2014-04-28 19:51:27 +00:00
html : templateHtml ,
2014-04-09 17:25:52 +00:00
type : 'reconnect'
2014-04-28 19:51:27 +00:00
} ) ;
2014-04-09 17:25:52 +00:00
}
else {
var $inSituContent = $ ( context . _ . template ( $templateServerConnection . html ( ) , buildOptions ( ) , { variable : 'data' } ) ) ;
$inSituContent . find ( '.reconnect-countdown' ) . html ( formatDelaySecs ( reconnectDelaySecs ( ) ) ) ;
$messageContents . empty ( ) ;
$messageContents . append ( $inSituContent ) ;
2014-05-19 13:46:03 +00:00
$inSituBannerHolder . addClass ( 'active' ) ;
2014-04-09 17:25:52 +00:00
content = $inSituBannerHolder ;
}
return content ;
}
function formatDelaySecs ( secs ) {
2014-04-28 19:51:27 +00:00
return $ ( '<span class="countdown-seconds"><span class="countdown">' + secs + '</span> ' + ( secs == 1 ? ' second.<span style="visibility:hidden">s</span>' : 'seconds.' ) + '</span>' ) ;
2014-04-09 17:25:52 +00:00
}
function setCountdown ( $parent ) {
$parent . find ( '.reconnect-countdown' ) . html ( formatDelaySecs ( reconnectDelaySecs ( ) ) ) ;
}
function renderCouldNotReconnect ( ) {
return renderDisconnected ( ) ;
}
function renderReconnecting ( ) {
$currentDisplay . find ( '.reconnect-progress-msg' ) . text ( 'Attempting to reconnect...' )
2014-04-28 19:51:27 +00:00
if ( $currentDisplay . is ( '.no-websocket-connection' ) ) {
2014-04-09 17:25:52 +00:00
$currentDisplay . find ( '.disconnected-reconnect' ) . removeClass ( 'reconnect-enabled' ) . addClass ( 'reconnect-disabled' ) ;
}
else {
$currentDisplay . find ( '.disconnected-reconnect' ) . removeClass ( 'button-orange' ) . addClass ( 'button-grey' ) ;
}
}
function failedReconnect ( ) {
reconnectAttempt += 1 ;
$currentDisplay = renderCouldNotReconnect ( ) ;
beginReconnectPeriod ( ) ;
}
function guardAgainstRapidTransition ( start , nextStep ) {
var now = new Date ( ) . getTime ( ) ;
if ( ( now - start ) < 1500 ) {
2014-04-28 19:51:27 +00:00
setTimeout ( function ( ) {
2014-04-09 17:25:52 +00:00
nextStep ( ) ;
} , 1500 - ( now - start ) )
}
else {
nextStep ( ) ;
}
}
function attemptReconnect ( ) {
2014-08-15 14:40:04 +00:00
if ( server . connecting ) {
logger . warn ( "attemptReconnect called when already connecting" ) ;
return ;
}
if ( server . connected ) {
logger . warn ( "attemptReconnect called when already connected" ) ;
return ;
}
2014-04-09 17:25:52 +00:00
var start = new Date ( ) . getTime ( ) ;
renderReconnecting ( ) ;
rest . serverHealthCheck ( )
2014-04-28 19:51:27 +00:00
. done ( function ( ) {
2014-04-09 17:25:52 +00:00
guardAgainstRapidTransition ( start , internetUp ) ;
} )
2014-04-28 19:51:27 +00:00
. fail ( function ( xhr , textStatus , errorThrown ) {
2014-04-09 17:25:52 +00:00
2014-04-28 19:51:27 +00:00
if ( xhr && xhr . status >= 100 ) {
2014-04-09 17:25:52 +00:00
// we could connect to the server, and it's alive
guardAgainstRapidTransition ( start , internetUp ) ;
}
else {
guardAgainstRapidTransition ( start , failedReconnect ) ;
}
} ) ;
return false ;
}
function clearReconnectTimers ( ) {
2014-04-28 19:51:27 +00:00
if ( countdownInterval ) {
2014-04-09 17:25:52 +00:00
clearInterval ( countdownInterval ) ;
countdownInterval = null ;
}
}
function beginReconnectPeriod ( ) {
// allow user to force reconnect
2014-04-28 19:51:27 +00:00
$currentDisplay . find ( 'a.disconnected-reconnect' ) . unbind ( 'click' ) . click ( function ( ) {
if ( $ ( this ) . is ( '.button-orange' ) || $ ( this ) . is ( '.reconnect-enabled' ) ) {
2014-08-15 02:31:34 +00:00
logger . debug ( "user initiated reconnect" )
2014-04-09 17:25:52 +00:00
clearReconnectTimers ( ) ;
attemptReconnect ( ) ;
}
return false ;
} ) ;
reconnectingWaitPeriodStart = new Date ( ) . getTime ( ) ;
reconnectDueTime = reconnectingWaitPeriodStart + reconnectDelaySecs ( ) * 1000 ;
// update count down timer periodically
2014-04-28 19:51:27 +00:00
countdownInterval = setInterval ( function ( ) {
2014-04-09 17:25:52 +00:00
var now = new Date ( ) . getTime ( ) ;
2014-04-28 19:51:27 +00:00
if ( now > reconnectDueTime ) {
2014-04-09 17:25:52 +00:00
clearReconnectTimers ( ) ;
attemptReconnect ( ) ;
}
else {
var secondsUntilReconnect = Math . ceil ( ( reconnectDueTime - now ) / 1000 ) ;
$currentDisplay . find ( '.reconnect-countdown' ) . html ( formatDelaySecs ( secondsUntilReconnect ) ) ;
2012-09-28 08:20:43 +00:00
}
2014-04-09 17:25:52 +00:00
} , 333 ) ;
}
function reconnectDelaySecs ( ) {
if ( reconnectAttempt > reconnectAttemptLookup . length - 1 ) {
return reconnectAttemptLookup [ reconnectAttemptLookup . length - 1 ] ;
}
else {
return reconnectAttemptLookup [ reconnectAttempt ] ;
}
}
server . registerOnSocketClosed = function ( callback ) {
server . socketClosedListeners . push ( callback ) ;
}
server . registerMessageCallback = function ( messageType , callback ) {
if ( server . dispatchTable [ messageType ] === undefined ) {
server . dispatchTable [ messageType ] = [ ] ;
}
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
server . dispatchTable [ messageType ] . push ( callback ) ;
2012-09-28 08:20:43 +00:00
} ;
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
server . unregisterMessageCallback = function ( messageType , callback ) {
if ( server . dispatchTable [ messageType ] !== undefined ) {
for ( var i = server . dispatchTable [ messageType ] . length ; i -- ; ) {
if ( server . dispatchTable [ messageType ] [ i ] === callback ) {
server . dispatchTable [ messageType ] . splice ( i , 1 ) ;
break ;
}
2012-09-28 08:20:43 +00:00
}
2014-04-09 17:25:52 +00:00
if ( server . dispatchTable [ messageType ] . length === 0 ) {
delete server . dispatchTable [ messageType ] ;
}
}
2012-09-28 08:20:43 +00:00
} ;
2014-04-09 17:25:52 +00:00
server . connect = function ( ) {
2014-05-19 13:46:03 +00:00
2014-08-15 02:31:34 +00:00
if ( server . connecting ) {
logger . error ( "server.connect should never be called if we are already connecting. cancelling." )
2015-02-27 17:51:45 +00:00
// XXX should return connectDeferred, but needs to be tested/vetted
2014-08-15 02:31:34 +00:00
return ;
}
if ( server . connected ) {
logger . error ( "server.connect should never be called if we are already connected. cancelling." )
2015-02-27 17:51:45 +00:00
// XXX should return connectDeferred, but needs to be tested/vetted
2014-08-15 02:31:34 +00:00
return ;
}
2014-08-15 14:40:39 +00:00
2014-08-15 14:40:04 +00:00
if ( ! clientType ) {
clientType = context . JK . clientType ( ) ;
}
if ( ! mode ) {
mode = context . jamClient . getOperatingMode ? context . jamClient . getOperatingMode ( ) : 'client' ;
}
2014-08-15 02:31:34 +00:00
2014-04-09 17:25:52 +00:00
connectDeferred = new $ . Deferred ( ) ;
2014-04-30 20:29:10 +00:00
channelId = context . JK . generateUUID ( ) ; // create a new channel ID for every websocket connection
2014-09-26 04:13:34 +00:00
var rememberToken = $ . cookie ( "remember_token" ) ;
if ( isClientMode ( ) && ! rememberToken ) {
server . noReconnect = true ;
logger . debug ( "no login info; shutting down websocket" ) ;
renderLoginRequired ( ) ;
connectDeferred . reject ( ) ;
return connectDeferred ;
}
2014-06-19 19:05:33 +00:00
// we will log in one of 3 ways:
// browser: use session cookie, and auth with token
// native: use session cookie, and use the token
// latency_tester: ask for client ID from backend; no token (trusted)
var params = {
channel _id : channelId ,
2014-09-26 04:13:34 +00:00
token : rememberToken ,
2014-07-10 15:45:08 +00:00
client _type : isClientMode ( ) ? context . JK . clientType ( ) : 'latency_tester' ,
2014-08-31 15:30:59 +00:00
client _id : isClientMode ( ) ? ( gon . global . env == "development" ? $ . cookie ( 'client_id' ) : null ) : context . jamClient . clientID ,
2014-09-13 03:30:51 +00:00
os : context . JK . GetOSAsString ( ) ,
udp _reachable : context . JK . StunInstance ? ! context . JK . StunInstance . sync ( ) : null // latency tester doesn't have the stun class loaded
2014-06-19 19:05:33 +00:00
}
var uri = context . gon . websocket _gateway _uri + '?' + $ . param ( params ) ; // Set in index.html.erb.
2014-05-19 21:57:08 +00:00
logger . debug ( "connecting websocket: " + uri ) ;
2014-04-09 17:25:52 +00:00
2014-06-19 19:52:08 +00:00
server . connecting = true ;
2014-04-09 17:25:52 +00:00
server . socket = new context . WebSocket ( uri ) ;
2014-09-17 22:19:35 +00:00
server . socket . channelId = channelId ;
2014-04-09 17:25:52 +00:00
server . socket . onopen = server . onOpen ;
server . socket . onmessage = server . onMessage ;
server . socket . onclose = server . onClose ;
2014-04-28 19:51:27 +00:00
connectTimeout = setTimeout ( function ( ) {
2014-06-19 19:52:08 +00:00
logger . debug ( "connection timeout fired" )
2014-04-09 17:25:52 +00:00
connectTimeout = null ;
2014-04-28 19:47:24 +00:00
if ( connectDeferred . state ( ) === 'pending' ) {
2014-04-28 19:47:24 +00:00
server . close ( true ) ;
2014-04-09 17:25:52 +00:00
connectDeferred . reject ( ) ;
}
} , 4000 ) ;
return connectDeferred ;
2012-09-28 08:20:43 +00:00
} ;
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
server . close = function ( in _error ) {
2014-05-19 13:46:03 +00:00
logger . info ( "closing websocket" ) ;
2013-09-01 02:18:23 +00:00
2014-04-30 03:01:28 +00:00
clientClosedConnection = true ;
2014-04-09 17:25:52 +00:00
server . socket . close ( ) ;
2013-09-01 02:18:23 +00:00
2014-04-09 17:25:52 +00:00
closedCleanup ( in _error ) ;
2013-09-01 02:18:23 +00:00
}
2013-09-01 16:28:03 +00:00
2014-04-09 17:25:52 +00:00
server . rememberLogin = function ( ) {
var token , loginMessage ;
token = $ . cookie ( "remember_token" ) ;
2014-04-30 03:01:28 +00:00
2014-04-09 17:25:52 +00:00
loginMessage = msg _factory . login _with _token ( token , null , clientType ) ;
server . send ( loginMessage ) ;
2013-03-08 15:34:05 +00:00
} ;
2014-05-19 13:46:03 +00:00
server . latencyTesterLogin = function ( ) {
var loginMessage = msg _factory . login _with _client _id ( context . jamClient . clientID , 'latency_tester' ) ;
server . send ( loginMessage ) ;
}
2014-04-09 17:25:52 +00:00
server . onOpen = function ( ) {
2014-05-19 13:46:03 +00:00
logger . debug ( "server.onOpen" ) ;
2014-06-19 19:05:33 +00:00
// we should receive LOGIN_ACK very soon. we already set a timer elsewhere to give 4 seconds to receive it
2012-09-28 08:20:43 +00:00
} ;
2014-04-09 17:25:52 +00:00
server . onMessage = function ( e ) {
var message = JSON . parse ( e . data ) ,
messageType = message . type . toLowerCase ( ) ,
payload = message [ messageType ] ,
callbacks = server . dispatchTable [ message . type ] ;
2014-07-23 16:15:14 +00:00
if ( message . type == context . JK . MessageType . PEER _MESSAGE ) {
2014-08-06 21:41:27 +00:00
//logger.info("server.onMessage:" + messageType);
2014-07-23 16:15:14 +00:00
}
else if ( message . type != context . JK . MessageType . HEARTBEAT _ACK && message . type != context . JK . MessageType . PEER _MESSAGE ) {
2014-05-19 13:46:03 +00:00
logger . info ( "server.onMessage:" + messageType + " payload:" + JSON . stringify ( payload ) ) ;
2014-04-09 17:25:52 +00:00
}
if ( callbacks !== undefined ) {
var len = callbacks . length ;
for ( var i = 0 ; i < len ; i ++ ) {
try {
callbacks [ i ] ( message , payload ) ;
} catch ( ex ) {
logger . warn ( 'exception in callback for websocket message:' + ex ) ;
throw ex ;
}
2012-09-28 08:20:43 +00:00
}
2014-04-09 17:25:52 +00:00
}
else {
2014-05-19 13:46:03 +00:00
logger . info ( "Unexpected message type %s." , message . type ) ;
2014-04-09 17:25:52 +00:00
}
2012-09-28 08:20:43 +00:00
} ;
2014-04-30 03:01:28 +00:00
// onClose is called if either client or server closes connection
2014-04-09 17:25:52 +00:00
server . onClose = function ( ) {
2014-05-19 13:46:03 +00:00
logger . info ( "Socket to server closed." ) ;
2014-04-09 17:25:52 +00:00
2014-09-17 22:19:35 +00:00
var disconnectedSocket = this ;
if ( disconnectedSocket . channelId != server . socket . channelId ) {
logger . debug ( " ignoring disconnect for non-current socket. current=" + server . socket . channelId + ", disc=" + disconnectedSocket . channelId )
return ;
2014-09-18 01:31:58 +00:00
}
2014-09-17 22:19:35 +00:00
2014-04-28 19:51:27 +00:00
if ( connectDeferred . state ( ) === "pending" ) {
2014-04-09 17:25:52 +00:00
connectDeferred . reject ( ) ;
}
2012-10-15 09:23:58 +00:00
2014-04-09 17:25:52 +00:00
closedCleanup ( true ) ;
2012-09-28 08:20:43 +00:00
} ;
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
server . send = function ( message ) {
2013-02-09 22:20:25 +00:00
2014-04-09 17:25:52 +00:00
var jsMessage = JSON . stringify ( message ) ;
2012-10-15 09:23:58 +00:00
2014-07-23 16:15:14 +00:00
if ( message . type == context . JK . MessageType . PEER _MESSAGE ) {
2014-08-06 21:41:27 +00:00
//logger.info("server.send(PEER_MESSAGE)")
2014-07-23 16:15:14 +00:00
}
else if ( message . type != context . JK . MessageType . HEARTBEAT && message . type != context . JK . MessageType . PEER _MESSAGE ) {
2014-05-19 13:46:03 +00:00
logger . info ( "server.send(" + jsMessage + ")" ) ;
2014-04-09 17:25:52 +00:00
}
if ( server !== undefined && server . socket !== undefined && server . socket . send !== undefined ) {
2015-02-27 17:51:45 +00:00
try {
server . socket . send ( jsMessage ) ;
}
catch ( err ) {
logger . warn ( "error when sending on websocket: " + err )
}
2014-04-09 17:25:52 +00:00
} else {
2014-05-19 13:46:03 +00:00
logger . warn ( "Dropped message because server connection is closed." ) ;
2014-04-09 17:25:52 +00:00
}
2012-09-28 08:20:43 +00:00
} ;
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
server . loginSession = function ( sessionId ) {
var loginMessage ;
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
if ( ! server . signedIn ) {
2014-05-19 13:46:03 +00:00
logger . warn ( "Not signed in!" ) ;
2014-04-09 17:25:52 +00:00
// TODO: surface the error
return ;
}
2012-10-14 15:48:56 +00:00
2014-04-09 17:25:52 +00:00
loginMessage = msg _factory . login _jam _session ( sessionId ) ;
server . send ( loginMessage ) ;
2012-09-28 08:20:43 +00:00
} ;
2013-11-03 20:55:55 +00:00
/ * * w i t h t h e a d v e n t o f t h e r e l i a b l e U D P c h a n n e l , t h i s i s n o l o n g e r h o w m e s s a g e s a r e s e n t f r o m c l i e n t - t o - c l e n t
* however , the mechanism still exists and is useful in test contexts ; and maybe in the future
* @ param receiver _id client ID of message to send
* @ param message the actual message
* /
2014-04-09 17:25:52 +00:00
server . sendP2PMessage = function ( receiver _id , message ) {
//logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message);
//console.time('sendP2PMessage');
var outgoing _msg = msg _factory . client _p2p _message ( server . clientID , receiver _id , message ) ;
server . send ( outgoing _msg ) ;
//console.timeEnd('sendP2PMessage');
2012-10-15 09:23:58 +00:00
} ;
2012-11-03 00:29:58 +00:00
2014-04-28 19:51:27 +00:00
server . updateNotificationSeen = function ( notificationId , notificationCreatedAt ) {
2014-04-09 17:25:52 +00:00
var time = new Date ( notificationCreatedAt ) ;
2014-04-28 19:51:27 +00:00
if ( ! notificationCreatedAt ) {
2014-04-09 17:25:52 +00:00
throw 'invalid value passed to updateNotificationSeen'
}
2014-04-28 19:51:27 +00:00
if ( ! notificationLastSeenAt ) {
2014-04-09 17:25:52 +00:00
notificationLastSeenAt = notificationCreatedAt ;
notificationLastSeen = notificationId ;
logger . debug ( "updated notificationLastSeenAt with: " + notificationCreatedAt ) ;
}
2014-04-28 19:51:27 +00:00
else if ( time . getTime ( ) > new Date ( notificationLastSeenAt ) . getTime ( ) ) {
2014-04-09 17:25:52 +00:00
notificationLastSeenAt = notificationCreatedAt ;
notificationLastSeen = notificationId ;
logger . debug ( "updated notificationLastSeenAt with: " + notificationCreatedAt ) ;
}
else {
logger . debug ( "ignored notificationLastSeenAt for: " + notificationCreatedAt ) ;
}
}
2012-10-15 09:23:58 +00:00
2014-04-09 17:25:52 +00:00
server . registerMessageCallback ( context . JK . MessageType . PEER _MESSAGE , function ( header , payload ) {
if ( context . jamClient !== undefined ) {
context . jamClient . P2PMessageReceived ( header . from , payload . message ) ;
}
2012-10-15 09:23:58 +00:00
} ) ;
2014-12-18 21:13:55 +00:00
server . get$Server = function ( ) {
return $self ;
}
2014-04-09 17:25:52 +00:00
context . JK . JamServer = server ;
2012-10-15 09:23:58 +00:00
// Callbacks from jamClient
2014-07-23 16:15:14 +00:00
if ( context . jamClient !== undefined && context . jamClient . IsNativeClient ( ) ) {
context . jamClient . SendP2PMessage . connect ( server . sendP2PMessage ) ;
2012-10-15 09:23:58 +00:00
}
2012-09-28 08:20:43 +00:00
2014-04-09 17:25:52 +00:00
function initialize ( ) {
2014-04-30 20:29:10 +00:00
2014-04-09 17:25:52 +00:00
registerLoginAck ( ) ;
registerHeartbeatAck ( ) ;
2014-08-14 16:35:17 +00:00
registerServerRejection ( ) ;
2014-04-09 17:25:52 +00:00
registerSocketClosed ( ) ;
$inSituBanner = $ ( '.server-connection' ) ;
$inSituBannerHolder = $ ( '.no-websocket-connection' ) ;
$messageContents = $inSituBannerHolder . find ( '.message-contents' ) ;
$dialog = $ ( '#banner' ) ;
$templateServerConnection = $ ( '#template-server-connection' ) ;
2014-09-26 04:13:34 +00:00
$templateNoLogin = $ ( '#template-no-login' ) ;
2014-04-09 17:25:52 +00:00
$templateDisconnected = $ ( '#template-disconnected' ) ;
2014-04-28 19:51:27 +00:00
if ( $inSituBanner . length != 1 ) {
throw "found wrong number of .server-connection: " + $inSituBanner . length ;
}
if ( $inSituBannerHolder . length != 1 ) {
throw "found wrong number of .no-websocket-connection: " + $inSituBannerHolder . length ;
}
if ( $messageContents . length != 1 ) {
throw "found wrong number of .message-contents: " + $messageContents . length ;
}
if ( $dialog . length != 1 ) {
throw "found wrong number of #banner: " + $dialog . length ;
}
if ( $templateServerConnection . length != 1 ) {
throw "found wrong number of #template-server-connection: " + $templateServerConnection . length ;
}
if ( $templateDisconnected . length != 1 ) {
throw "found wrong number of #template-disconnected: " + $templateDisconnected . length ;
}
2014-04-09 17:25:52 +00:00
}
2013-02-10 18:51:37 +00:00
2014-04-09 17:25:52 +00:00
this . initialize = initialize ;
2013-11-03 20:55:55 +00:00
2014-04-09 17:25:52 +00:00
return this ;
}
2012-10-14 15:48:56 +00:00
} ) ( window , jQuery ) ;