(function (context, $) { "use strict"; // Change underscore's default templating characters as they // conflict withe the ERB rendering. Templates will use: // {{ interpolate }} // {% evaluate %} // {{- escape }} // context._.templateSettings = { evaluate: /\{%([\s\S]+?)%\}/g, interpolate: /\{\{([\s\S]+?)\}\}/g, escape: /\{\{-([\s\S]+?)\}\}/g }; context.JK = context.JK || {}; var JamKazam = context.JK.JamKazam = function () { var app; var logger = context.JK.logger; var heartbeatInterval = null; var heartbeatMS = null; var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset var inBadState = false; var lastHeartbeatAckTime = null; var lastHeartbeatFound = false; var heartbeatAckCheckInterval = null; var opts = { inClient: true, // specify false if you want the app object but none of the client-oriented features layoutOpts: { layoutFooter: true // specify false if you want footer to be left alone } }; /** * Dynamically build routes from markup. Any layout="screen" will get a route corresponding to * his layout-id attribute. If a layout-arg attribute is present, that will be named as a data * section of the route. */ function routing() { var routes = context.RouteMap, rules = {}, rule, routingContext = {}; $('div[layout="screen"]').each(function () { var target = $(this).attr('layout-id'), targetUrl = target, targetArg = $(this).attr('layout-arg'), fn = function (data) { data.screen = target; app.layout.changeToScreen(target, data); }; if (targetArg) { targetUrl += "/:" + targetArg; } rules[target] = {route: '/' + targetUrl + '/:d?', method: target}; routingContext[target] = fn; }); routes.context(routingContext); for (rule in rules) if (rules.hasOwnProperty(rule)) routes.add(rules[rule]); $(context).bind('hashchange', function(e) { app.layout.onHashChange(e, routes.handler)}); $(routes.handler); } 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() > heartbeatMissedMS) { logger.error("no heartbeat ack received from server after ", heartbeatMissedMS, " seconds . giving up on socket connection"); context.JK.JamServer.close(true); } else { lastHeartbeatFound = true; } } function _heartbeat() { if (app.heartbeatActive) { var message = context.JK.MessageFactory.heartbeat(); context.JK.JamServer.send(message); lastHeartbeatFound = false; } } function loggedIn(header, payload) { app.clientId = payload.client_id; // tell the backend that we have logged in context.jamClient.OnLoggedIn(payload.user_id, payload.token); $.cookie('client_id', payload.client_id); app.initAfterConnect(); heartbeatMS = payload.heartbeat_interval * 1000; logger.debug("jamkazam.js.loggedIn(): clientId now " + app.clientId + "; Setting up heartbeat every " + heartbeatMS + " MS"); 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 } function heartbeatAck(header, payload) { lastHeartbeatAckTime = new Date(); } /** * This occurs when the websocket gateway loses a connection to the backend messaging system, * resulting in severe loss of functionality */ function serverBadStateError() { if (!inBadState) { inBadState = true; app.notify({title: "Server Unstable", text: "The server is currently unstable, resulting in feature loss. If you are experiencing any problems, please try to use JamKazam later."}) } } /** * This occurs when the websocket gateway loses a connection to the backend messaging system, * resulting in severe loss of functionality */ function serverBadStateRecovered() { if (inBadState) { inBadState = false; app.notify({title: "Server Recovered", text: "The server is now stable again. If you are still experiencing problems, either create a new music session or restart the client altogether."}) } } /** * This occurs when a new download from a recording has become available */ function downloadAvailable() { context.jamClient.OnDownloadAvailable(); } /** * 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(); // stop future heartbeats if (heartbeatInterval != null) { clearInterval(heartbeatInterval); heartbeatInterval = null; } // stop checking for heartbeat acks if (heartbeatAckCheckInterval != null) { clearTimeout(heartbeatAckCheckInterval); heartbeatAckCheckInterval = null; } } 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 registerBadStateError() { logger.debug("register for server_bad_state_error"); context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError); } function registerBadStateRecovered() { logger.debug("register for server_bad_state_recovered"); context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_RECOVERED, serverBadStateRecovered); } function registerDownloadAvailable() { logger.debug("register for download_available"); context.JK.JamServer.registerMessageCallback(context.JK.MessageType.DOWNLOAD_AVAILABLE, downloadAvailable); } function registerSocketClosed() { logger.debug("register for socket closed"); context.JK.JamServer.registerOnSocketClosed(socketClosed); } /** * Generic error handler for Ajax calls. */ function ajaxError(jqXHR, textStatus, errorMessage) { if (jqXHR.status == 404) { logger.error("Unexpected ajax error: " + textStatus + ", msg:" + errorMessage); app.notify({title: "Oops!", text: "What you were looking for is gone now."}); } else if (jqXHR.status = 422) { logger.error("Unexpected ajax error: " + textStatus + ", msg: " + errorMessage + ", response: " + jqXHR.responseText); // present a nicer message try { var text = "