Merge branch 'develop' into vrfs925

This commit is contained in:
Jonathan Kolyer 2014-04-14 10:59:58 +00:00
commit bf1e315b48
68 changed files with 1908 additions and 717 deletions

View File

@ -48,7 +48,7 @@ gem 'unf', '0.1.3' #optional fog dependency
gem 'country-select'
gem 'aasm', '3.0.16'
gem 'postgres-copy', '0.6.0'
gem 'aws-sdk', '1.29.1'
gem 'aws-sdk' #, '1.29.1'
gem 'bugsnag'
gem 'gon'
gem 'cocoon'

View File

@ -28,7 +28,7 @@ gem 'amqp', '1.0.2'
gem 'will_paginate'
gem 'actionmailer', '3.2.13'
gem 'sendgrid', '1.2.0'
gem 'aws-sdk', '1.29.1'
gem 'aws-sdk' #, '1.29.1'
gem 'carrierwave', '0.9.0'
gem 'aasm', '3.0.16'
gem 'devise', '>= 1.1.2'

View File

@ -36,7 +36,7 @@ module JamWebEventMachine
end
def self.run_em
def self.run_em(calling_thread)
EM.run do
# this is global because we need to check elsewhere if we are currently connected to amqp before signalling success with some APIs, such as 'create session'
@ -53,6 +53,8 @@ module JamWebEventMachine
MQRouter.user_exchange = exchange
end
end
calling_thread.wakeup
end
end
@ -63,9 +65,12 @@ module JamWebEventMachine
end
def self.run
current = Thread.current
Thread.new do
run_em
run_em(current)
end
Thread.stop
end
def self.start
@ -79,8 +84,9 @@ module JamWebEventMachine
EM.stop
end
@@log.debug("starting EventMachine")
current = Thread.current
Thread.new do
run_em
run_em(current)
end
die_gracefully_on_signal
end

View File

@ -102,7 +102,7 @@ FOO
def send_test_batch
self.perform_event('do_test_run!')
if 'test'==Rails.env
if 'test' == Rails.env
BatchMailer.send_batch_email_test(self.id).deliver!
else
BatchMailer.send_batch_email_test(self.id).deliver

View File

@ -366,6 +366,8 @@ module JamRuby
def send_session_ended(session_id)
return if session_id.nil? # so we don't query every notification in the system with a nil session_id
notifications = Notification.where(:session_id => session_id)
# publish to all users who have a notification for this session

View File

@ -68,6 +68,8 @@ describe RecordedTrack do
describe "aws-based operations", :aws => true do
def put_file_to_aws(signed_data, contents)
begin
RestClient.put( signed_data[:url],
contents,
{
@ -76,6 +78,11 @@ describe RecordedTrack do
:'Content-MD5' => signed_data[:md5],
:Authorization => signed_data[:authorization]
})
rescue => e
puts e.response
raise e
end
end
# create a test file
upload_file='some_file.ogg'

View File

@ -1,3 +1,6 @@
ENV["RAILS_ENV"] = "test"
require 'simplecov'
require 'support/utilities'
require 'active_record'
@ -31,13 +34,14 @@ require 'resque_spec/scheduler'
include JamRuby
# manually register observers
ActiveRecord::Base.add_observer InvitedUserObserver.instance
ActiveRecord::Base.add_observer UserObserver.instance
ActiveRecord::Base.add_observer FeedbackObserver.instance
ActiveRecord::Base.add_observer RecordedTrackObserver.instance
RecordedTrack.observers.disable :all # only a few tests want this observer active
#RecordedTrack.observers.disable :all # only a few tests want this observer active
# put ActionMailer into test mode
ActionMailer::Base.delivery_method = :test

View File

@ -2,8 +2,9 @@ JAMKAZAM_TESTING_BUCKET = 'jamkazam-testing' # cuz i'm not comfortable using aws
def app_config
klass = Class.new do
def aws_bucket
JAMKAZAM_TESTING_BUCKET
JAMKAZAM_TESTING_BUCKET
end
def aws_access_key_id
@ -14,6 +15,18 @@ def app_config
'h0V0ffr3JOp/UtgaGrRfAk25KHNiO9gm8Pj9m6v3'
end
def aws_region
'us-east-1'
end
def aws_bucket_public
'jamkazam-testing-public'
end
def aws_cache
'315576000'
end
def audiomixer_path
# you can specify full path to audiomixer with AUDIOMIXER_PATH env variable...
# or we check for audiomixer path in the user's workspace
@ -77,30 +90,6 @@ def app_config
"#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}"
end
def aws_access_key_id
'AKIAJESQY24TOT542UHQ'
end
def aws_secret_access_key
'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'
end
def aws_region
'us-east-1'
end
def aws_bucket
'jamkazam-testing'
end
def aws_bucket_public
'jamkazam-testing-public'
end
def aws_cache
'315576000'
end
def max_audio_downloads
100
end
@ -124,7 +113,7 @@ def app_config
end
return klass.new
klass.new
end
def run_tests? type

View File

@ -48,7 +48,7 @@ gem 'fb_graph', '2.5.9'
gem 'sendgrid', '1.2.0'
gem 'recaptcha', '0.3.4'
gem 'filepicker-rails', '0.1.0'
gem 'aws-sdk', '1.29.1'
gem 'aws-sdk' #, '1.29.1'
gem 'aasm', '3.0.16'
gem 'carrierwave', '0.9.0'
gem 'carrierwave_direct'

View File

@ -1,15 +1,47 @@
// The wrapper around the web-socket connection to the server
(function(context, $) {
// manages the connection, heartbeats, and reconnect logic.
// presents itself as a dialog, or in-situ banner (_jamServer.html.haml)
(function (context, $) {
"use strict";
"use strict";
context.JK = context.JK || {};
context.JK = context.JK || {};
var logger = context.JK.logger;
var msg_factory = context.JK.MessageFactory;
var logger = context.JK.logger;
var msg_factory = context.JK.MessageFactory;
// Let socket.io know where WebSocketMain.swf is
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
// Let socket.io know where WebSocketMain.swf is
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
context.JK.JamServer = function (app) {
// heartbeat
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 lastHeartbeatAckTime = null;
var lastHeartbeatFound = false;
var heartbeatAckCheckInterval = null;
var notificationLastSeenAt = undefined;
var notificationLastSeen = undefined;
// 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;
// elements
var $inSituBanner = null;
var $inSituBannerHolder = null;
var $messageContents = null;
var $dialog = null;
var $templateServerConnection = null;
var $templateDisconnected = null;
var $currentDisplay = null;
var server = {};
server.socket = {};
@ -21,141 +53,459 @@
server.connected = 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(!initialConnect) {
context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error);
}
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(server.connected) {
server.connected = false;
context.JK.CurrentSessionModel.onWebsocketDisconnected(in_error);
// stop future heartbeats
if (heartbeatInterval != null) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
// 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);
}
}
// stop checking for heartbeat acks
if (heartbeatAckCheckInterval != null) {
clearTimeout(heartbeatAckCheckInterval);
heartbeatAckCheckInterval = null;
}
if (server.connected) {
server.connected = false;
if(app.clientUpdating) {
// we don't want to do a 'cover the whole screen' dialog
// because the client update is already showing.
return;
}
server.reconnecting = true;
var result = app.activeElementEvent('beforeDisconnect');
initiateReconnect(result, in_error);
app.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);
}
}
}
}
server.registerOnSocketClosed = function(callback) {
server.socketClosedListeners.push(callback);
////////////////////
//// 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() > 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;
}
}
server.registerMessageCallback = function(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() {
logger.log("server.connect");
var uri = context.JK.websocket_gateway_uri; // Set in index.html.erb.
//var uri = context.gon.websocket_gateway_uri; // Leaving here for now, as we're looking for a better solution.
server.socket = new context.WebSocket(uri);
server.socket.onopen = server.onOpen;
server.socket.onmessage = server.onMessage;
server.socket.onclose = server.onClose;
};
server.close = function(in_error) {
logger.log("closing websocket");
server.socket.close();
closedCleanup(in_error);
function _heartbeat() {
if (app.heartbeatActive) {
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
notificationLastSeenAt = undefined;
notificationLastSeen = undefined;
context.JK.JamServer.send(message);
lastHeartbeatFound = false;
}
}
server.rememberLogin = function() {
var token, loginMessage;
token = $.cookie("remember_token");
var clientType = context.jamClient.IsNativeClient() ? 'client' : 'browser';
loginMessage = msg_factory.login_with_token(token, null, clientType);
server.send(loginMessage);
};
function loggedIn(header, payload) {
server.onOpen = function() {
logger.log("server.onOpen");
server.rememberLogin();
};
if(!connectTimeout) {
clearTimeout(connectTimeout);
connectTimeout = null;
}
server.onMessage = function(e) {
var message = JSON.parse(e.data),
messageType = message.type.toLowerCase(),
payload = message[messageType],
callbacks = server.dispatchTable[message.type];
app.clientId = payload.client_id;
if(message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) {
logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload));
// tell the backend that we have logged in
context.jamClient.OnLoggedIn(payload.user_id, payload.token);
$.cookie('client_id', payload.client_id);
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
connectDeferred.resolve();
app.activeElementEvent('afterConnect', payload);
}
function heartbeatAck(header, payload) {
lastHeartbeatAckTime = new Date();
context.JK.CurrentSessionModel.trackChanges(header, payload);
}
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);
}
/**
* 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();
}
///////////////////
/// RECONNECT /////
///////////////////
function internetUp() {
var start = new Date().getTime();
server.connect()
.done(function() {
guardAgainstRapidTransition(start, performReconnect);
})
.fail(function() {
guardAgainstRapidTransition(start, closedOnReconnectAttempt);
});
}
// websocket couldn't connect. let's try again soon
function closedOnReconnectAttempt() {
failedReconnect();
}
function performReconnect() {
if($currentDisplay.is('.no-websocket-connection')) {
$currentDisplay.hide();
// TODO: tell certain elements that we've reconnected
}
else {
context.JK.CurrentSessionModel.leaveCurrentSession()
.always(function() {
window.location.reload();
});
}
server.reconnecting = false;
}
function buildOptions() {
return {};
}
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.show();
content = $inSituBannerHolder;
}
return content;
}
function formatDelaySecs(secs) {
return $('<span class="countdown-seconds"><span class="countdown">' + secs + '</span> ' + (secs == 1 ? ' second.<span style="visibility:hidden">s</span>' : 'seconds.') + '</span>');
}
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() {
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')) {
clearReconnectTimers();
attemptReconnect();
}
return false;
});
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;
}
}
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 {
logger.log("Unexpected message type %s.", message.type);
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) {
if (server.dispatchTable[messageType] === undefined) {
server.dispatchTable[messageType] = [];
}
server.dispatchTable[messageType].push(callback);
};
server.onClose = function() {
logger.log("Socket to server closed.");
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;
}
}
closedCleanup(true);
if (server.dispatchTable[messageType].length === 0) {
delete server.dispatchTable[messageType];
}
}
};
server.send = function(message) {
server.connect = function () {
connectDeferred = new $.Deferred();
logger.log("server.connect");
var uri = context.JK.websocket_gateway_uri; // 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 jsMessage = JSON.stringify(message);
server.socket = new context.WebSocket(uri);
server.socket.onopen = server.onOpen;
server.socket.onmessage = server.onMessage;
server.socket.onclose = server.onClose;
if(message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) {
logger.log("server.send(" + jsMessage + ")");
}
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
server.socket.send(jsMessage);
} else {
logger.log("Dropped message because server connection is closed.");
connectTimeout = setTimeout(function() {
connectTimeout = null;
if(connectDeferred.state() === 'pending') {
connectDeferred.reject();
}
}, 4000);
return connectDeferred;
};
server.loginSession = function(sessionId) {
var loginMessage;
server.close = function (in_error) {
logger.log("closing websocket");
if (!server.signedIn) {
logger.log("Not signed in!");
// TODO: surface the error
return;
server.socket.close();
closedCleanup(in_error);
}
server.rememberLogin = function () {
var token, loginMessage;
token = $.cookie("remember_token");
var clientType = context.jamClient.IsNativeClient() ? 'client' : 'browser';
loginMessage = msg_factory.login_with_token(token, null, clientType);
server.send(loginMessage);
};
server.onOpen = function () {
logger.log("server.onOpen");
server.rememberLogin();
};
server.onMessage = function (e) {
var message = JSON.parse(e.data),
messageType = message.type.toLowerCase(),
payload = message[messageType],
callbacks = server.dispatchTable[message.type];
if (message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) {
logger.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.log("Unexpected message type %s.", message.type);
}
};
loginMessage = msg_factory.login_jam_session(sessionId);
server.send(loginMessage);
server.onClose = function () {
logger.log("Socket to server closed.");
if(connectDeferred.state() === "pending") {
connectDeferred.reject();
}
closedCleanup(true);
};
server.send = function (message) {
var jsMessage = JSON.stringify(message);
if (message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) {
logger.log("server.send(" + jsMessage + ")");
}
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
server.socket.send(jsMessage);
} else {
logger.log("Dropped message because server connection is closed.");
}
};
server.loginSession = function (sessionId) {
var loginMessage;
if (!server.signedIn) {
logger.log("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
@ -163,47 +513,88 @@
* @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.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.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);
}
}
// Message callbacks
server.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, function (header, payload) {
server.signedIn = true;
logger.debug("Handling LOGIN_ACK. Updating client id to " + payload.client_id);
server.clientID = payload.client_id;
server.publicIP = payload.public_ip;
server.connected = true;
if (context.jamClient !== undefined) {
logger.debug("... (handling LOGIN_ACK) Updating backend client, connected to true and clientID to " +
payload.client_id);
context.jamClient.connected = true;
context.jamClient.clientID = server.clientID;
}
});
server.registerMessageCallback(context.JK.MessageType.PEER_MESSAGE, function (header, payload) {
if (context.jamClient !== undefined) {
context.jamClient.P2PMessageReceived(header.from, payload.message);
}
});
context.JK.JamServer = server;
// Message callbacks
server.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, function(header, payload) {
server.signedIn = true;
logger.debug("Handling LOGIN_ACK. Updating client id to " + payload.client_id);
server.clientID = payload.client_id;
server.publicIP = payload.public_ip;
server.connected = true;
if (context.jamClient !== undefined)
{
logger.debug("... (handling LOGIN_ACK) Updating backend client, connected to true and clientID to " +
payload.client_id);
context.jamClient.connected = true;
context.jamClient.clientID = server.clientID;
}
});
server.registerMessageCallback(context.JK.MessageType.PEER_MESSAGE, function(header, payload) {
if (context.jamClient !== undefined)
{
context.jamClient.P2PMessageReceived(header.from, payload.message);
}
});
// Callbacks from jamClient
if (context.jamClient !== undefined)
{
context.jamClient.SendP2PMessage.connect(server.sendP2PMessage);
if (context.jamClient !== undefined) {
context.jamClient.SendP2PMessage.connect(server.sendP2PMessage);
}
function initialize() {
registerLoginAck();
registerHeartbeatAck();
registerSocketClosed();
$inSituBanner = $('.server-connection');
$inSituBannerHolder = $('.no-websocket-connection');
$messageContents = $inSituBannerHolder.find('.message-contents');
$dialog = $('#banner');
$templateServerConnection = $('#template-server-connection');
$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;
this.initiateReconnect = initiateReconnect;
return this;
}
})(window, jQuery);

View File

@ -25,7 +25,7 @@
return newContent;
}
$('#banner').show()
$('#banner').attr('data-type', options.type).show()
$('#banner_overlay').show()
// return the core of the banner so that caller can attach event handlers to newly created HTML

View File

@ -123,6 +123,11 @@
return false;
}
if(!context.JK.JamServer.connected) {
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
return false;
}
// If user hasn't completed FTUE - do so now.
if (!(context.JK.hasOneConfiguredDevice())) {
app.afterFtue = function() { submitForm(evt); };

View File

@ -342,6 +342,12 @@
}
function afterShow(data) {
if(!context.JK.JamServer.connected) {
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
window.location = '/client#/home'
return;
}
clearResults();
buildQuery();
refreshDisplay();

View File

@ -2,15 +2,26 @@
"use strict";
context.JK = context.JK || {};
context.JK.BandHoverBubble = function(bandId, position) {
context.JK.BandHoverBubble = function(bandId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#band-hover";
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top});
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else
css.top = y - (7 + $(hoverSelector).height()) + 'px';
$(hoverSelector).css(css);
$(hoverSelector).fadeIn(500);
rest.getBand(bandId)
@ -28,7 +39,7 @@
instrumentHtml = '<td><div class="nowrap">';
if (val.instruments) { // @FIXME: edge case for Test user that has no instruments?
$.each(val.instruments, function(index, instrument) {
instrumentHtml += '<img src="' + instrumentLogoMap[instrument.instrument_id] + '" width="24" height="24" />&nbsp;';
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(instrument.instrument_id) + '" width="24" height="24" />&nbsp;';
});
}

View File

@ -2,7 +2,7 @@
"use strict";
context.JK = context.JK || {};
context.JK.FanHoverBubble = function(userId, position) {
context.JK.FanHoverBubble = function(userId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
@ -10,7 +10,19 @@
var hoverSelector = "#fan-hover";
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top});
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else
css.top = y - (7 + $(hoverSelector).height()) + 'px';
$(hoverSelector).css(css);
$(hoverSelector).fadeIn(500);
rest.getUserDetail({id: userId})

View File

@ -2,15 +2,27 @@
"use strict";
context.JK = context.JK || {};
context.JK.MusicianHoverBubble = function(userId, position) {
context.JK.MusicianHoverBubble = function(userId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#musician-hover";
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top});
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else
css.top = y - (7 + $(hoverSelector).height()) + 'px';
$(hoverSelector).css(css);
$(hoverSelector).fadeIn(500);
rest.getUserDetail({id: userId})
@ -20,7 +32,7 @@
// instruments
var instrumentHtml = '';
$.each(response.instruments, function(index, val) {
instrumentHtml += '<div class="left mr10 mb"><img src="' + instrumentLogoMap[val.instrument_id] + '" width="24" height="24" /></div>';
instrumentHtml += '<div class="left mr10 mb"><img src="' + context.JK.getInstrumentIcon24(val.instrument_id) + '" width="24" height="24" /></div>';
});
// followings

View File

@ -2,10 +2,9 @@
"use strict";
context.JK = context.JK || {};
context.JK.RecordingHoverBubble = function(recordingId, position) {
context.JK.RecordingHoverBubble = function(recordingId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#recording-hover";
function deDupTracks(recordedTracks) {
@ -39,7 +38,19 @@
}
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top+20});
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else
css.top = y - (7 + $(hoverSelector).height()) + 'px';
$(hoverSelector).css(css);
$(hoverSelector).fadeIn(500);
rest.getClaimedRecording(recordingId)
@ -61,8 +72,8 @@
instrumentHtml = '<td><div class="nowrap">';
$.each(val.instrument_ids, function(index, val) {
instrumentHtml += '<img src="' + instrumentLogoMap[val] + '" width="24" height="24" />&nbsp;&nbsp;';
})
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(val) + '" width="24" height="24" />&nbsp;&nbsp;';
});
instrumentHtml += '</div></td>';
musicianHtml += instrumentHtml;
@ -77,7 +88,7 @@
claimedRecordingId: claimedRecording.id,
name: claimedRecording.name,
genre: claimedRecording.genre_id.toUpperCase(),
created_at: context.JK.formatDateTime(recording.created_at),
created_at: $.timeago(recording.created_at),
description: response.description ? response.description : "",
play_count: recording.play_count,
comment_count: recording.comment_count,

View File

@ -2,15 +2,26 @@
"use strict";
context.JK = context.JK || {};
context.JK.SessionHoverBubble = function(sessionId, position) {
context.JK.SessionHoverBubble = function(sessionId, x, y) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#session-hover";
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top+10});
var mouseLeft = x < (document.body.clientWidth / 2);
var mouseTop = y < (document.body.clientHeight / 2);
var css = {};
if (mouseLeft)
css.left = x + 10 + 'px';
else
css.left = x - (7 + $(hoverSelector).width()) + 'px';
if (mouseTop)
css.top = y + 10 + 'px';
else
css.top = y - (7 + $(hoverSelector).height()) + 'px';
$(hoverSelector).css(css);
$(hoverSelector).fadeIn(500);
rest.getSessionHistory(sessionId)
@ -28,7 +39,7 @@
instrumentHtml = '<td><div class="nowrap">';
var instruments = val.instruments.split("|");
$.each(instruments, function(index, instrument) {
instrumentHtml += '<img src="' + instrumentLogoMap[instrument] + '" width="24" height="24" />&nbsp;';
instrumentHtml += '<img src="' + context.JK.getInstrumentIcon24(instrument) + '" width="24" height="24" />&nbsp;';
});
instrumentHtml += '</div></td>';
@ -45,7 +56,7 @@
genre: response.genres.toUpperCase(),
comment_count: response.comment_count,
like_count: response.like_count,
created_at: context.JK.formatDateTime(response.created_at),
created_at: $.timeago(response.created_at),
musicians: musicianHtml
});

View File

@ -20,16 +20,9 @@
var app;
var logger = context.JK.logger;
var rest = context.JK.Rest();
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 userDeferred = null;
var notificationLastSeenAt = undefined;
var notificationLastSeen = undefined;
var opts = {
inClient: true, // specify false if you want the app object but none of the client-oriented features
@ -72,58 +65,6 @@
$(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(notificationLastSeen, notificationLastSeenAt);
notificationLastSeenAt = undefined;
notificationLastSeen = undefined;
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();
context.JK.CurrentSessionModel.trackChanges(header, payload);
}
/**
* This occurs when the websocket gateway loses a connection to the backend messaging system,
* resulting in severe loss of functionality
@ -153,37 +94,6 @@
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");
@ -200,12 +110,6 @@
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.
*/
@ -259,7 +163,7 @@
* being shown or hidden.
* @screen is a string corresponding to the screen's layout-id attribute
* @handler is an object with up to four optional keys:
* beforeHide, afterHide, beforeShow, afterShow, which should all have
* beforeHide, afterHide, beforeShow, afterShow, beforeDisconnect, which should all have
* functions as values. If there is data provided by the screen's route
* it will be provided to these functions.
*/
@ -387,26 +291,12 @@
return userDeferred;
}
this.activeElementEvent = function(evtName, data) {
return this.layout.activeElementEvent(evtName, data);
}
this.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);
}
context.JK.JamServer.updateNotificationSeen(notificationId, notificationCreatedAt);
}
this.unloadFunction = function () {
@ -437,11 +327,8 @@
userDeferred = rest.getUserDetail();
if (opts.inClient) {
registerLoginAck();
registerHeartbeatAck();
registerBadStateRecovered();
registerBadStateError();
registerSocketClosed();
registerDownloadAvailable();
context.JK.FaderHelpers.initialize();
context.window.onunload = this.unloadFunction;

View File

@ -27,6 +27,8 @@
// privates
var logger = context.JK.logger;
var NOT_HANDLED = "not handled";
var me = null; // Reference to this instance for context sanity.
var opts = {
@ -36,6 +38,7 @@
notifyGutter: 10,
collapsedSidebar: 30,
panelHeaderHeight: 36,
alwaysOpenPanelHeaderHeight:78, // for the search bar
gutter: 60, // Margin around the whole UI
screenMargin: 0, // Margin around screens (not headers/sidebar)
gridOuterMargin: 6, // Outer margin on Grids (added to screenMargin if screen)
@ -74,7 +77,7 @@
}
function setInitialExpandedSidebarPanel() {
expandedPanel = $('[layout="panel"]').first().attr("layout-id");
expandedPanel = 'panelFriends';
}
function layout() {
@ -253,10 +256,9 @@
}
var $expandedPanel = $('[layout-id="' + expandedPanel + '"]');
var $expandedPanelContents = $expandedPanel.find('[layout-panel="contents"]');
var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
var searchHeight = $('.sidebar .search').first().height();
var combinedHeaderHeight = ($('[layout-panel="contents"]').length - 1) * opts.panelHeaderHeight + opts.alwaysOpenPanelHeaderHeight;
var expanderHeight = $('[layout-sidebar-expander]').height();
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight);
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight);
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({"height": "1px"});
$expandedPanelContents.show();
@ -315,11 +317,16 @@
// This allows dialogs to use the notification.
$('[layout="notify"]').css({"z-index": "1001", "padding": "20px"});
$('[layout="panel"]').css({position: 'relative'});
$('[layout-panel="expanded"] [layout-panel="header"]').css({
margin: "0px",
padding: "0px",
height: opts.panelHeaderHeight + "px"
});
var $headers = $('[layout-panel="expanded"] [layout-panel="header"]');
context._.each($headers, function($header) {
$header = $($header);
var isAlwaysOpenHeader = $header.is('.always-open');
$header.css({
margin: "0px",
padding: "0px",
height: (isAlwaysOpenHeader ? opts.alwaysOpenPanelHeaderHeight : opts.panelHeaderHeight) + "px"
});
})
$('[layout-grid]').css({
position: "relative"
});
@ -436,18 +443,29 @@
return screenBindings[screen][evtName].call(me, data);
}
}
return NOT_HANDLED;
}
function dialogEvent(dialog, evtName, data) {
if (dialog && dialog in dialogBindings) {
if (evtName in dialogBindings[dialog]) {
var result = dialogBindings[dialog][evtName].call(me, data);
if (result === false) {
return false;
}
return dialogBindings[dialog][evtName].call(me, data);
}
}
return true;
return NOT_HANDLED;
}
function activeElementEvent(evtName, data) {
var result = {};
var currDialog = currentDialog();
if(currDialog) {
result.dialog = dialogEvent(currDialog.attr('layout-id'), evtName, data);
}
if(currentScreen) {
result.screen = screenEvent(currentScreen, evtName, data);
}
return result;
}
function onHashChange(e, postFunction) {
@ -614,7 +632,7 @@
}
function showDialog(dialog, options) {
if (!dialogEvent(dialog, 'beforeShow', options)) {
if (dialogEvent(dialog, 'beforeShow', options) === false) {
return;
}
var $overlay = $('.dialog-overlay')
@ -896,14 +914,30 @@
return isNoisyNotification(payload);
}
this.shouldFreezeAppOnDisconnect = function() {
return shouldFreezeAppOnDisconnect();
}
this.isDialogShowing = function() {
return isDialogShowing();
}
this.activeElementEvent = function(evtName, data) {
return activeElementEvent(evtName, data);
}
this.close = function (evt) {
close(evt);
};
this.beforeDisconnect = function() {
fireEvents();
}
this.afterReconnect = function() {
fireEvents();
}
this.closeDialog = closeDialog;
this.handleDialogState = handleDialogState;

View File

@ -30,6 +30,18 @@
setCount(count + 1);
}
function decrementNotificationCount() {
var count = parseInt($count.text());
if(count > 0) {
count = count - 1;
setCount(count);
if(count == 0) {
lowlightCount();
missedNotificationsWhileAway = false;
}
}
}
// set the element to white, and pulse it down to the un-highlighted value 2x, then set
function pulseToDark() {
logger.debug("pulsing notification badge")
@ -348,6 +360,7 @@
function deleteNotification(notificationId) {
console.trace();
var url = "/api/users/" + context.JK.currentUserId + "/notifications/" + notificationId;
$.ajax({
type: "DELETE",
@ -356,8 +369,11 @@
url: url,
processData: false,
success: function(response) {
$('li[notification-id=' + notificationId + ']').hide();
//decrementNotificationCount();
var notification = $('li[notification-id=' + notificationId + ']');
if(notification.length > 0) {
decrementNotificationCount();
}
notification.remove();
},
error: app.ajaxError
});

View File

@ -77,6 +77,7 @@
}
context.JK.SearchResultScreen.onSearchSuccess = function(response) {
$('#sidebar-search-results').show();
searchResults(response, true);
searchResults(response, false);
context.JK.bindHoverEvents();
@ -183,11 +184,6 @@
if (isSidebar) {
// show header
$('#sidebar-search-header').show();
// hide panels
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({"height": "1px"});
// resize search results area
$('#sidebar-search-results').height(context.JK.Sidebar.getHeight() + 'px');
}
else {
$('#result-count').html(resultCount);

View File

@ -139,8 +139,11 @@
shareDialog.initialize(context.JK.FacebookHelperInstance);
}
function beforeDisconnect() {
return { freezeInteraction: true };
}
function alertCallback(type, text) {
function alertCallback(type, text) {
function timeCallback() {
var start = new Date();
@ -275,6 +278,13 @@
function afterShow(data) {
if(!context.JK.JamServer.connected) {
promptLeave = false;
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
window.location = '/client#/home'
return;
}
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
app.afterFtue = function() { initializeSession(); };
app.cancelFtue = function() { promptLeave = false; window.location = '/client#/home' };
@ -1516,7 +1526,8 @@
'beforeShow': beforeShow,
'afterShow': afterShow,
'beforeHide': beforeHide,
'beforeLeave' : beforeLeave
'beforeLeave' : beforeLeave,
'beforeDisconnect' : beforeDisconnect,
};
app.bindScreen('session', screenBindings);
};

View File

@ -151,6 +151,11 @@
var $parentRow = $('tr[id=' + session.id + ']', tbGroup);
$('.join-link', $parentRow).click(function(evt) {
if(!context.JK.JamServer.connected) {
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
return false;
}
// If no FTUE, show that first.
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
app.afterFtue = function() { joinClick(session.id); };

View File

@ -22,7 +22,7 @@
// we track all the clientIDs of all the participants ever seen by this session, so that we can reliably convert a clientId from the backend into a username/avatar
var participantsEverSeen = {};
function id() {
function id() {
return currentSession ? currentSession.id : null;
}
@ -379,67 +379,11 @@
});
}
function reconnect() {
context.JK.CurrentSessionModel.leaveCurrentSession()
.always(function() {
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
});
rest.serverHealthCheck()
.done(function() {
reconnect();
})
.fail(function(xhr, textStatus, errorThrown) {
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);
}
});
return false;
});
}
function onWebsocketDisconnected(in_error) {
if(app.clientUpdating) {
// we don't want to do a 'cover the whole screen' dialog
// because the client update is already showing.
return;
}
// kill the streaming of the session immediately
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: currentSessionId });
if(in_error) {
var template = $('#template-disconnected').html();
var templateHtml = context.JK.fillTemplate(template, null);
var content = context.JK.Banner.show({
html : template
}) ;
registerReconnect(content);
}
}
// returns a deferred object
@ -456,7 +400,6 @@
if(foundParticipant) {
return $.Deferred().resolve(foundParticipant.user).promise();
}
}
// TODO: find it via some REST API if not found?

View File

@ -104,40 +104,14 @@
}
}
context.JK.Sidebar.getHeight = function() {
// TODO: refactor this - copied from layout.js
var sidebarHeight = $(context).height() - 75 - 2 * 60 + $('[layout-sidebar-expander]').height();
var combinedHeaderHeight = $('[layout-panel="contents"]').length * 36;
var searchHeight = $('.sidebar .search').first().height();
var expanderHeight = $('[layout-sidebar-expander]').height();
var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight);
return expandedPanelHeight;
}
function showFriendsPanel() {
var $expandedPanelContents = $('[layout-id="panelFriends"] [layout-panel="contents"]');
var expandedPanelHeight = context.JK.Sidebar.getHeight();
// hide all other contents
$('[layout-panel="contents"]').hide();
$('[layout-panel="contents"]').css({"height": "1px"});
// show the appropriate contens
$expandedPanelContents.show();
$expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, 400);
}
function hideSearchResults() {
emptySearchResults();
$('#search-input').val('');
$('#sidebar-search-header').hide();
showFriendsPanel();
}
function emptySearchResults() {
$('#sidebar-search-results').empty();
$('#sidebar-search-results').height('0px');
}
var delay = (function(){

View File

@ -10,6 +10,8 @@
var $previousMessagesScroller = null;
var $sendTextMessage = null;
var $form = null;
var $interactionBlocker = null;
var $disconnectedMsg = null;
var $textBox = null;
var userLookup = null;
var otherId = null;
@ -48,10 +50,14 @@
}
function sendMessage() {
if(!context.JK.JamServer.connected) {
return false;
}
var msg = $textBox.val();
if(!msg || msg == '') {
// don't bother the server with empty messages
return;
return false;
}
if(!sendingMessage) {
@ -136,35 +142,30 @@
$textBox.focus();
}
function beforeShow(args) {
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
function renderDialog() {
app.user()
.done(function(userDetail) {
.done(function (userDetail) {
user = userDetail;
var other = args.d1;
if(!other) throw "other must be specified in TextMessageDialog"
otherId = other;
showing = true;
userLookup[user.id] = user;
rest.getUserDetail({id: otherId})
.done(function(otherUser) {
.done(function (otherUser) {
userLookup[otherUser.id] = otherUser;
$dialog.find('.receiver-name').text(otherUser.name);
$dialog.find('textarea').attr('placeholder', 'enter a message to ' + otherUser.name + '...');
$dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
$dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
if (!context.JK.JamServer.connected) {
renderNotConnected();
}
$sendTextMessage.click(sendMessage);
rest.getNotifications(buildParams())
.done(function(response) {
context._.each(response, function(textMessage) {
.done(function (response) {
context._.each(response, function (textMessage) {
renderMessage(textMessage.message, textMessage.source_user_id, userLookup[textMessage.source_user_id].name, textMessage.created_at);
})
@ -172,21 +173,53 @@
fullyInitialized = true;
drainQueue();
})
.fail(function(jqXHR) {
.fail(function (jqXHR) {
app.notifyServerError(jqXHR, 'Unable to Load Conversation')
})
})
.fail(function(jqXHR) {
.fail(function (jqXHR) {
app.notifyServerError(jqXHR, 'Unable to Load Other User')
})
})
}
function beforeShow(args) {
app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog
var other = args.d1;
if (!other) throw "other must be specified in TextMessageDialog"
otherId = other;
renderDialog();
}
function afterHide() {
showing = false;
reset();
}
function renderNotConnected() {
$interactionBlocker.addClass('active');
$disconnectedMsg.addClass('active');
}
function renderConnected() {
$interactionBlocker.removeClass('active');
$disconnectedMsg.removeClass('active');
}
function beforeDisconnect() {
renderNotConnected();
}
function afterConnect() {
renderConnected();
reset();
renderDialog();
}
function pasteIntoInput(el, text) {
el.focus();
if (typeof el.selectionStart == "number"
@ -255,24 +288,13 @@
}
}
/**
function showDialog(_other) {
app.layout.closeDialog('text-message') // this dialog is implemented as a singleton, so must enforce this
reset();
if(!_other) throw "other must be specified in TextMessageDialog"
otherId = _other;
app.layout.showDialog('text-message')
}*/
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
'afterHide': afterHide
'afterHide': afterHide,
'beforeDisconnect': beforeDisconnect,
'afterConnect': afterConnect
};
@ -284,6 +306,8 @@
$sendTextMessage = $dialog.find('.btn-send-text-message');
$form = $dialog.find('form');
$textBox = $form.find('textarea');
$interactionBlocker = $dialog.find('.interaction-blocker');
$disconnectedMsg = $dialog.find('.disconnected-msg');
events();
}

View File

@ -154,6 +154,8 @@
}
context.JK.bindHoverEvents = function ($parent) {
var timeout = 300;
var fadeoutValue = 100;
if (!$parent) {
$parent = $('body');
@ -165,75 +167,87 @@
}
function hideBubble($hoverElement) {
var bubbleSelector = $hoverElement.attr("bubble-id");
$(bubbleSelector).hover(
function () {
// do nothing when entering the bubble (this should never happen)
// do nothing when entering the bubble
},
function () {
$(this).fadeOut(100);
$(this).fadeOut(fadeoutValue);
}
);
// first check to see if the user isn't hovering over the hover bubble
if (!$(bubbleSelector).is(":hover")) {
$(bubbleSelector).fadeOut(fadeoutValue);
}
}
// MUSICIAN
$("[hoveraction='musician']", $parent).hoverIntent({
over: function () {
var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), $(this).offset());
over: function(e) {
var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
},
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1
sensitivity: 1,
timeout: timeout
});
// FAN
$("[hoveraction='fan']", $parent).hoverIntent({
over: function () {
var bubble = new JK.FanHoverBubble($(this).attr('user-id'), $(this).offset());
over: function(e) {
var bubble = new JK.FanHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
},
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1
sensitivity: 1,
timeout: timeout
});
// BAND
$("[hoveraction='band']", $parent).hoverIntent({
over: function () {
var bubble = new JK.BandHoverBubble($(this).attr('band-id'), $(this).offset());
over: function(e) {
var bubble = new JK.BandHoverBubble($(this).attr('band-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
},
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1
sensitivity: 1,
timeout: timeout
});
// SESSION
$("[hoveraction='session']", $parent).hoverIntent({
over: function () {
var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), $(this).offset());
over: function(e) {
var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
},
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1
sensitivity: 1,
timeout: timeout
});
// RECORDING
$("[hoveraction='recording']", $parent).hoverIntent({
over: function () {
var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), $(this).offset());
over: function(e) {
var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
},
out: function () { // this registers for leaving the hoverable element
hideBubble($(this));
},
sensitivity: 1
sensitivity: 1,
timeout: timeout
});
}

View File

@ -28,7 +28,6 @@
}
function togglePlay() {
if(playing) {
stopPlay();
}
@ -51,10 +50,10 @@
function like() {
rest.addRecordingLike(recordingId, claimedRecordingId, JK.currentUserId)
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike", $scope).unbind("click");
});
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike", $scope).unbind("click");
});
}
function play() {
@ -68,22 +67,35 @@
var comment = $("#txtRecordingComment").val();
if ($.trim(comment).length > 0) {
rest.addRecordingComment(recordingId, JK.currentUserId, comment)
.done(function(response) {
$("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
var template = $('#template-landing-comment').html();
var commentHtml = context.JK.fillTemplate(template, {
avatar_url: context.JK.currentUserAvatarUrl,
name: context.JK.currentUserName,
comment: comment,
timeago: $.timeago(Date.now())
});
$(".landing-comment-scroller").prepend(commentHtml);
});
.done(function(response) {
$("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
});
}
}
function renderComment(comment, userId, userName, userAvatarUrl, timeago, musician, append) {
var template = $('#template-landing-comment').html();
var commentHtml = context.JK.fillTemplate(template, {
avatar_url: userAvatarUrl,
user_id: userId,
hoverAction: musician ? "musician" : "fan",
name: userName,
comment: comment,
timeago: timeago
});
if (append) {
$(".landing-comment-scroller").append(commentHtml);
}
else {
$(".landing-comment-scroller").prepend(commentHtml);
}
context.JK.bindHoverEvents();
}
function initialize(_claimedRecordingId, _recordingId) {
recordingId = _recordingId;
claimedRecordingId = _claimedRecordingId;
@ -116,6 +128,32 @@
$("#btnLike").click(like);
$("#btnPlay").click(play);
$playButton.trigger('click');
pollForUpdates(claimedRecordingId);
}
function pollForUpdates(claimedRecordingId) {
$(".landing-comment-scroller").empty();
rest.getClaimedRecording(claimedRecordingId)
.done(function(response) {
if (response.recording && response.recording.comments) {
$("#spnPlayCount", $scope).html(response.recording.play_count);
$("#spnCommentCount", $scope).html(response.recording.comment_count);
$("#spnLikeCount", $scope).html(response.recording.like_count);
$.each(response.recording.comments, function(index, val) {
renderComment(val.comment, val.creator.id, val.creator.name,
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
});
setTimeout(function() {
pollForUpdates(claimedRecordingId);
}, 60000);
}
})
.fail(function(xhr) {
});
}
this.initialize = initialize;

View File

@ -4,38 +4,53 @@
var logger = context.JK.logger;
var rest = new JK.Rest();
var sessionId = null;
var $scope = $(".landing-details");
var $controls = null;
var $status = null;
var $playButton = $('.play-button');
var playing = false;
function like() {
rest.addSessionLike(sessionId, JK.currentUserId)
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike").unbind("click");
});
.done(function(response) {
$("#spnLikeCount").html(parseInt($("#spnLikeCount").text()) + 1);
$("#btnLike").unbind("click");
});
}
function addComment() {
var comment = $("#txtSessionComment").val();
if ($.trim(comment).length > 0) {
rest.addSessionComment(sessionId, JK.currentUserId, comment)
.done(function(response) {
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
var template = $('#template-landing-comment').html();
var commentHtml = context.JK.fillTemplate(template, {
avatar_url: context.JK.currentUserAvatarUrl,
name: context.JK.currentUserName,
comment: comment,
timeago: $.timeago(Date.now())
});
$(".landing-comment-scroller").prepend(commentHtml);
});
rest.addSessionComment(sessionId, JK.currentUserId, comment)
.done(function(response) {
$("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
});
}
}
function renderComment(comment, userId, userName, userAvatarUrl, timeago, musician, append) {
var template = $('#template-landing-comment').html();
var commentHtml = context.JK.fillTemplate(template, {
avatar_url: userAvatarUrl,
user_id: userId,
hoverAction: musician ? "musician" : "fan",
name: userName,
comment: comment,
timeago: timeago
});
if (append) {
$(".landing-comment-scroller").append(commentHtml);
}
else {
$(".landing-comment-scroller").prepend(commentHtml);
}
context.JK.bindHoverEvents();
}
function stateChange(e, data) {
if(data.displayText)
{
@ -88,34 +103,57 @@
$controls.bind('statechange.listenBroadcast', stateChange);
context.JK.prettyPrintElements($('time.duration').show());
context.JK.TickDuration(null);
$('.play-button').click(togglePlay);
$playButton.click(togglePlay);
sessionId = musicSessionId;
if (JK.currentUserId) {
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
shareDialog.initialize(JK.FacebookHelperInstance);
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
shareDialog.initialize(JK.FacebookHelperInstance);
$("#btnShare").click(function(e) {
shareDialog.showDialog();
});
$("#btnShare").click(function(e) {
shareDialog.showDialog();
});
$("#btnPostComment").click(function(e) {
if ($.trim($("#txtSessionComment").val()).length > 0) {
addComment();
$("#txtSessionComment").val('');
$("#txtSessionComment").blur();
}
});
$("#btnPostComment").click(function(e) {
if ($.trim($("#txtSessionComment").val()).length > 0) {
addComment();
$("#txtSessionComment").val('');
$("#txtSessionComment").blur();
}
});
}
else {
$("#txtSessionComment").attr("disabled", "disabled");
$("#txtSessionComment").val("You must be logged in to add a comment.");
$("#txtSessionComment").attr("disabled", "disabled");
$("#txtSessionComment").val("You must be logged in to add a comment.");
}
$("#btnLike").click(like);
$playButton.trigger('click');
pollForUpdates(musicSessionId);
}
function pollForUpdates(musicSessionId) {
$(".landing-comment-scroller").empty();
rest.getSessionHistory(musicSessionId)
.done(function(response) {
if (response && response.comments) {
$("#spnCommentCount", $scope).html(response.comment_count);
$("#spnLikeCount", $scope).html(response.like_count);
$.each(response.comments, function(index, val) {
renderComment(val.comment, val.creator.id, val.creator.name,
context.JK.resolveAvatarUrl(val.creator.photo_url), $.timeago(val.created_at), val.creator.musician, true);
});
setTimeout(function() {
pollForUpdates(musicSessionId);
}, 60000);
}
})
.fail(function(xhr) {
});
}
this.initialize = initialize;

View File

@ -1,8 +1,19 @@
#banner {
display:none;
&[data-type="reconnect"] {
height:240px;
}
h2 {
font-weight:bold;
font-size:x-large;
}
.countdown {
font-weight:bold;
min-width:9px;
display:inline-block;
}
}
#banner h2 {
font-weight:bold;
font-size:x-large;
}

View File

@ -33,6 +33,7 @@
*= require ./account
*= require ./search
*= require ./ftue
*= require ./jamServer
*= require ./gearWizard
*= require ./whatsNextDialog
*= require ./invitationDialog

View File

@ -4,11 +4,11 @@
background-color:#242323;
border:solid 1px #ed3618;
position:absolute;
z-index:999;
z-index:1100;
&.musician-bubble {
width:410px;
width:425px;
}
h2 {

View File

@ -0,0 +1,65 @@
.no-websocket-connection {
display:none;
text-align:center;
width:100%;
position:absolute;
}
.server-connection {
margin:auto;
display:inline-block;
zoom:1;
text-align:center;
padding:10px 20px;
background-color:#404040;
border-color:#ccc;
border-style:solid;
border-width:0 2px 2px;
-webkit-box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
-moz-box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
h2 {
font-size:20px;
vertical-align:baseline;
margin-bottom:10px;
}
img.alert-icon {
top:14px;
height:16px;
width:16px;
&.left-side {
padding-right:20px;
}
&.right-side {
padding-left:20px;
}
}
.reconnect-progress-msg {
margin-bottom:10px;
}
.reconnect-countdown {
}
#reconnect-now {
margin-top:10px;
}
.countdown {
font-weight:bold;
min-width:9px;
display:inline-block;
}
.countdown-seconds {
}
}

View File

@ -58,4 +58,39 @@
text-align:center;
width:50px;
}
.interaction-blocker {
display:none;
position:absolute;
background-color:#333;
text-align:center;
&.active {
display:block;
top:0;
bottom:40px;
left:0;
right:0;
opacity:0.5;
}
}
.disconnected-msg {
color:white;
font-size:25px;
left:0;
margin:auto;
position:absolute;
text-align:center;
top:30%;
width:100%;
display:none;
&.active {
display:inline-block;
}
}
}

View File

@ -9,3 +9,8 @@ body {
height:100%;
margin:0 !important;
}
.wrapper {
width:1280px;
margin:0 auto;
}

View File

@ -0,0 +1,9 @@
class ExtrasController < ApplicationController
before_filter :signed_in_user
before_filter :admin_user
def settings
render layout: "web"
end
end

View File

@ -396,10 +396,6 @@ class UsersController < ApplicationController
redirect_to(root_url) unless current_user?(@user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
# the User Model expects instruments in a different format than the form submits it
# so we have to fix it up.
def fixup_instruments(original_instruments)

View File

@ -1,8 +0,0 @@
class VideosController < ApplicationController
def show_dialog
@video_id = @params[:video_id]
end
end

View File

@ -57,6 +57,10 @@ module SessionsHelper
cookies.delete(:remember_token, domain: Rails.application.config.session_cookie_domain)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)

View File

@ -35,7 +35,7 @@ child(:recording => :recording) {
attributes :comment, :created_at
child(:user => :creator) {
attributes :id, :first_name, :last_name, :photo_url
attributes :id, :first_name, :last_name, :name, :photo_url, :musician
}
}
}

View File

@ -22,4 +22,12 @@ child(:music_session_user_histories => :users) {
child(:user => :user) {
attributes :name, :photo_url
}
}
child(:comments => :comments) {
attributes :comment, :created_at
child(:user => :creator) {
attributes :id, :first_name, :last_name, :name, :photo_url, :musician
}
}

View File

@ -1,7 +1,7 @@
<!-- generic banner for use by an code -->
<div class="overlay" id="banner_overlay"></div>
<div id="banner" class="dialog-overlay-sm">
<div id="banner" class="dialog-overlay-sm" data-type="">
<!-- dialog header -->
<div class="content-head">

View File

@ -0,0 +1,12 @@
.no-websocket-connection
.server-connection
%h2
= image_tag( "content/icon_alert.png" , :class => "alert-icon left-side" )
%span Disconnected from Server
= image_tag( "content/icon_alert.png" , :class => "alert-icon right-side" )
.message-contents
%script{type: 'text/template', id: 'template-server-connection'}
%div.reconnect-progress-msg
%span.reconnect-before= 'Reconnecting automatically in '
%span.reconnect-countdown= '{{data.countdown}}'
%a.disconnected-reconnect.reconnect-enabled{href:'#'} RECONNECT NOW

View File

@ -11,28 +11,36 @@
</div>
<!-- Search Box -->
<div class="search">
<div layout-panel="header" class="panel-header">
<h2>search</h2>
<div class="search" layout="panel" layout-id="panelSearch">
<div layout-panel="collapsed">
</div>
<div class="searchbox">
<form id="searchForm">
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
</form>
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
<div class="left">
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">&laquo;&nbsp;EXPAND</a></strong>
</div>
<!-- search filter dropdown -->
<div class="right">
Show:&nbsp;<%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
<div layout-panel="expanded" class="panel expanded">
<div layout-panel="header" class="panel-header always-open">
<h2>search</h2>
<div class="searchbox">
<form id="searchForm">
<input id="search-input" autocomplete="off" type="text" name="search" placeholder="enter search terms" />
</form>
</div>
</div>
</div>
<div style="clear:both;"></div><br />
<!-- border between header and beginning of search results -->
<div class="sidebar-search-result"></div>
<div id="sidebar-search-results" class="results-wrapper">
<!-- border between header and beginning of search results -->
<!--<div class="sidebar-search-result"></div>-->
<div class="panelcontents" layout-panel="contents">
<div id="sidebar-search-header" style="margin: 4px 4px 8px 8px">
<div class="left">
<strong><a id="sidebar-search-expand" style="color:#fff; text-decoration:underline">&laquo;&nbsp;EXPAND</a></strong>
</div>
<!-- search filter dropdown -->
<div class="right">
Show:&nbsp;<%= select_tag(Search::SEARCH_TEXT_TYPE_ID, options_for_select(Search::SEARCH_TEXT_TYPES.collect { |ii| [ii.to_s.titleize, ii] })) %>
</div>
</div>
<div style="clear:both;"></div><br />
<div id="sidebar-search-results" class="results-wrapper"></div>
</div>
</div>
</div>

View File

@ -16,6 +16,9 @@
.right
%a.button-grey.btn-close-dialog{href:'#', 'layout-action' => 'close'} CLOSE
%a.button-orange.btn-send-text-message{href:'#'} SEND
.interaction-blocker
%span.disconnected-msg DISCONNECTED FROM SERVER
%script{type: 'text/template', id: 'template-previous-message'}
.previous-message

View File

@ -4,39 +4,12 @@
<br />
<br />
<div align="center">
You have been disconnected from JamKazam.
You have been disconnected from JamKazam. <br/><br/>
<span class="reconnect-progress-msg">Reconnecting automatically in <span class="reconnect-countdown">{countdown}</span></span>
</div>
<br clear="all" /><br />
<div class="right">
<a href="#" class="button-orange disconnected-reconnect">RECONNECT</a>
<a href="#" class="button-orange disconnected-reconnect">RECONNECT NOW</a>
</div>
</script>
<script type="text/template" id="template-reconnecting">
<h2 align="center">Reconnecting to Server</h2>
<br />
<br />
<br />
<div align="center">
Attempting to reestablish connection to the server...
</div>
<br clear="all" /><br />
</script>
<script type="text/template" id="template-could-not-reconnect">
<h2 align="center">Unable to Reconnect</h2>
<br />
<br />
<br />
<div align="center">
We were not able to reconnect to JamKazam. <br/></br/>
Please check your internet connection, then try again later.
</div>
<br clear="all" /><br />
<div class="right">
<a href="#" class="button-orange disconnected-reconnect">TRY AGAIN TO RECONNECT</a>
</div>
</script>

View File

@ -14,6 +14,7 @@
<%= render "faders" %>
<%= render "vu_meters" %>
<%= render "ftue" %>
<%= render "jamServer" %>
<%= render "clients/gear/gear_wizard" %>
<%= render "terms" %>
<%= render "leaveSessionWarning" %>
@ -118,8 +119,9 @@
window.location.href = '/?redirect-to=' + encodeURIComponent(JK.locationPath());
<% end %>
// Some things can't be initialized until we're connected. Put them here.
function _initAfterConnect() {
function _initAfterConnect(connected) {
if (this.didInitAfterConnect) return;
this.didInitAfterConnect = true
@ -242,12 +244,17 @@
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
testBridgeScreen.initialize();
if(!connected) {
jamServer.initiateReconnect(null, true);
}
JK.app.initialRouting();
JK.hideCurtain(300);
}
JK.app = JK.JamKazam();
JK.app.initAfterConnect = _initAfterConnect;
var jamServer = new JK.JamServer(JK.app);
jamServer.initialize();
// If no jamClient (when not running in native client)
// create a fake one.
@ -302,25 +309,16 @@
JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration');
JK.JamServer.connect(); // singleton here defined in JamServer.js
JK.JamServer.connect() // singleton here defined in JamServer.js
.done(function() {
_initAfterConnect(true);
})
.fail(function() {
_initAfterConnect(false);
});
// this ensures that there is always a CurrentSessionModel, even if it's for a non-active session
JK.CurrentSessionModel = new JK.SessionModel(JK.app, JK.JamServer, window.jamClient);
// Run a check to see if we're logged in yet. Only after that should
// we initialize the other screens.
function testConnected() {
this.numCalls = (this.numCalls || 0) + 1;
if (JK.clientId) {
JK.app.initAfterConnect();
} else {
if (50 <= this.numCalls) { // 5 second max
JK.notifyAlert('Server Error', 'Could not connect to server. Please try again later.')
} else {
window.setTimeout(testConnected, 100);
}
}
}
testConnected();
}
JK.bindHoverEvents();

View File

@ -0,0 +1,6 @@
%h1 Extra Feature Settings
%form
%button.launch_new_ftue{type: 'button'}

View File

@ -32,7 +32,9 @@
<body class="jam">
<div id="minimal-container">
<%= javascript_include_tag "minimal/minimal" %>
<%= yield %>
<div class="wrapper">
<%= yield %>
</div>
</div>
<script type="text/javascript">
@ -56,5 +58,4 @@
<%= render "shared/ga" %>
<!-- version info: <%= version %> -->
</body>
</html>
c
</html>

View File

@ -92,10 +92,12 @@
JK.currentUserId = '<%= current_user.id %>';
JK.currentUserAvatarUrl = JK.resolveAvatarUrl('<%= current_user.photo_url %>');
JK.currentUserName = '<%= current_user.name %>';
JK.currentUserMusician = '<%= current_user.musician %>';
<% else %>
JK.currentUserId = null;
JK.currentUserAvatarUrl = null;
JK.currentUserName = null;
JK.currentUserMusician = null;
<% end %>
JK.app = JK.JamKazam();

View File

@ -12,33 +12,18 @@
<br clear="all" />
<div class="landing-comment-scroller">
<% comments.each do |c| %>
<% hoverAction = c.user.musician ? "musician" : "fan" %>
<div user-id="<%= c.user.id %>" hoveraction="<%= hoverAction %>" class="avatar-small mr10">
<% unless c.user.photo_url.blank? %>
<%= image_tag "#{c.user.photo_url}", {:alt => ""} %>
<% else %>
<%= image_tag "shared/avatar_generic.png", {:alt => ""} %>
<% end %>
</div>
<div class="w80 left p10 lightgrey mt10">
<a user-id="<%= c.user.id %>" hoveraction="<%= hoverAction %>" href="#"><%= c.user.name %></a>&nbsp;<%= c.comment %>
<br />
<div class="f12 grey mt5"><%= timeago(c.created_at) %></div>
</div>
<br clear="all" />
<% end %>
</div>
</div>
<script type="text/template" id="template-landing-comment">
<div class="avatar-small mr10">
<img src="{avatar_url}" alt="" />
<div user-id="{user_id}" hoveraction="{hoverAction}" class="avatar-small mr10">
<img src="{avatar_url}" alt="" />
</div>
<div class="w80 left p10 lightgrey mt10">
<a href="#">{name}</a>&nbsp;{comment}
<br />
<div class="f12 grey mt5">{timeago}</div>
</div>
<br clear="all" />
<div class="w80 left p10 lightgrey mt10 comment-text">
<a user-id="{user_id}" hoveraction="{hoverAction}">{name}</a>&nbsp;{comment}
<br />
<div class="f12 grey mt5 comment-timestamp">{timeago}</div>
</div>
<br clear="all" />
</script>

View File

@ -2,8 +2,9 @@ unless $rails_rake_task
JamWebEventMachine.start
if APP_CONFIG.websocket_gateway_enable && !$rails_rake_task
if APP_CONFIG.websocket_gateway_enable && !$rails_rake_task && ENV['NO_WEBSOCKET_GATEWAY'] != '1'
current = Thread.current
Thread.new do
JamWebsockets::Server.new.run(
:port => APP_CONFIG.websocket_gateway_port,
@ -11,8 +12,10 @@ unless $rails_rake_task
:connect_time_stale => APP_CONFIG.websocket_gateway_connect_time_stale,
:connect_time_expire => APP_CONFIG.websocket_gateway_connect_time_expire,
:rabbitmq_host => APP_CONFIG.rabbitmq_host,
:rabbitmq_port => APP_CONFIG.rabbitmq_port)
:rabbitmq_port => APP_CONFIG.rabbitmq_port,
:calling_thread => current)
end
Thread.stop
end
end

View File

@ -63,8 +63,6 @@ SampleApp::Application.routes.draw do
match '/events/:slug', to: 'events#show', :via => :get, :as => 'event'
match '/video/dialog/:id', to: 'videos#show', :via => :get
match '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
# temporarily allow for debugging--only allows admini n
@ -100,6 +98,8 @@ SampleApp::Application.routes.draw do
# vanilla forums sso
match '/forums/sso', to: 'vanilla_forums#authenticate'
# admin-only page to control settings
match '/extras/settings', to: 'extras#settings'
scope '/corp' do
# about routes

296
web/public/maintenance.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,12 +4,6 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
let(:user) { FactoryGirl.create(:user) }
before(:each) do
@ -92,7 +86,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do
before(:each) do
fill_in "first_name", with: "Bobby"
fill_in "last_name", with: "Toes"
find('input[name=subscribe_email]').set(false)
uncheck('subscribe_email')
find("#account-edit-profile-submit").trigger(:click)
end

View File

@ -16,16 +16,16 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do
#end
end
let(:fan) { FactoryGirl.create(:fan) }
let(:user) { FactoryGirl.create(:user) }
let(:finder) { FactoryGirl.create(:user) }
before(:each) do
UserMailer.deliveries.clear
navigate_band_setup
end
def navigate_band_setup
sign_in_poltergeist(user)
def navigate_band_setup login=user
sign_in_poltergeist(login)
wait_until_curtain_gone
find('div.homecard.profile').trigger(:click)
find('#profile-bands-link').trigger(:click)
@ -33,26 +33,143 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do
expect(page).to have_selector('#band-setup-title')
end
it "have validation errors shown, but then can navigate past and eventually save" do
find('#btn-band-setup-next').trigger(:click)
find('#tdBandName .error-text li', text: "can't be blank")
find('#tdBandBiography .error-text li', text: "can't be blank")
find('#tdBandGenres .error-text li', text: "At least 1 genre is required.")
def complete_band_setup_form(band, biography, params={})
navigate_band_setup unless URI.parse(current_url).fragment == '/band/setup/new'
params['band-name'] ||= band || "Default band name"
params['band-biography'] ||= biography || "Default band biography"
within('#band-setup-form') do
fill_in "band-name", with: "The Band"
fill_in "band-biography", with: "Biography"
params.each do |field, value|
fill_in field, with: "#{value}"
end
first('#band-genres input[type="checkbox"]').trigger(:click)
end
sleep 1 # work around race condition
find('#btn-band-setup-next').trigger(:click)
find('h2', text: 'Step 2: Add Band Members')
find('#btn-band-setup-save').trigger(:click)
find('#band-profile-name', text: "The Band")
find('#band-profile-biography', text: "Biography")
end
context "band profile - new band setup" do
it "displays 'Set up your band' link to user" do
sign_in_poltergeist user
view_profile_of user
find('#profile-bands-link').trigger(:click)
expect(page).to have_selector('#band-setup-link')
end
it "does not display band setup link when viewed by other user" do
in_client(fan) do
sign_in_poltergeist fan
view_profile_of user
find('#profile-bands-link').trigger(:click)
expect(page).to_not have_selector('#band-setup-link')
end
end
it "indicates required fields and user may eventually complete" do
navigate_band_setup
find('#btn-band-setup-next').trigger(:click)
expect(page).to have_selector('#tdBandName .error-text li', text: "can't be blank")
expect(page).to have_selector('#tdBandBiography .error-text li', text: "can't be blank")
expect(page).to have_selector('#tdBandGenres .error-text li', text: "At least 1 genre is required.")
complete_band_setup_form("Band name", "Band biography")
expect(page).to have_selector('#band-profile-name', text: "Band name")
expect(page).to have_selector('#band-profile-biography', text: "Band biography")
end
it "limits genres to 3" do
navigate_band_setup
within('#band-setup-form') do
fill_in 'band-name', with: "whatever"
fill_in 'band-biography', with: "a good story"
all('#band-genres input[type="checkbox"]').each_with_index do |cb, i|
cb.trigger(:click) unless i > 3
end
end
sleep 1
find('#btn-band-setup-next').trigger(:click)
expect(page).to have_selector('#tdBandGenres .error-text li', text: "No more than 3 genres are allowed.")
end
it "handles max-length field input" do
pending "update this after VRFS-1610 is resolved"
max = {
name: 1024,
bio: 4000,
website: 1024 # unsure what the max is, see VRFS-1610
}
navigate_band_setup
band_name = 'a'*(max[:name] + 1)
band_bio = 'b'*(max[:bio] + 1)
band_website = 'c'*(max[:website] + 1)
complete_band_setup_form(band_name, band_bio, 'band-website' => band_website)
expect(page).to have_selector('#band-profile-name', text: band_name.slice(0, max[:name]))
expect(page).to have_selector('#band-profile-biography', text: band_bio.slice(0, max[:bio]))
end
it "handles special characters in text fields" do
pending "update this after VRFS-1609 is resolved"
navigate_band_setup
band_name = garbage(3) + ' ' + garbage(50)
band_bio = garbage(500)
band_website = garbage(500)
complete_band_setup_form(band_name, band_bio, 'band-website' => band_website)
expect(page).to have_selector('#band-profile-name', text: band_name)
expect(page).to have_selector('#band-profile-biography', text: band_bio)
end
it "another user receives invite notification during Band Setup"
end
context "about view" do
it "displays the band's information to another user"
#photo
#name
#website address
#country, state, city
#biography/description
#genres chosen
#number of followers, recordings, sessions
#actions: follow button
it "allows a user to follow the band"
end
context "members view" do
it "photo and name links to the musician's profile page"
it "displays photo, name, location, instruments played"
it "displays a hover bubble containing more info on musician"
it "displays any pending band invitations when viewed by current band member"
end
context "history view" do
it "shows public info"
it "does not show private info to non-band user"
it "shows private info to band user"
end
context "social view" do
it "displays musicians and fans who follow band"
end
context "band profile - editing" do
it "about page shows the current band's info when 'Edit Profile' is clicked"
it "members page shows 'Edit Members' button and user can remove member"
it "non-member cannot Edit Profile"
it "non-member cannot Edit Members"
end
it "band shows up in sidebar search result"
end

View File

@ -4,11 +4,6 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
@ -138,30 +133,34 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
end
end
describe "delete notification" do
describe "user no unseen notifications" do
describe "notification occurs in realtime" do
before(:each) do
User.delete_all
end
describe "sidebar is open" do
describe "user can see notifications" do
it "stays deactivated" do
it "while notification panel closed" do
# we should see the count go to 1, but once the notification is accepted, which causes it to delete,
# we should see the count go back down to 0.
end
end
in_client(user) do
sign_in_poltergeist(user)
end
describe "user can not see notifications" do
describe "with dialog open" do
it "temporarily activates" do
in_client(user2) do
sign_in_poltergeist(user2)
find_musician(user)
find(".result-list-button-wrapper[data-musician-id='#{user.id}'] .search-m-friend").trigger(:click)
end
end
end
in_client(user) do
badge = find("#{NOTIFICATION_PANEL} .badge", text: '1')
badge['class'].include?('highlighted').should == true
describe "with document blurred" do
it "temporarily activates" do
find('#notification #ok-button', text: 'ACCEPT').trigger(:click)
end
end
end
badge = find("#{NOTIFICATION_PANEL} .badge", text: '0')
badge['class'].include?('highlighted').should == false
end
end
end

View File

@ -0,0 +1,117 @@
require 'spec_helper'
# tests what happens when the websocket connection goes away
describe "Reconnect", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
before(:all) do
User.delete_all
end
before(:each) do
emulate_client
end
it "websocket connection is down on initial connection" do
FactoryGirl.create(:friendship, :user => user1, :friend => user2)
FactoryGirl.create(:friendship, :user => user2, :friend => user1)
Rails.application.config.stub(:websocket_gateway_uri).and_return('ws://localhost:99/websocket') # bogus port
sign_in_poltergeist(user1, validate: false)
page.should have_selector('.no-websocket-connection')
find('.homecard.createsession').trigger(:click)
find('h1', text:'create session')
find('#btn-create-session').trigger(:click)
find('#notification h2', text: 'Not Connected') # get notified you can't go to create session
page.evaluate_script('window.history.back()')
find('.homecard.findsession').trigger(:click)
find('#notification h2', text: 'Not Connected') # get notified you can't go to find session
find('h2', text: 'create session') # and be back on home screen
find('.homecard.feed').trigger(:click)
find('h1', text:'feed')
page.evaluate_script('window.history.back()')
find('.homecard.musicians').trigger(:click)
find('h1', text:'musicians')
page.evaluate_script('window.history.back()')
find('.homecard.profile').trigger(:click)
find('h1', text:'profile')
page.evaluate_script('window.history.back()')
find('.homecard.account').trigger(:click)
find('h1', text:'account')
page.evaluate_script('window.history.back()')
initiate_text_dialog user2
find('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
end
it "websocket goes down on home page" do
sign_in_poltergeist(user1)
5.times do
close_websocket
# we should see indication that the websocket is down
page.should have_selector('.no-websocket-connection')
# but.. after a few seconds, it should reconnect on it's own
page.should_not have_selector('.no-websocket-connection')
end
# then verify we can create a session
create_join_session(user1, [user2])
formal_leave_by user1
# websocket goes down while chatting
in_client(user1) do
initiate_text_dialog user2
# normal, happy dialog
page.should_not have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
close_websocket
# dialog-specific disconnect should show
page.should have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
# and generic disconnect
page.should have_selector('.no-websocket-connection')
# after a few seconds, the page should reconnect on it's own
page.should_not have_selector('span.disconnected-msg', text: 'DISCONNECTED FROM SERVER')
page.should_not have_selector('.no-websocket-connection')
end
end
it "websocket goes down on session page" do
create_session(creator: user1)
5.times do
close_websocket
# we should see indication that the websocket is down
page.should have_selector('h2', text: 'Disconnected from Server')
# 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')
end
end
end

View File

@ -12,12 +12,12 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
before(:each) do
MusicSessionHistory.delete_all
sign_in_poltergeist(user)
end
let (:claimed_recording) { FactoryGirl.create(:claimed_recording) }
it "should render comments" do
pending "weird error"
recording = ClaimedRecording.first
comment = "test comment"
@ -31,10 +31,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
# (1) Test a user creating a comment and ensure it displays.
# comment body
find('div', text: comment)
find('div.comment-text', text: comment)
# timestamp
find('div', text: timestamp)
find('div.comment-timestamp', text: timestamp)
# (2) Test a user visiting a landing page with an existing comment.
@ -42,9 +42,9 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
visit url
# comment body
find('div', text: comment)
find('div.comment-text', text: comment)
# timestamp
find('div', text: timestamp)
find('div.comment-timestamp', text: timestamp)
end
end

View File

@ -14,7 +14,6 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
end
it "should render comments" do
pending "weird error"
msh = MusicSessionHistory.first
comment = "test comment"
@ -28,10 +27,10 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
find('#btnPostComment').trigger(:click)
# comment body
find('div', text: comment)
find('div.comment-text', text: comment)
# timestamp
find('div', text: timestamp)
find('div.comment-timestamp', text: timestamp)
# (2) Test a user visiting a landing page with an existing comment.
@ -39,9 +38,9 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do
visit url
# comment body
find('div', text: comment)
find('div.comment-text', text: comment)
# timestamp
find('div', text: timestamp)
find('div.comment-timestamp', text: timestamp)
end
end

View File

@ -4,13 +4,8 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
before(:each) do
UserMailer.deliveries.clear
@ -36,5 +31,19 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr
end
end
describe "panel behavior" do
it "search, then click notifications" do
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be_false
site_search(user2.name, validate: user2)
open_notifications
find("#sidebar-notification-list li[notification-id='#{notification.id}']")
end
end
end

View File

@ -1,3 +1,11 @@
# temporary to debug failing tests on the build server
def bputs(msg)
if ENV["BUILD_PUTS"] == "1"
puts msg
end
end
require 'simplecov'
require 'rubygems'
#require 'spork'
@ -7,18 +15,27 @@ require 'omniauth'
ENV["RAILS_ENV"] ||= 'test'
bputs "before activerecord load"
require 'active_record'
require 'action_mailer'
require 'jam_db'
require "#{File.dirname(__FILE__)}/spec_db"
bputs "before db_config load"
# recreate test database and migrate it
db_config = YAML::load(File.open('config/database.yml'))["test"]
# initialize ActiveRecord's db connection\
bputs "before recreate db"
SpecDb::recreate_database(db_config)
bputs "before connect db"
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
bputs "before load jam_ruby"
require 'jam_ruby'
# uncomment this to see active record logs
@ -34,13 +51,22 @@ tests_started = false
Thread.new {
sleep 30
if ENV['BUILD_NUMBER']
sleep 240
else
sleep 30
end
unless tests_started
bputs "tests are hung. exiting..."
puts "tests are hung. exiting..."
exit! 20
end
}
bputs "before load websocket server"
current = Thread.current
Thread.new do
ActiveRecord::Base.connection.disconnect!
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
@ -52,30 +78,47 @@ Thread.new do
:connect_time_stale => 2,
:connect_time_expire => 5,
:rabbitmq_host => 'localhost',
:rabbitmq_port => 5672)
:rabbitmq_port => 5672,
:calling_thread => current)
rescue Exception => e
puts "websocket-gateway failed: #{e}"
end
end
bputs "before websocket thread wait"
Thread.stop
bputs "before connection reestablish"
ActiveRecord::Base.connection.disconnect!
bputs "before connection reestablishing"
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
#Spork.prefork do
# Loading more in this block will cause your tests to run faster. However,
# if you change any configuration or code from libraries loaded here, you'll
# need to restart spork for it take effect.
# This file is copied to spec/ when you run 'rails generate rspec:install'
bputs "before load environment"
begin
require File.expand_path("../../config/environment", __FILE__)
rescue => e
bputs "exception in load environment"
bputs "e: #{e}"
end
bputs "before loading rails"
require 'rspec/rails'
bputs "before connection autorun"
require 'rspec/autorun'
bputs "before load capybara"
require 'capybara'
require 'capybara/rspec'
require 'capybara-screenshot/rspec'
bputs "before load poltergeist"
require 'capybara/poltergeist'
bputs "before register capybara"
Capybara.register_driver :poltergeist do |app|
driver = Capybara::Poltergeist::Driver.new(app, { debug: false, phantomjs_logger: File.open('log/phantomjs.out', 'w') })
end

View File

@ -9,7 +9,10 @@ def site_search(text, options = {})
fill_in "search-input", with: text
end
find('#sidebar-search-expand').trigger(:click) if options[:expand]
if options[:expand]
page.driver.execute_script("jQuery('#searchForm').submit()")
find('h1', text:'search results')
end
end
# goes to the musician tile, and tries to find a musician
@ -37,6 +40,16 @@ def find_musician(user)
raise "unable to find musician #{user}"
end
def initiate_text_dialog(user)
# verify that the chat window is grayed out
site_search(user.first_name, expand: true)
find("#search-results a[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name).hover_intent
find('#musician-hover #btnMessage').trigger(:click)
find('h1', text: 'conversation with ' + user.name)
end
# sends a text message in the chat interface.
def send_text_message(msg, options={})
find('#text-message-dialog') # assert that the dialog is showing already
@ -62,6 +75,7 @@ def open_notifications
find("#{NOTIFICATION_PANEL} .panel-header").trigger(:click)
end
def hover_intent(element)
element.hover
element.hover
@ -95,4 +109,8 @@ end
# simulates focus event on window
def window_focus
page.evaluate_script(%{window.jQuery(window).trigger('focus');})
end
def close_websocket
page.evaluate_script("window.JK.JamServer.close(true)")
end

View File

@ -4,10 +4,19 @@ include ApplicationHelper
module Capybara
module Node
class Element
def attempt_hover
begin
hover
rescue => e
end
end
def hover_intent
hover
hover
hover
sleep 0.3
attempt_hover
sleep 0.3
attempt_hover
end
end
end
@ -86,13 +95,19 @@ def set_cookie(k, v)
end
end
def sign_in_poltergeist(user)
def sign_in_poltergeist(user, options = {})
validate = options[:validate]
validate = true if validate.nil?
visit signin_path
fill_in "Email Address:", with: user.email
fill_in "Password:", with: user.password
click_button "SIGN IN"
wait_until_curtain_gone
# presence of this means websocket gateway is not working
page.should have_no_selector('.no-websocket-connection') if validate
end
@ -400,6 +415,13 @@ def assert_all_tracks_seen(users=[])
end
end
def view_profile_of user
id = user.kind_of?(JamRuby::User) ? user.id : user
# assume some user signed in already (allows reuse in multi-user tests)
visit "/client#/profile/#{id}"
wait_until_curtain_gone
end
def show_user_menu
page.execute_script("$('ul.shortcuts').show()")
#page.execute_script("JK.UserDropdown.menuHoverIn()")
@ -411,8 +433,19 @@ def wait_for_easydropdown(select)
end
# defaults to enter key (13)
def send_key(keycode = 13)
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); $('#search-input').trigger(e);"
def send_key(selector, keycode = 13)
keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
page.driver.execute_script(keypress_script)
end
def special_characters
["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]
end
def garbage length
output = ''
length.times { output << special_characters.sample }
output.gsub!(/<[\/|!|\?]/, '/<') # security risk -- avoids inputting tags until VRFS-1609 resolved
output.slice(0, length)
end

View File

@ -35,7 +35,7 @@ gem 'aasm', '3.0.16'
gem 'carrierwave'
gem 'devise'
gem 'postgres-copy'
gem 'aws-sdk', '1.29.1'
gem 'aws-sdk' #, '1.29.1'
gem 'bugsnag'
gem 'postgres_ext'
gem 'resque'

View File

@ -12,13 +12,13 @@ module JamWebsockets
end
def run(options={})
host = "0.0.0.0"
port = options[:port]
connect_time_stale = options[:connect_time_stale].to_i
connect_time_expire = options[:connect_time_expire].to_i
rabbitmq_host = options[:rabbitmq_host]
rabbitmq_port = options[:rabbitmq_port].to_i
calling_thread = options[:calling_thread]
@log.info "starting server #{host}:#{port} staleness_time=#{connect_time_stale}; reconnect time = #{connect_time_expire}, rabbitmq=#{rabbitmq_host}:#{rabbitmq_port}"
@ -36,6 +36,7 @@ module JamWebsockets
start_connection_expiration(expire_time)
start_connection_flagger(connect_time_stale)
start_websocket_listener(host, port, options[:emwebsocket_debug])
calling_thread.wakeup if calling_thread
end
# if you don't do this, the app won't exit unless you kill -9
@ -55,6 +56,7 @@ module JamWebsockets
@log.info "new client #{ws}"
@router.new_client(ws)
end
@log.debug("started websocket")
end
def start_connection_expiration(stale_max_time)