// The wrapper around the web-socket connection to the server // 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; var rest = context.JK.Rest(); var EVENTS = context.JK.EVENTS; // Let socket.io know where WebSocketMain.swf is context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf"; context.JK.JamServer = function (app, activeElementEvent) { console.log("_DEBUG Init JK scripts....", context.JK.JamServer); // uniquely identify the websocket connection var channelId = null; var clientType = null; var mode = null; var isLatencyTesterMode = false; // heartbeat var startHeartbeatTimeout = null; var heartbeatInterval = null; var heartbeatMS = null; var connection_expire_time = null; var lastHeartbeatSentTime = null; var lastHeartbeatAckTime = null; var lastHeartbeatFound = false; var lastDisconnectedReason = null; var heartbeatAckCheckInterval = null; var notificationLastSeenAt = undefined; var notificationLastSeen = undefined; var clientClosedConnection = false; var initialConnectAttempt = true; var active = true; // 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; var activityTimeout; // elements var $inSituBanner = null; var $inSituBannerHolder = null; var $messageContents = null; var $dialog = null; var $templateServerConnection = null; var $templateNoLogin = null; var $templateDisconnected = null; var $currentDisplay = null; var $self = $(this); var server = {}; server.socket = {}; server.signedIn = false; server.clientID = ""; server.publicIP = ""; server.dispatchTable = {}; server.socketClosedListeners = []; 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) function heartbeatStateReset() { lastHeartbeatSentTime = null; lastHeartbeatAckTime = null; lastHeartbeatFound = false; } // 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)); if (in_error) { reconnectAttempt = 0; //$currentDisplay = renderDisconnected(); //beginReconnectPeriod(); } } // handles logic if the websocket connection closes, and if it was in error then also prompt for reconnect function closedCleanup(in_error) { if(isLatencyTester()) { logger.info("latency-tester: websocket connection lost") } if(server.connected) { $self.triggerHandler(EVENTS.CONNECTION_DOWN); } server.connected = false; server.connecting = false; // stop future heartbeats if (heartbeatInterval != null) { clearInterval(heartbeatInterval); heartbeatInterval = null; } // stop the heartbeat start delay from happening if (startHeartbeatTimeout != null) { clearTimeout(startHeartbeatTimeout); startHeartbeatTimeout = null; } // stop checking for heartbeat acks if (heartbeatAckCheckInterval != null) { clearTimeout(heartbeatAckCheckInterval); heartbeatAckCheckInterval = null; } 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 if(server.noReconnect) { //renderLoginRequired(); } else if ((initialConnectAttempt || !server.reconnecting)) { server.reconnecting = true; initialConnectAttempt = false; var result = activeElementEvent('beforeDisconnect'); initiateReconnect(result, in_error); activeElementEvent('afterDisconnect'); // 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 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"); lastDisconnectedReason = 'NO_HEARTBEAT_ACK'; context.JK.JamServer.close(true); } else { lastHeartbeatFound = true; } } function isClientMode() { return mode == "client"; } function isLatencyTester() { return isLatencyTesterMode; } function clearConnectTimeout() { if (connectTimeout) { clearTimeout(connectTimeout); connectTimeout = null; } } function loggedIn(header, payload) { console.log("___loggedIn", header, payload) // 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; } clearConnectTimeout(); heartbeatStateReset(); app.clientId = payload.client_id; if (isClientMode() && context.jamClient) { // tell the backend that we have logged in try { var msg = {user_id: payload.user_id, token: payload.token, username: payload.username, arses: payload.arses, client_id_int: payload.client_id_int, subscription: payload.subscription} if(payload.connection_policy) { try { msg.policy= JSON.parse(payload.connection_policy) } catch(e) { msg.policy = null console.log("unable to parse connection policy", e) } } console.log("logged with new msg", msg) context.jamClient.OnLoggedIn(msg); // ACTS AS CONTINUATION } catch(e) { console.log("fallback to old callback", e) context.jamClient.OnLoggedIn(payload.user_id, payload.token, payload.username); // ACTS AS CONTINUATION } $.cookie('client_id', payload.client_id); } // 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; 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'); // add some randomness to help move heartbeats apart from each other // send 1st heartbeat somewhere between 0 - 0.5 of the connection expire time var randomStartTime = connection_expire_time * (Math.random() / 2) if (startHeartbeatTimeout) { logger.warn("start heartbeat timeout is active; should be null") clearTimeout(startHeartbeatTimeout) } if (heartbeatInterval != null) { logger.warn("heartbeatInterval is active; should be null") clearInterval(heartbeatInterval); heartbeatInterval = null; } if (heartbeatAckCheckInterval != null) { logger.warn("heartbeatAckCheckInterval is active; should be null") clearInterval(heartbeatAckCheckInterval); heartbeatAckCheckInterval = null; } startHeartbeatTimeout = setTimeout(function() { if(server.connected) { 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 } }, randomStartTime) logger.info("starting heartbeat timer in " + randomStartTime/1000 + 's') connectDeferred.resolve(); $self.triggerHandler(EVENTS.CONNECTION_UP) activeElementEvent('afterConnect', payload); 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) } function setActive(active) { if(context.UserActivityActions) { context.UserActivityActions.setActive(active) } } function markAway() { logger.debug("sleep again!") active = false; setActive(active) var userStatus = msg_factory.userStatus(false, null); server.send(userStatus); } function activityCheck() { var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes //var timeoutTime = 2000; active = true; setActive(active) activityTimeout = setTimeout(markAway, timeoutTime); $(document).ready(function() { $('body').bind('mousedown keydown touchstart focus', function(event) { if (activityTimeout) { clearTimeout(activityTimeout); activityTimeout = null; } if (!active) { if(server && server.connected) { logger.debug("awake again!") var userStatus = msg_factory.userStatus(true, null); server.send(userStatus); } } active = true; setActive(active) activityTimeout = setTimeout(markAway, timeoutTime); }); }); } 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); } function registerServerRejection() { logger.debug("register for server rejection"); context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_REJECTION_ERROR, serverRejection); } /** * 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 if (context.jamClient) context.jamClient.OnLoggedOut(); } function serverRejection(header, payload) { logger.warn("server rejected our websocket connection. reason=" + payload.error_msg) if(payload.error_code == 'max_user_connections') { 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.") } 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!! } else if (payload.error_code == 'no_reconnect') { logger.debug(payload.error_code + ": no longer reconnecting") server.noReconnect = true; // stop trying to log in!! context.JK.Banner.showAlert("Misbehaved Client", "Please restart your application in order to continue using JamKazam.") } } /////////////////// /// RECONNECT ///// /////////////////// function internetUp() { var start = new Date().getTime(); server.connect() .done(function () { guardAgainstRapidTransition(start, finishReconnect); }) .fail(function () { guardAgainstRapidTransition(start, closedOnReconnectAttempt); }); } // websocket couldn't connect. let's try again soon function closedOnReconnectAttempt() { failedReconnect(); } function finishReconnect() { 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' } // if ($currentDisplay.is('.no-websocket-connection')) { // // this path is the 'not in session path'; so there is nothing else to do // $currentDisplay.removeClass('active'); // // TODO: tell certain elements that we've reconnected // } // else { // window.location.reload(); // } } function buildOptions() { return {}; } 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; } function renderDisconnected() { var content = null; if (freezeInteraction) { var template = $templateDisconnected.html(); var templateHtml = $(context.JK.fillTemplate(template, buildOptions())); templateHtml.find('.reconnect-countdown').html(formatDelaySecs(reconnectDelaySecs())); content = context.JK.Banner.show({ html: templateHtml, type: 'reconnect' }); } else { var $inSituContent = $(context._.template($templateServerConnection.html(), buildOptions(), { variable: 'data' })); $inSituContent.find('.reconnect-countdown').html(formatDelaySecs(reconnectDelaySecs())); $messageContents.empty(); $messageContents.append($inSituContent); $inSituBannerHolder.addClass('active'); content = $inSituBannerHolder; } return content; } function formatDelaySecs(secs) { return $('' + secs + ' ' + (secs == 1 ? ' second.s' : 'seconds.') + ''); } 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...') if ($currentDisplay.is('.no-websocket-connection')) { $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) { setTimeout(function () { nextStep(); }, 1500 - (now - start)) } else { nextStep(); } } function attemptReconnect() { if(server.connecting) { logger.warn("attemptReconnect called when already connecting"); return; } if(server.connected) { logger.warn("attemptReconnect called when already connected"); return; } var start = new Date().getTime(); //renderReconnecting(); rest.serverHealthCheck() .done(function () { guardAgainstRapidTransition(start, internetUp); }) .fail(function (xhr, textStatus, errorThrown) { if (xhr && xhr.status >= 100) { // we could connect to the server, and it's alive guardAgainstRapidTransition(start, internetUp); } else { guardAgainstRapidTransition(start, failedReconnect); } }); return false; } function clearReconnectTimers() { if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } } function beginReconnectPeriod() { // allow user to force reconnect $currentDisplay.find('a.disconnected-reconnect').unbind('click').click(function () { if ($(this).is('.button-orange') || $(this).is('.reconnect-enabled')) { logger.debug("user initiated reconnect") clearReconnectTimers(); attemptReconnect(); } return false; }); reconnectingWaitPeriodStart = new Date().getTime(); reconnectDueTime = reconnectingWaitPeriodStart + reconnectDelaySecs() * 1000; // update count down timer periodically countdownInterval = setInterval(function () { var now = new Date().getTime(); if (now > reconnectDueTime) { clearReconnectTimers(); attemptReconnect(); } else { var secondsUntilReconnect = Math.ceil((reconnectDueTime - now) / 1000); $currentDisplay.find('.reconnect-countdown').html(formatDelaySecs(secondsUntilReconnect)); } }, 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) { console.log("server.registerMessageCallback", messageType, callback) if (server.dispatchTable[messageType] === undefined) { server.dispatchTable[messageType] = []; } server.dispatchTable[messageType].push(callback); }; 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; } } if (server.dispatchTable[messageType].length === 0) { delete server.dispatchTable[messageType]; } } }; server.connect = function () { if(server.connecting) { logger.error("server.connect should never be called if we are already connecting. cancelling.") // XXX should return connectPromise, but needs to be tested/vetted return; } if(server.connected) { logger.error("server.connect should never be called if we are already connected. cancelling.") // XXX should return connectPromise, but needs to be tested/vetted return; } if(!clientType) { clientType = context.JK.clientType(); } if(!mode) { mode = 'client' if (context.jamClient && context.jamClient.getOperatingMode) { mode = context.jamClient.getOperatingMode() } isLatencyTesterMode = mode == 'server'; } connectDeferred = new $.Deferred(); channelId = context.JK.generateUUID(); // create a new channel ID for every websocket connection var rememberToken = $.cookie("remember_token"); if(isClientMode() && !rememberToken) { server.noReconnect = true; logger.debug("no login info; shutting down websocket"); //renderLoginRequired(); connectDeferred.reject(); return connectDeferred; } // 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) if (isClientMode()) { var client_type = context.JK.clientType() var client_id = (gon.global.env == "development" ? $.cookie('client_id') : null) var machine = context.jamClient.SessionGetMacHash() if (machine) { machine = machine.all } } else { var client_type = 'latency_tester' var client_id = context.jamClient.clientID var machine = null } var params = { channel_id: channelId, token: rememberToken, client_type: client_type, // isClientMode() ? context.JK.clientType() : 'latency_tester', client_id: client_id, machine: machine, os: context.JK.GetOSAsString(), //jamblaster_serial_no: context.PlatformStore.jamblasterSerialNo(), udp_reachable: context.JK.StunInstance ? !context.JK.StunInstance.sync() : null // latency tester doesn't have the stun class loaded } var uri = context.gon.websocket_gateway_uri + '?' + $.param(params); // Set in index.html.erb. logger.debug("connecting websocket: " + uri); server.connecting = true; server.socket = new context.WebSocket(uri); console.log("server.socket", server.socket) server.socket.channelId = channelId; server.socket.onopen = server.onOpen; server.socket.onmessage = server.onMessage; server.socket.onclose = server.onClose; server.socket.onerror = server.onError; connectTimeout = setTimeout(function () { logger.debug("connection timeout fired") connectTimeout = null; if(connectDeferred.state() === 'pending') { server.close(true); connectDeferred.reject(); } }, 10000); return connectDeferred; }; server.close = function (in_error) { logger.info("closing websocket"); clientClosedConnection = true; server.socket.close(); closedCleanup(in_error); } server.rememberLogin = function () { var token, loginMessage; token = $.cookie("remember_token"); loginMessage = msg_factory.login_with_token(token, null, clientType); server.send(loginMessage); }; server.latencyTesterLogin = function() { var loginMessage = msg_factory.login_with_client_id(context.jamClient.clientID, 'latency_tester'); server.send(loginMessage); } server.onOpen = function () { logger.debug("server.onOpen"); // we should receive LOGIN_ACK very soon. we already set a timer elsewhere to give 10 seconds to receive it }; server.onMessage = function (e) { console.log("server.onMessage", e) var message = JSON.parse(e.data), messageType = message.type.toLowerCase(), payload = message[messageType], callbacks = server.dispatchTable[message.type]; console.log("server.onMessage", message, messageType, payload, callbacks) if (message.type == context.JK.MessageType.PEER_MESSAGE) { console.log("server.onMessage:" + messageType); } else if (message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) { console.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload)); } 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; } } } else { logger.info("Unexpected message type %s.", message.type); } }; // onClose is called if either client or server closes connection server.onClose = function (e) { logger.info("Socket to server closed.", e.code, e.reason); 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; } if (connectDeferred.state() === "pending") { connectDeferred.reject(); } closedCleanup(true); }; server.onError = function (e) { logger.error("Socket error.", e); }; server.send = function (message) { var jsMessage = JSON.stringify(message); if( isLatencyTester() && (message.type == context.JK.MessageType.HEARTBEAT || message.type == context.JK.MessageType.PEER_MESSAGE)) { logger.info("latency-tester: server.send(" + jsMessage + ")") } else if (message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) { logger.info("server.send(" + jsMessage + ")"); } if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) { try { server.socket.send(jsMessage); } catch(err) { logger.warn("error when sending on websocket: " + err) } } else { logger.warn("Dropped message because server connection is closed."); } }; server.loginSession = function (sessionId) { var loginMessage; if (!server.signedIn) { logger.warn("Not signed in!"); // TODO: surface the error return; } loginMessage = msg_factory.login_jam_session(sessionId); server.send(loginMessage); }; /** with the advent of the reliable UDP channel, this is no longer how messages are sent from client-to-clent * 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 */ 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'); }; server.sendLogin = function (token) { logger.debug("sending login message on behalf of client") var outgoing_msg = msg_factory.login_with_token(token, null, null); server.send(outgoing_msg); }; server.sendLogout = function () { logger.debug("sending logout message on behalf of client") var outgoing_msg = msg_factory.logout(); server.send(outgoing_msg); }; server.sendChatMessage = function(channel, message) { if (server.connected) { var chatMsg = msg_factory.chatMessage(channel, message) server.send(chatMsg) return true; } else { return false; } } server.updateNotificationSeen = function (notificationId, notificationCreatedAt) { var time = new Date(notificationCreatedAt); if (!notificationCreatedAt) { throw 'invalid value passed to updateNotificationSeen' } if (!notificationLastSeenAt) { notificationLastSeenAt = notificationCreatedAt; notificationLastSeen = notificationId; logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt); } else if (time.getTime() > new Date(notificationLastSeenAt).getTime()) { notificationLastSeenAt = notificationCreatedAt; notificationLastSeen = notificationId; logger.debug("updated notificationLastSeenAt with: " + notificationCreatedAt); } else { logger.debug("ignored notificationLastSeenAt for: " + notificationCreatedAt); } } server.registerMessageCallback(context.JK.MessageType.PEER_MESSAGE, function (header, payload) { if (context.jamClient !== undefined) { context.jamClient.P2PMessageReceived(header.from, payload.message); } }); server.get$Server = function() { return $self; } context.JK.JamServer = server; function _heartbeat() { if(isLatencyTester()) { logger.info("latency-tester debug: heartbeat" + app.heartbeatActive) } if (app.heartbeatActive) { //console.log("heartbeat active?: " + active) var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt, active); notificationLastSeenAt = undefined; notificationLastSeen = undefined; // 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(); if(lastHeartbeatSentTime) { var drift = new Date().getTime() - lastHeartbeatSentTime.getTime() - heartbeatMS; if (drift > 500) { logger.warn("significant drift between heartbeats: " + drift + 'ms beyond target interval') } } lastHeartbeatSentTime = now; //context.JK.JamServer.send(message); server.send(message) lastHeartbeatFound = false; } } // Callbacks from jamClient if (context.jamClient !== undefined && context.jamClient.IsNativeClient()) { context.jamClient.SendP2PMessage.connect(server.sendP2PMessage); if (context.jamClient.SendLogin) { context.jamClient.SendLogin.connect(server.sendLogin); context.jamClient.SendLogin.connect(server.sendLogout); } } function initialize() { registerLoginAck(); registerHeartbeatAck(); registerServerRejection(); registerSocketClosed(); activityCheck(); context.JK.JamServer.send("hello") // $inSituBanner = $('.server-connection'); // $inSituBannerHolder = $('.no-websocket-connection'); // $messageContents = $inSituBannerHolder.find('.message-contents'); // $dialog = $('#banner'); // $templateServerConnection = $('#template-server-connection'); // $templateNoLogin = $('#template-no-login'); // $templateDisconnected = $('#template-disconnected'); // 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; // } } this.initialize = initialize; return this; } })(window, jQuery);