* VRFS-1730 - websocket on web layout

This commit is contained in:
Seth Call 2014-05-19 16:57:08 -05:00
parent a1b8d78e6a
commit 1f86b809ff
20 changed files with 211 additions and 108 deletions

View File

@ -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;

View File

@ -34,6 +34,7 @@
//= require AAA_Log
//= require globals
//= require AAB_message_factory
//= require jam_rest
//= require AAC_underscore
//= require utils
//= require custom_controls

View File

@ -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) {

View File

@ -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];

View File

@ -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]
};

View File

@ -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 () {
if (context.jamClient) {
return context.jamClient.IsNativeClient() ? 'client' : 'browser';

View File

@ -52,3 +52,8 @@
//= require web/sessions
//= require web/recordings
//= require web/welcome
//= require banner
//= require fakeJamClient
//= require fakeJamClientMessages
//= require fakeJamClientRecordings
//= require JamServer

View File

@ -1,4 +1,6 @@
/**
*= require client/banner
*= require client/jamServer
*= require client/ie
*= require client/jamkazam
*= require easydropdown

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -70,6 +70,9 @@
<%= render "clients/footer" %>
</div>
<%= 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")
});
})
</script>

View File

@ -0,0 +1,57 @@
<!-- you need this javascript -->
<%= javascript_include_tag "jamServer" %>
<!-- gon is required -->
<%= include_gon %>
<!-- you need these templates -->
<%= render "clients/banner" %>
<%= render "clients/banners/disconnected" %>
<%= render "clients/jamServer" %>
<!-- you need these stylesheets -->
<%= stylesheet_link_tag "client/banner", media: "all" %>
<%= stylesheet_link_tag "client/jamServer", media: "all" %>
<script type="text/javascript">
// you can probably do nothing with this in your own code
function signalActiveElement(event_type) {
// event can be one of:
// * beforeDisconnect
// * afterDisconnect
// * afterConnect
// the purpose of this method is to signal to the active element in the UI that websocket state changed
// and in beforeDisconnect in particular, you can return a 'vote' that let's you affect how a reconnect is handled
// you can safely return null, though
console.log("websocket event:" + event_type);
if(event_type == 'beforeDisconnect') {
return null; // causes a in-place websocket reconnect (as opposed to a full page refresh when connection re-established)
}
// no other event cares about return
}
$(function() {
JK = JK || {};
JK.app = JK.JamKazam();
var jamServer = new JK.JamServer(JK.app, signalActiveElement);
jamServer.initialize();
// now you can register for messages somewhere in your code safely... (not necessarily inline here--just somewhere)
// i.e., you can call: context.JK.JamServer.registerMessageCallback
// JamServer.connect needs the jamClient to be initialized
JK.initJamClient();
JK.JamServer.connect() // singleton here defined in JamServer.js
.done(function() {
console.log("websocket connected") // this is used in /client to signal hide of curtain, and initializing the bulk of logic. maybe not useful...
})
.fail(function() {
console.log("websocket failed to connect") // this is used in /client
});
})
</script>

View File

@ -79,7 +79,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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -479,4 +479,4 @@ def garbage length
output = ''
length.times { output << special_characters.sample }
output.slice(0, length)
end
end