From dad7ac669134d581e189dba13e3d4938c11bd71e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 31 Aug 2013 13:54:11 +0000 Subject: [PATCH] * VRFS-594; add dialog to initiate reconnect if you lose webocket connection --- app/assets/javascripts/JamServer.js | 3 +- app/assets/javascripts/banner.js | 56 ++++++++ app/assets/javascripts/ftue.js | 22 +++ app/assets/javascripts/jam_rest.js | 10 ++ app/assets/javascripts/session.js | 31 ++-- app/assets/javascripts/sessionModel.js | 135 +++++++++++++----- app/assets/stylesheets/client/banner.css.scss | 8 ++ app/assets/stylesheets/client/client.css | 1 + app/assets/stylesheets/client/ftue.css.scss | 19 ++- app/views/clients/_banner.html.erb | 18 +++ app/views/clients/_ftue.html.erb | 30 ++-- .../clients/banners/_disconnected.html.erb | 42 ++++++ app/views/clients/index.html.erb | 7 + app/views/layouts/landing.erb | 2 + 14 files changed, 327 insertions(+), 57 deletions(-) create mode 100644 app/assets/javascripts/banner.js create mode 100644 app/assets/stylesheets/client/banner.css.scss create mode 100644 app/views/clients/_banner.html.erb create mode 100644 app/views/clients/banners/_disconnected.html.erb diff --git a/app/assets/javascripts/JamServer.js b/app/assets/javascripts/JamServer.js index d635bf7fd..da30e3637 100644 --- a/app/assets/javascripts/JamServer.js +++ b/app/assets/javascripts/JamServer.js @@ -95,7 +95,8 @@ context.jamClient.connected = false; } - // TODO: reconnect + context.JK.CurrentSessionModel.onWebsocketDisconnected(); + }; server.send = function(message) { diff --git a/app/assets/javascripts/banner.js b/app/assets/javascripts/banner.js new file mode 100644 index 000000000..d0aa78aa7 --- /dev/null +++ b/app/assets/javascripts/banner.js @@ -0,0 +1,56 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.Banner = (function() { + var self = this; + var logger = context.JK.logger; + + // responsible for updating the contents of the update dialog + // as well as registering for any event handlers + function show(options) { + var text = options.text; + var html = options.html; + + var newContent = null; + if (html) { + newContent = $('#banner .dialog-inner').html(html); + } + else if(text) { + newContent = $('#banner .dialog-inner').html(text); + } + else { + console.error("unable to show banner for empty message") + return newContent; + } + + $('#banner').show() + $('#banner_overlay').show() + + // return the core of the banner so that caller can attach event handlers to newly created HTML + return newContent; + } + + function hide() { + $('#banner').hide(); + $('#banner_overlay .dialog-inner').html(""); + $('#banner_overlay').hide(); + } + + function initialize() { + + return self; + } + + // Expose publics + var me = { + initialize: initialize, + show : show, + hide : hide + } + + return me; + })(); + +})(window,jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/ftue.js b/app/assets/javascripts/ftue.js index da6730878..9653e0126 100644 --- a/app/assets/javascripts/ftue.js +++ b/app/assets/javascripts/ftue.js @@ -270,7 +270,29 @@ ftueSave(false); } + function videoLinkClicked(evt) { + var myOS = jamClient.GetOSAsString(); + var link; + if (myOS === 'MacOSX') { + link = $(evt.currentTarget).attr('external-link-mac'); + } else if (myOS === 'Win32') { + link = $(evt.currentTarget).attr('external-link-win'); + } + if (link) { + context.jamClient.OpenSystemBrowser(link); + } + } + function events() { + $('.ftue-video-link').hover( + function(evt) { // handlerIn + $(this).addClass('hover'); + }, + function(evt) { // handlerOut + $(this).removeClass('hover'); + } + ); + $('.ftue-video-link').on('click', videoLinkClicked); $('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged); $('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged); $('#btn-asio-control-panel').on('click', openASIOControlPanel); diff --git a/app/assets/javascripts/jam_rest.js b/app/assets/javascripts/jam_rest.js index 8ba454931..3236bd28d 100644 --- a/app/assets/javascripts/jam_rest.js +++ b/app/assets/javascripts/jam_rest.js @@ -144,6 +144,15 @@ }); } + /** check if the server is alive */ + function serverHealthCheck(options) { + return $.ajax({ + url: window.host, + cache: false, + dataType: "html" + }); + } + function getId(options) { var id = options && options["id"] @@ -201,6 +210,7 @@ this.getClientDownloads = getClientDownloads this.createInvitation = createInvitation; this.postFeedback = postFeedback; + this.serverHealthCheck = serverHealthCheck; return this; }; diff --git a/app/assets/javascripts/session.js b/app/assets/javascripts/session.js index b96ea511c..2cd3dcf70 100644 --- a/app/assets/javascripts/session.js +++ b/app/assets/javascripts/session.js @@ -121,22 +121,37 @@ } function afterCurrentUserLoaded() { - sessionModel = new context.JK.SessionModel( + // It seems the SessionModel should be a singleton. + // a client can only be in one session at a time, + // and other parts of the code want to know at any certain times + // about the current session, if any (for example, reconnect logic) + context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel( context.JK.JamServer, - context.jamClient, - context.JK.userMe + context.jamClient ); + sessionModel.subscribe('sessionScreen', sessionChanged); - sessionModel.joinSession(sessionId); + sessionModel.joinSession(sessionId) + .fail(function(xhr, textStatus, errorMessage) { + if(xhr.status == 404) { + // we tried to join the session, but it's already gone. kick user back to join session screen + window.location = "#/findSession" + app.notify( + { title: "Unable to Join Session", + text: "The session you attempted to join is over." + }, + { no_cancel: true }); + }else { + app.ajaxError(xhr, textStatus, errorMessage); + } + }) } function beforeHide(data) { // track that the screen is inactive, to disable body-level handlers screenActive = false; - sessionModel.leaveCurrentSession(sessionId); - // 'unregister' for callbacks - context.jamClient.SessionRegisterCallback(""); - context.jamClient.SessionSetAlertCallback(""); + sessionModel.leaveCurrentSession() + .fail(app.ajaxError) } function sessionChanged() { diff --git a/app/assets/javascripts/sessionModel.js b/app/assets/javascripts/sessionModel.js index 6c400ea95..859e645b4 100644 --- a/app/assets/javascripts/sessionModel.js +++ b/app/assets/javascripts/sessionModel.js @@ -7,12 +7,13 @@ context.JK = context.JK || {}; var logger = context.JK.logger; - context.JK.SessionModel = function(server, client, currentUser) { + context.JK.SessionModel = function(server, client) { var clientId = client.clientID; var currentSessionId = null; // Set on join, prior to setting currentSession. var currentSession = null; var subscribers = {}; var users = {}; // User info for session participants + var rest = context.JK.Rest(); function id() { return currentSession.id; @@ -32,24 +33,54 @@ function joinSession(sessionId) { currentSessionId = sessionId; logger.debug("SessionModel.joinSession(" + sessionId + ")"); - joinSessionRest(sessionId, function() { - refreshCurrentSession(); - }); - server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); - server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + var deferred = joinSessionRest(sessionId); + + deferred + .done(function(){ + logger.debug("calling jamClient.JoinSession"); + client.JoinSession({ sessionID: sessionId }); + refreshCurrentSession(); + server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); + server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + }); + + return deferred; } /** - * Leave the current session + * Leave the current session, if there is one. + * callback: called in all conditions; either after an attempt is made to tell the server that we are leaving, + * or immediately if there is no session */ function leaveCurrentSession() { - logger.debug("SessionModel.leaveCurrentSession()"); - // TODO - sessionChanged will be called with currentSession = null - server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); - server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); - leaveSessionRest(currentSessionId, sessionChanged); - currentSession = null; - currentSessionId = null; + var deferred; + + if(currentSessionId) { + logger.debug("SessionModel.leaveCurrentSession()"); + // TODO - sessionChanged will be called with currentSession = null + server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession); + server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); + // leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long + // time, for that entire duration you'll still be sending voice data to the other users. + // this may be bad if someone decides to badmouth others in the left-session during this time + logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); + client.LeaveSession({ sessionID: currentSessionId }); + deferred = leaveSessionRest(currentSessionId); + deferred.done(function() { + sessionChanged(); + }); + + // 'unregister' for callbacks + context.jamClient.SessionRegisterCallback(""); + context.jamClient.SessionSetAlertCallback(""); + currentSession = null; + currentSessionId = null; + } + else { + deferred = rest.serverHealthCheck(); + } + + return deferred; } /** @@ -268,7 +299,7 @@ * Make the server calls to join the current user to * the session provided. */ - function joinSessionRest(sessionId, callback) { + function joinSessionRest(sessionId) { var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); var data = { client_id: clientId, @@ -277,38 +308,77 @@ tracks: tracks }; var url = "/api/sessions/" + sessionId + "/participants"; - $.ajax({ + return $.ajax({ type: "POST", dataType: "json", contentType: 'application/json', url: url, async: false, data: JSON.stringify(data), - processData:false, - success: function(response) { - logger.debug("calling jamClient.JoinSession"); - client.JoinSession({ sessionID: sessionId }); - callback(); - }, - error: ajaxError + processData:false }); } - function leaveSessionRest(sessionId, callback) { + function leaveSessionRest(sessionId) { var url = "/api/participants/" + clientId; - $.ajax({ + return $.ajax({ type: "DELETE", url: url, - async: false, - success: function (response) { - logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); - client.LeaveSession({ sessionID: sessionId }); - callback(); - }, - error: ajaxError + async: false }); } + function reconnect() { + window.location.reload(); + } + function registerReconnect(content) { + $('a.disconnected-reconnect', content).click(function() { + + var template = $('#template-reconnecting').html(); + var templateHtml = context.JK.fillTemplate(template, null); + var content = context.JK.Banner.show({ + html : template + }); + + context.JK.CurrentSessionModel.leaveCurrentSession() + .done(function() { + reconnect(); + }) + .fail(function(xhr, textStatus, errorThrown) { + console.log("leaveCurrentSession failed: ", arguments); + + if(xhr && xhr.status >= 100) { + // we could connect to the server, and it's alive + reconnect(); + } + else { + var template = $('#template-could-not-reconnect').html(); + var templateHtml = context.JK.fillTemplate(template, null); + var content = context.JK.Banner.show({ + html : template + }); + + registerReconnect(content); + } + }); + + }); + } + + function onWebsocketDisconnected() { + var template = $('#template-disconnected').html(); + var templateHtml = context.JK.fillTemplate(template, null); + var content = context.JK.Banner.show({ + html : template + }) ; + + // kill the streaming of the session immediately + logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); + client.LeaveSession({ sessionID: currentSessionId }); + + registerReconnect(content); + } + function ajaxError(jqXHR, textStatus, errorMessage) { logger.error("Unexpected ajax error: " + textStatus); } @@ -324,6 +394,7 @@ this.addTrack = addTrack; this.updateTrack = updateTrack; this.deleteTrack = deleteTrack; + this.onWebsocketDisconnected = onWebsocketDisconnected; this.getCurrentSession = function() { return currentSession; }; diff --git a/app/assets/stylesheets/client/banner.css.scss b/app/assets/stylesheets/client/banner.css.scss new file mode 100644 index 000000000..e3b8de209 --- /dev/null +++ b/app/assets/stylesheets/client/banner.css.scss @@ -0,0 +1,8 @@ +#banner { + display:none; +} + +#banner h2 { + font-weight:bold; + font-size:x-large; +} \ No newline at end of file diff --git a/app/assets/stylesheets/client/client.css b/app/assets/stylesheets/client/client.css index 08784199b..7fb658934 100644 --- a/app/assets/stylesheets/client/client.css +++ b/app/assets/stylesheets/client/client.css @@ -30,6 +30,7 @@ *= require ./genreSelector *= require ./sessionList *= require ./searchResults + *= require ./banner *= require ./clientUpdate *= require jquery.Jcrop */ \ No newline at end of file diff --git a/app/assets/stylesheets/client/ftue.css.scss b/app/assets/stylesheets/client/ftue.css.scss index 7dca409a3..a1cb9228d 100644 --- a/app/assets/stylesheets/client/ftue.css.scss +++ b/app/assets/stylesheets/client/ftue.css.scss @@ -18,12 +18,16 @@ div.dialog.ftue .ftue-inner div[layout-wizard-step="1"] { li { text-align:center; - width: 33%; + width: 31%; + height: 170px; margin:0px; - padding:0px; + /*padding:0px;*/ list-style: none; float:left; } + li.first { + width: 34%; + } } div.dialog.ftue .ftue-inner div[layout-wizard-step="3"] { h5 { @@ -421,3 +425,14 @@ table.audiogeartable { font-size:15px; color:#aaa; } + +.ftue-video-link { + padding:4px; + cursor:pointer; + background-color:#333; + border: 1px solid #333; +} +.ftue-video-link.hover { + background-color:#444; + border: 1px solid #555; +} diff --git a/app/views/clients/_banner.html.erb b/app/views/clients/_banner.html.erb new file mode 100644 index 000000000..9a932e3a9 --- /dev/null +++ b/app/views/clients/_banner.html.erb @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/views/clients/_ftue.html.erb b/app/views/clients/_ftue.html.erb index cda116b21..04c288aae 100644 --- a/app/views/clients/_ftue.html.erb +++ b/app/views/clients/_ftue.html.erb @@ -10,35 +10,37 @@

- Please identify which of the three types of audio gear below you are going to use with the JamKazam - service, and click either the Windows or Mac link under the appropriate gear to watch a video on how - to navigate this initial setup and testing process. After watching the video, click the 'NEXT' - button to get started. + Please identify which of the three types of audio gear below you + are going to use with the JamKazam service, and click one to + watch a video on how to navigate this initial setup and testing + process. After watching the video, click the 'NEXT' button to + get started. If you don't have your audio gear handy now, click + Cancel.

- + CANCEL  NEXT
diff --git a/app/views/clients/banners/_disconnected.html.erb b/app/views/clients/banners/_disconnected.html.erb new file mode 100644 index 000000000..4a5b341be --- /dev/null +++ b/app/views/clients/banners/_disconnected.html.erb @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/app/views/clients/index.html.erb b/app/views/clients/index.html.erb index dfedf5c5c..d2f129f30 100644 --- a/app/views/clients/index.html.erb +++ b/app/views/clients/index.html.erb @@ -29,6 +29,8 @@ <%= render "account_audio_profile" %> <%= render "notify" %> <%= render "client_update" %> +<%= render "banner" %> +<%= render "clients/banners/disconnected" %> <%= render "overlay_small" %>