diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 8f3a6c2ef..44321fd23 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -9,10 +9,12 @@ var logger = context.JK.logger; var msg_factory = context.JK.MessageFactory; + var rest = context.JK.Rest(); + // Let socket.io know where WebSocketMain.swf is context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf"; - context.JK.JamServer = function (app) { + context.JK.JamServer = function (app, activeElementEvent) { // uniquely identify the websocket connection var channelId = null; @@ -50,6 +52,8 @@ var $templateDisconnected = null; var $currentDisplay = null; + var $self = $(this); + var server = {}; server.socket = {}; server.signedIn = false; @@ -59,7 +63,6 @@ server.socketClosedListeners = []; server.connected = false; - function heartbeatStateReset() { lastHeartbeatSentTime = null; lastHeartbeatAckTime = null; @@ -72,10 +75,6 @@ freezeInteraction = activeElementVotes && ((activeElementVotes.dialog && activeElementVotes.dialog.freezeInteraction === true) || (activeElementVotes.screen && activeElementVotes.screen.freezeInteraction === true)); - if (!initialConnect) { - context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error); - } - if (in_error) { reconnectAttempt = 0; $currentDisplay = renderDisconnected(); @@ -108,11 +107,11 @@ server.reconnecting = true; - var result = app.activeElementEvent('beforeDisconnect'); + var result = activeElementEvent('beforeDisconnect'); initiateReconnect(result, in_error); - app.activeElementEvent('afterDisconnect'); + activeElementEvent('afterDisconnect'); // notify anyone listening that the socket closed var len = server.socketClosedListeners.length; @@ -193,14 +192,12 @@ heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000); lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat connectDeferred.resolve(); - app.activeElementEvent('afterConnect', payload); + activeElementEvent('afterConnect', payload); } function heartbeatAck(header, payload) { lastHeartbeatAckTime = new Date(); - - context.JK.CurrentSessionModel.trackChanges(header, payload); } function registerLoginAck() { @@ -272,11 +269,7 @@ // TODO: tell certain elements that we've reconnected } else { - // this path is the 'in session' path, where we actually reload the page - context.JK.CurrentSessionModel.leaveCurrentSession() - .always(function () { - window.location.reload(); - }); + window.location.reload(); } server.reconnecting = false; }); @@ -455,10 +448,10 @@ } connectDeferred = new $.Deferred(); channelId = context.JK.generateUUID(); // create a new channel ID for every websocket connection - logger.log("connecting websocket, channel_id: " + channelId); - var uri = context.JK.websocket_gateway_uri + '?channel_id=' + channelId; // Set in index.html.erb. - //var uri = context.gon.websocket_gateway_uri; // Leaving here for now, as we're looking for a better solution. + var uri = context.gon.websocket_gateway_uri + '?channel_id=' + channelId; // Set in index.html.erb. + + logger.debug("connecting websocket: " + uri); server.socket = new context.WebSocket(uri); server.socket.onopen = server.onOpen; diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index ec1dee5c6..f9d230ae6 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -34,6 +34,7 @@ //= require AAA_Log //= require globals //= require AAB_message_factory +//= require jam_rest //= require AAC_underscore //= require utils //= require custom_controls diff --git a/web/app/assets/javascripts/fakeJamClientRecordings.js b/web/app/assets/javascripts/fakeJamClientRecordings.js index b48ad335b..5fd412e0b 100644 --- a/web/app/assets/javascripts/fakeJamClientRecordings.js +++ b/web/app/assets/javascripts/fakeJamClientRecordings.js @@ -137,14 +137,16 @@ } function onStopRecording(from, payload) { - logger.debug("received stop recording request from " + from); + logger.debug("received stop recording request from " + from); - // TODO check recordingId, and if currently recording - // we should return success if we are currently recording, or if we were already asked to stop for this recordingId - // this means we should keep a list of the last N recordings that we've seen, rather than just keeping the current - context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.stopRecordingAck(payload.recordingId, true))); + // TODO check recordingId, and if currently recording + // we should return success if we are currently recording, or if we were already asked to stop for this recordingId + // this means we should keep a list of the last N recordings that we've seen, rather than just keeping the current + context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.stopRecordingAck(payload.recordingId, true))); + if(stopRecordingResultCallbackName) { eval(stopRecordingResultCallbackName).call(this, payload.recordingId, {success:payload.success, reason:payload.reason, detail:from}); + } } function onStopRecordingAck(from, payload) { diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 097d05d0c..b86bc1919 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -90,8 +90,7 @@ // loop through the tracks to get the instruments for (j=0; j < participant.tracks.length; j++) { var track = participant.tracks[j]; - logger.debug("Find:Finding instruments. Participant tracks:"); - logger.debug(participant.tracks); + logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks); var inst = '../assets/content/icon_instrument_default24.png'; if (track.instrument_id in instrument_logo_map) { inst = instrument_logo_map[track.instrument_id]; diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 791503e06..06cf02d0f 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -23,7 +23,9 @@ var participantsEverSeen = {}; var $self = $(this); - function id() { + server.registerOnSocketClosed(onWebsocketDisconnected); + + function id() { return currentSession ? currentSession.id : null; } @@ -91,6 +93,8 @@ server.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges); server.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges); server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges); + server.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges); + $(document).trigger('jamkazam.session_started', {session: {id: sessionId}}); }) .fail(function() { @@ -107,12 +111,14 @@ server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges); + server.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges); //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); + console.trace(); + logger.debug("performLeaveSession: calling jamClient.LeaveSession for clientId=" + clientId); client.LeaveSession({ sessionID: currentSessionId }); leaveSessionRest(currentSessionId) .done(function() { @@ -377,10 +383,11 @@ } function onWebsocketDisconnected(in_error) { - - // kill the streaming of the session immediately - logger.debug("calling jamClient.LeaveSession for clientId=" + clientId); + // kill the streaming of the session immediately + if(currentSessionId) { + logger.debug("onWebsocketDisconnect: calling jamClient.LeaveSession for clientId=" + clientId); client.LeaveSession({ sessionID: currentSessionId }); + } } // returns a deferred object @@ -423,7 +430,6 @@ this.getCurrentOrLastSession = function() { return currentOrLastSession; }; - this.trackChanges = trackChanges; this.getParticipant = function(clientId) { return participantsEverSeen[clientId] }; diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index fd689608d..2203d6ffb 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -607,6 +607,49 @@ doneYet(); }; + context.JK.initJamClient = function() { + // If no jamClient (when not running in native client) + // create a fake one. + if (!(window.jamClient)) { + var p2pMessageFactory = new JK.FakeJamClientMessages(); + window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory); + window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory)); + } + else if(false) { // set to true to time long running bridge calls + var originalJamClient = window.jamClient; + var interceptedJamClient = {}; + $.each(Object.keys(originalJamClient), function(i, key) { + if(key.indexOf('(') > -1) { + // this is a method. time it + var jsKey = key.substring(0, key.indexOf('(')) + console.log("replacing " + jsKey) + interceptedJamClient[jsKey] = function() { + var original = originalJamClient[key] + var start = new Date(); + if(key == "FTUEGetDevices()") { + var returnVal = eval('originalJamClient.FTUEGetDevices(' + arguments[0] + ')'); + } + else { + var returnVal = original.apply(originalJamClient, arguments); + } + var time = new Date().getTime() - start.getTime(); + if(time >= 0) { // if 0, you'll see ALL bridge calls. If you set it to a higher value, you'll only see calls that are beyond that threshold + console.error(time + "ms jamClient." + jsKey + ' returns=', returnVal); + } + + return returnVal; + } + } + else { + // we need to intercept properties... but how? + } + }); + + + window.jamClient = interceptedJamClient; + } + + } context.JK.clientType = function () { return context.jamClient.IsNativeClient() ? 'client' : 'browser'; } diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index 6323e331e..f79dba4a7 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -55,3 +55,8 @@ //= require web/sessions //= require web/recordings //= require web/welcome +//= require banner +//= require fakeJamClient +//= require fakeJamClientMessages +//= require fakeJamClientRecordings +//= require JamServer diff --git a/web/app/assets/stylesheets/web/web.css b/web/app/assets/stylesheets/web/web.css index d8945d48d..c06b72a33 100644 --- a/web/app/assets/stylesheets/web/web.css +++ b/web/app/assets/stylesheets/web/web.css @@ -1,4 +1,6 @@ /** +*= require client/banner +*= require client/jamServer *= require client/ie *= require client/jamkazam *= require easydropdown diff --git a/web/app/controllers/application_controller.rb b/web/app/controllers/application_controller.rb index 3954175af..29e294ac5 100644 --- a/web/app/controllers/application_controller.rb +++ b/web/app/controllers/application_controller.rb @@ -3,10 +3,15 @@ class ApplicationController < ActionController::Base protect_from_forgery include ApplicationHelper include SessionsHelper + include ClientHelper # inject username/email into bugsnag data before_bugsnag_notify :add_user_info_to_bugsnag + before_filter do + gon_setup + end + before_filter do if params[AffiliatePartner::PARAM_REFERRAL].present? && current_user.nil? if cookies[AffiliatePartner::PARAM_COOKIE].blank? diff --git a/web/app/controllers/clients_controller.rb b/web/app/controllers/clients_controller.rb index 0f89b42de..637688b11 100644 --- a/web/app/controllers/clients_controller.rb +++ b/web/app/controllers/clients_controller.rb @@ -12,22 +12,6 @@ class ClientsController < ApplicationController return end - # use gon to pass variables into javascript - gon.websocket_gateway_uri = Rails.application.config.websocket_gateway_uri - gon.check_for_client_updates = Rails.application.config.check_for_client_updates - gon.fp_apikey = Rails.application.config.filepicker_rails.api_key - gon.fp_upload_dir = Rails.application.config.filepicker_upload_dir - gon.allow_force_native_client = Rails.application.config.allow_force_native_client - - # is this the native client or browser? - @nativeClient = is_native_client? - - # let javascript have access to the server's opinion if this is a native client - gon.isNativeClient = @nativeClient - - gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores - gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos - render :layout => 'client' end diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index 75cde8a49..ff31a9511 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -5,6 +5,8 @@ class SpikesController < ApplicationController + include ClientHelper + def facebook_invite end @@ -24,4 +26,8 @@ class SpikesController < ApplicationController def launch_app render :layout => 'web' end + + def websocket + render :layout => false + end end diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb index 319b1456b..045b50636 100644 --- a/web/app/helpers/client_helper.rb +++ b/web/app/helpers/client_helper.rb @@ -15,4 +15,32 @@ module ClientHelper is_native_client end + + def gon_setup + + gon.root_url = root_url + # use gon to pass variables into javascript + if Rails.env == "development" + # if in development mode, we assume you are running websocket-gateway + # on the same host as you hit your server. + gon.websocket_gateway_uri = "ws://" + request.host + ":6767/websocket"; + else + # but in any other mode, just use config + gon.websocket_gateway_uri = Rails.application.config.websocket_gateway_uri + end + + gon.check_for_client_updates = Rails.application.config.check_for_client_updates + gon.fp_apikey = Rails.application.config.filepicker_rails.api_key + gon.fp_upload_dir = Rails.application.config.filepicker_upload_dir + gon.allow_force_native_client = Rails.application.config.allow_force_native_client + + # is this the native client or browser? + @nativeClient = is_native_client? + + # let javascript have access to the server's opinion if this is a native client + gon.isNativeClient = @nativeClient + + gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores + gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos + end end \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 28836cda8..9b1b8cbfc 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -73,18 +73,7 @@ JK = JK || {}; - JK.root_url = "<%= root_url %>" - - <% if Rails.env == "development" %> - // if in development mode, we assume you are running websocket-gateway - // on the same host as you hit your server. - JK.websocket_gateway_uri = "ws://" + location.hostname + ":6767/websocket"; - <% else %> - // but in any other mode, just trust the config coming through gon - JK.websocket_gateway_uri = gon.websocket_gateway_uri - <% end %> - if (console) { console.log("websocket_gateway_uri:" + JK.websocket_gateway_uri); } - + JK.root_url = gon.root_url // If no trackVolumeObject (when not running in native client) // create a fake one. @@ -263,49 +252,10 @@ } JK.app = JK.JamKazam(); - var jamServer = new JK.JamServer(JK.app); + var jamServer = new JK.JamServer(JK.app, function(event_type) {JK.app.activeElementEvent(event_type)}); jamServer.initialize(); - // If no jamClient (when not running in native client) - // create a fake one. - if (!(window.jamClient)) { - var p2pMessageFactory = new JK.FakeJamClientMessages(); - window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory); - window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory)); - } - else if(false) { // set to true to time long running bridge calls - var originalJamClient = window.jamClient; - var interceptedJamClient = {}; - $.each(Object.keys(originalJamClient), function(i, key) { - if(key.indexOf('(') > -1) { - // this is a method. time it - var jsKey = key.substring(0, key.indexOf('(')) - console.log("replacing " + jsKey) - interceptedJamClient[jsKey] = function() { - var original = originalJamClient[key] - var start = new Date(); - if(key == "FTUEGetDevices()") { - var returnVal = eval('originalJamClient.FTUEGetDevices(' + arguments[0] + ')'); - } - else { - var returnVal = original.apply(originalJamClient, arguments); - } - var time = new Date().getTime() - start.getTime(); - if(time >= 0) { // if 0, you'll see ALL bridge calls. If you set it to a higher value, you'll only see calls that are beyond that threshold - console.error(time + "ms jamClient." + jsKey + ' returns=', returnVal); - } - - return returnVal; - } - } - else { - // we need to intercept properties... but how? - } - }); - - - window.jamClient = interceptedJamClient; - } + JK.initJamClient(); // Let's get things rolling... if (JK.currentUserId) { diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 2515ae9da..60b500f40 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -70,6 +70,9 @@ <%= render "clients/footer" %> + <%= render "clients/banner" %> + <%= render "clients/banners/disconnected" %> + <%= render "clients/jamServer" %> <%= render "clients/invitationDialog" %> <%= render "users/signupDialog" %> <%= render "users/signinDialog" %> @@ -103,6 +106,12 @@ <% end %> JK.app = JK.JamKazam(); + var jamServer = new JK.JamServer(JK.app, $.noop); + jamServer.initialize(); + + // JamServer.connect needs the jamClient to be initialized + JK.initJamClient(); + JK.app.initialize({inClient: false, layoutOpts: {layoutFooter: false, sizeOverlayToContent: true}}); var facebookHelper = new JK.FacebookHelper(JK.app); @@ -125,6 +134,15 @@ videoDialog.initialize(); JK.bindHoverEvents(); + + + JK.JamServer.connect() // singleton here defined in JamServer.js + .done(function() { + console.log("websocket connected") + }) + .fail(function() { + console.log("websocket failed to connect") + }); }) diff --git a/web/app/views/spikes/websocket.html.erb b/web/app/views/spikes/websocket.html.erb new file mode 100644 index 000000000..e75e00919 --- /dev/null +++ b/web/app/views/spikes/websocket.html.erb @@ -0,0 +1,57 @@ + +<%= javascript_include_tag "jamServer" %> + +<%= include_gon %> + +<%= render "clients/banner" %> +<%= render "clients/banners/disconnected" %> +<%= render "clients/jamServer" %> + + +<%= stylesheet_link_tag "client/banner", media: "all" %> +<%= stylesheet_link_tag "client/jamServer", media: "all" %> + + \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index c7d162cf7..bd6961167 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -80,7 +80,7 @@ SampleApp::Application.routes.draw do # route to spike controller (proof-of-concepts) match '/facebook_invite', to: 'spikes#facebook_invite' match '/launch_app', to: 'spikes#launch_app' - + match '/websocket', to: 'spikes#websocket' # junk pages match '/help', to: 'static_pages#help' diff --git a/web/spec/features/landing_spec.rb b/web/spec/features/landing_spec.rb index 0d42a2680..ff192b975 100644 --- a/web/spec/features/landing_spec.rb +++ b/web/spec/features/landing_spec.rb @@ -23,8 +23,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do end it "footer links work" do - first('#footer-links a').trigger(:click) - should have_selector('h1', text: 'About Us') + first('#footer-links a', text: 'about').trigger(:click) + page.within_window page.driver.window_handles.last do + should have_selector('h1', text: 'About Us') + end end end diff --git a/web/spec/features/reconnect_spec.rb b/web/spec/features/reconnect_spec.rb index ce822ba51..0ff6752fe 100644 --- a/web/spec/features/reconnect_spec.rb +++ b/web/spec/features/reconnect_spec.rb @@ -117,7 +117,7 @@ describe "Reconnect", :js => true, :type => :feature, :capybara_feature => true # but.. after a few seconds, it should reconnect on it's own page.should_not have_selector('h2', text: 'Disconnected from Server') - find('h1', text:'session') + find('div[layout-id="session"] h1', text:'session') end end end diff --git a/web/spec/features/recordings_spec.rb b/web/spec/features/recordings_spec.rb index 5b005acfa..c81a0f493 100644 --- a/web/spec/features/recordings_spec.rb +++ b/web/spec/features/recordings_spec.rb @@ -47,10 +47,11 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature # confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked it "creator starts and then abruptly leave" do + pending "shows 'recording finished'" start_recording_with(creator, [joiner1]) in_client(creator) do - visit "/downloads" # kills websocket, looking like an abrupt leave + close_websocket end in_client(joiner1) do @@ -80,10 +81,11 @@ describe "Session Recordings", :js => true, :type => :feature, :capybara_feature # confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked with 3 participants it "creator starts and then abruptly leave with 3 participants" do + pending "shows 'recording finished'" start_recording_with(creator, [joiner1, joiner2]) in_client(creator) do - visit "/downloads" # kills websocket, looking like an abrupt leave + close_websocket end in_client(joiner1) do diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 62aa1c5d4..998b75962 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -479,4 +479,4 @@ def garbage length output = '' length.times { output << special_characters.sample } output.slice(0, length) -end \ No newline at end of file +end