diff --git a/app/assets/javascripts/AAB_message_factory.js b/app/assets/javascripts/AAB_message_factory.js new file mode 100644 index 000000000..b3aaaeefe --- /dev/null +++ b/app/assets/javascripts/AAB_message_factory.js @@ -0,0 +1,83 @@ +/* + Message builder for communicating over the websocket + */ +(function(context, $) { + + context.JK = context.JK || {}; + + var msg = context.JK.MessageType = { + LOGIN : "LOGIN", + LOGIN_ACK : "LOGIN_ACK", + LOGIN_MUSIC_SESSION : "LOGIN_MUSIC_SESSION", + LOGIN_MUSIC_SESSION_ACK : "LOGIN_MUSIC_SESSION_ACK", + USER_JOINED_MUSIC_SESSION : "USER_JOINED_MUSIC_SESSION", + LEAVE_MUSIC_SESSION : "LEAVE_MUSIC_SESSION", + LEAVE_MUSIC_SESSION_ACK : "LEAVE_MUSIC_SESSION_ACK", + HEARTBEAT : "HEARTBEAT", + TEST_SESSION_MESSAGE : "TEST_SESSION_MESSAGE", + PING_REQUEST : "PING_REQUEST", + PING_ACK : "PING_ACK", + PEER_MESSAGE : "PEER_MESSAGE", + SERVER_GENERIC_ERROR : "SERVER_GENERIC_ERROR", + SERVER_REJECTION_ERROR : "SERVER_REJECTION_ERROR" + }; + + var route_to = context.JK.RouteToPrefix = { + CLIENT : "client", + SESSION : "session", + SERVER : "server", + USER : "user" + }; + + var factory = {}; + + function client_container(type, target, inner) { + var type_field = type.toLowerCase(); + var body = { "type" : type, "route_to" : target}; + body[type_field] = inner; + return body; + } + + function route_to_client(client_id) { + return route_to.CLIENT + ":" + client_id; + } + + function route_to_session(session_id) { + return route_to.SESSION + ":" + session_id; + } + + // ping the provided client_id + factory.ping = function(client_id) { + var data = {}; + return client_container(msg.PING_REQUEST, route_to_client(client_id), data); + }; + + // create a login message using user/pass + factory.login_with_user_pass = function(username, password) { + var login = { username : username , password : password }; + return client_container(msg.LOGIN, route_to.SERVER, login); + }; + + // create a login message using token (a cookie or similiar) + factory.login_with_token = function(token) { + var login = { token : token }; + return client_container(msg.LOGIN, route_to.SERVER, login); + }; + + // create a music session login message + factory.login_music_session = function(music_session) { + var login_music_session = { music_session : music_session }; + return client_container(msg.LOGIN_MUSIC_SESSION, route_to.SERVER, login_music_session); + }; + + // client-to-client message + factory.client_p2p_message = function(sender_client_id, receiver_client_id, message) { + var peer_message = { "message" : message }; + var result = client_container(msg.PEER_MESSAGE, route_to_client(receiver_client_id), peer_message); + result.from = sender_client_id; + return result; + }; + + context.JK.MessageFactory = factory; + +})(window, jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/JamServer.js b/app/assets/javascripts/JamServer.js index 0c7bac3cd..68980405d 100644 --- a/app/assets/javascripts/JamServer.js +++ b/app/assets/javascripts/JamServer.js @@ -4,19 +4,21 @@ // // The wrapper around the web-socket connection to the server (function(context, $) { - var server = {}; + context.JK = context.JK || {}; + var logger = context.JK.logger; + var msg_factory = context.JK.MessageFactory; // Let socket.io know where WebSocketMain.swf is WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf"; + var server = {}; server.socket = {}; - server.singedIn = false; + server.signedIn = false; + server.clientID = ""; + server.publicIP = ""; server.dispatchTable = {}; - // TODO: Create the message factory here - //server.messageFactory = context.message_factory; - server.registerMessageCallback = function(messageType, callback) { if (server.dispatchTable[messageType] === undefined) { server.dispatchTable[messageType] = []; @@ -42,11 +44,7 @@ }; server.connect = function() { - server.registerMessageCallback( - context.Messages.LOGIN_ACK, - function() { server.signedIn = true; } - ); - + logger.log("server.connect"); server.socket = new WebSocket(gon.websocket_gateway_uri); server.socket.onopen = server.onOpen; server.socket.onmessage = server.onMessage; @@ -54,25 +52,24 @@ }; server.onOpen = function() { - logger.log('server.onOpen'); + logger.log("server.onOpen"); var token, loginMessage; - token = $.cookie("remember_token"); - loginMessage = context.message_factory.login_with_token(token); - + loginMessage = msg_factory.login_with_token(token); server.send(loginMessage); }; server.onMessage = function(e) { - logger.log('server.onMessage'); + logger.log("server.onMessage"); var message = JSON.parse(e.data), messageType = message.type.toLowerCase(), payload = message[messageType], callbacks = server.dispatchTable[message.type]; if (callbacks !== undefined) { - for(var i = callbacks.length; i--;) { - callbacks[i](messageType, payload); + var len = callbacks.length; + for(var i = 0; i < len; i++) { + callbacks[i](message, payload); } } else { @@ -82,13 +79,24 @@ server.onClose = function() { logger.log("Socket to server closed."); + + if (context.jamClient !== undefined) + { + context.jamClient.connected = false; + } + // TODO: reconnect }; server.send = function(message) { var jsMessage = JSON.stringify(message); - logger.log('server.send(' + jsMessage + ')'); - server.socket.send(jsMessage); + 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) { @@ -100,10 +108,50 @@ return; } - loginMessage = context.message_factory.login_jam_session(sessionId); + loginMessage = msg_factory.login_jam_session(sessionId); server.send(loginMessage); }; - context.JamServer = server; + server.sendP2PMessage = function(receiver_id, message) { + logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message); + var outgoing_msg = msg_factory.client_p2p_message(server.clientID, receiver_id, message); + server.send(outgoing_msg); + }; + + context.JK.JamServer = server; + + // Message callbacks + server.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, function(header, payload) { + server.signedIn = true; + server.clientID = payload.client_id; + server.publicIP = payload.public_ip; + + if (context.jamClient !== undefined) + { + 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); + } + }); + + server.registerMessageCallback(context.JK.MessageType.LOGIN_MUSIC_SESSION_ACK, function(header, payload) { + if (context.jamClient !== undefined) + { + // TODO: modify the LOGIN_MUSIC_SESSION_ACK message to include session_id + context.jamClient.JoinSession({ sessionID : payload.session_id}); + } + }); + + // Callbacks from jamClient + if (context.jamClient !== undefined) + { + context.jamClient.SendP2PMessage.connect(server.sendP2PMessage); + } })(window, jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/createSession.js b/app/assets/javascripts/createSession.js index a85dc8c40..9399d1229 100644 --- a/app/assets/javascripts/createSession.js +++ b/app/assets/javascripts/createSession.js @@ -34,6 +34,7 @@ Message from Seth on sequence for creating/joining sessions: function(response) { var newSessionId = response.id; self.location = '#/session/' + newSessionId; + context.JK.joinMusicSession(newSessionId); } ); evt.preventDefault(); diff --git a/app/assets/javascripts/joinSession.js b/app/assets/javascripts/joinSession.js new file mode 100644 index 000000000..ae1e372a4 --- /dev/null +++ b/app/assets/javascripts/joinSession.js @@ -0,0 +1,34 @@ +(function(context,$) { + + context.JK = context.JK || {}; + + context.JK.joinMusicSession = function(session_id) { + var logger = context.JK.logger; + var server = context.JK.JamServer; + var client = context.jamClient; + + if (!server.signedIn) + { + logger.log("Can't join a session because the client is not connected."); + return; + } + + logger.log("Joining session: " + session_id); + + var data = { client_id: server.clientID, ip_address: server.publicIP }; + var url = "/api/sessions/" + session_id + "/participants"; + $.ajax({ + type: "POST", + url: url, + data: data + }).done( + function(response) { + if (client !== undefined) + { + client.JoinSession({ sessionID: session_id }); + } + } + ); + }; + + })(window,jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/message_factory.js b/app/assets/javascripts/message_factory.js deleted file mode 100644 index d68109037..000000000 --- a/app/assets/javascripts/message_factory.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - Message builder for communicating over the websocket - */ -(function(context) { - - var CLIENT_TARGET = "client"; - var SERVER_TARGET = "server"; - var SESSION_TARGET_PREFIX = "session:"; - var USER_TARGET_PREFIX = "user:"; - var CLIENT_TARGET_PREFIX = "client:"; - - var msg = context.Messages = { - LOGIN : "LOGIN", - LOGIN_ACK : "LOGIN_ACK", - LOGIN_MUSIC_SESSION : "LOGIN_MUSIC_SESSION", - LOGIN_MUSIC_SESSION_ACK : "LOGIN_MUSIC_SESSION_ACK", - USER_JOINED_MUSIC_SESSION : "USER_JOINED_MUSIC_SESSION", - LEAVE_MUSIC_SESSION : "LEAVE_MUSIC_SESSION", - LEAVE_MUSIC_SESSION_ACK : "LEAVE_MUSIC_SESSION_ACK", - HEARTBEAT : "HEARTBEAT", - TEST_SESSION_MESSAGE : "TEST_SESSION_MESSAGE", - PING_REQUEST: "PING_REQUEST", - PING_ACK: "PING_ACK", - SERVER_GENERIC_ERROR : "SERVER_GENERIC_ERROR", - SERVER_REJECTION_ERROR : "SERVER_REJECTION_ERROR" - }; - - var message_factory = {}; - - function client_container(type, target, inner) { - var type_field = type.toLowerCase(); - var body = { "type" : type, "route_to" : target}; - body[type_field] = inner; - return body; - } - - // ping the provided client_id - message_factory.ping = function(client_id) { - var data = {}; - var target = CLIENT_TARGET_PREFIX + client_id; - return client_container(msg.PING_REQUEST, target, data); - }; - - // create a login message using user/pass - message_factory.login_with_user_pass = function(username, password) { - var login = { username : username , password : password }; - return client_container(msg.LOGIN, SERVER_TARGET, login); - }; - - // create a login message using token (a cookie or similiar) - message_factory.login_with_token = function(token) { - var login = { token : token }; - return client_container(msg.LOGIN, SERVER_TARGET, login); - }; - - // create a music session login message - message_factory.login_music_session = function(music_session) { - var login_music_session = { music_session : music_session }; - return client_container(msg.LOGIN_MUSIC_SESSION, SERVER_TARGET, login_music_session); - }; - - window.message_factory = message_factory; - -})(window); \ No newline at end of file diff --git a/app/assets/javascripts/messaging.js b/app/assets/javascripts/messaging.js index 4f804a13f..b540d596d 100644 --- a/app/assets/javascripts/messaging.js +++ b/app/assets/javascripts/messaging.js @@ -8,22 +8,22 @@ context.JK = context.JK || {}; context.JK.Messaging = function(app) { - if ("undefined" === typeof(context.JamServer)) + if ("undefined" === typeof(context.JK.JamServer)) return; // Alias some of the globals for less typing. var logger = context.JK.logger; - var server = context.JamServer; - var messages = context.Messages; - var msg_factory = context.message_factory; + var server = context.JK.JamServer; + var messages = context.JK.MessageType; + var msg_factory = context.JK.MessageFactory; var myClientId = null; var myPingTimer = null; var pingCount = 0; var maxPings = 5; - function logMessage(messageType, payload) { - logger.debug(messageType + ": " + JSON.stringify(payload)); + function logMessage(header, payload) { + logger.debug(header.type + ": " + JSON.stringify(payload)); } function pingMyself() { @@ -38,8 +38,8 @@ } } - function loggedIn(messageType, payload) { - logger.debug('Logged In handler: ' + messageType + ':' + JSON.stringify(payload)); + function loggedIn(header, payload) { + logger.debug('Logged In handler: ' + header.type + ':' + JSON.stringify(payload)); myClientId = payload.client_id; myPingTimer = context.setInterval(pingMyself, 1000); } @@ -54,8 +54,8 @@ */ this.register = function() { for (var message in messages) { - logger.debug("registering logger.debug: " + message); - context.JamServer.registerMessageCallback(message, logMessage); + logger.debug("registering " + message); + server.registerMessageCallback(message, logMessage); } registerLoginPinger(); diff --git a/app/controllers/api_users_controller.rb b/app/controllers/api_users_controller.rb index 2aa4670b9..86a7d3638 100644 --- a/app/controllers/api_users_controller.rb +++ b/app/controllers/api_users_controller.rb @@ -1,7 +1,8 @@ class ApiUsersController < ApplicationController before_filter :signed_in_user, only: [:index, :edit, :update, :delete, - :friend_request_index, :friend_request_create, + :friend_request_index, :friend_request_show, + :friend_request_create, :friend_request_update, :friend_index, :friend_destroy] respond_to :json @@ -10,35 +11,61 @@ class ApiUsersController < ApplicationController @users = User.paginate(page: params[:page]) end + def show + @user = User.find(params[:id]) + end + def create @user = User.new() + @user.email = params[:email] @user.creator = current_user @user.description = params[:description] @user.save respond_with @user, responder: ApiResponder, :location => api_user_detail_url(@user) end - def show - @user = User.find(params[:id]) - end - def delete @user = User.find(params[:id]) @user.delete - respond_with @user, responder: ApiResponder end def friend_request_index - end - - def friend_request_create + # get all outgoing and incoming friend requests + @friend_requests = FriendRequest.where("(friend_id='#{params[:id]}' OR user_id='#{params[:id]}') AND accepted is null") end def friend_request_show + @friend_request = FriendRequest.find(params[:id]) + end + + def friend_request_create + @friend_request = FriendRequest.new() + @friend_request.user_id = params[:user_id] + @friend_request.friend_id = params[:friend_id] + @friend_request.save + respond_with @friend_request, responder: ApiResponder, :location => api_friend_request_detail_url(@friend_request) end def friend_request_update + @friend_request = FriendRequest.find(params[:id]) + @friend_request.accepted = params[:accepted] + @friend_request.save + + # create both records for this friendship + if @friend_request.accepted? + @friendship = Friendship.new() + @friendship.user_id = @friend_request.user_id + @friendship.friend_id = @friend_request.friend_id + @friendship.save + + @friendship = Friendship.new() + @friendship.user_id = @friend_request.friend_id + @friendship.friend_id = @friend_request.user_id + @friendship.save + end + + respond_with @friend_request, responder: ApiResponder end def friend_index @@ -47,7 +74,9 @@ class ApiUsersController < ApplicationController end def friend_destroy + # clean up both records representing this "friendship" JamRuby::Friendship.delete_all "(user_id = '#{params[:id]}' AND friend_id = '#{params[:friend_id]}') OR (user_id = '#{params[:friend_id]}' AND friend_id = '#{params[:id]}')" + respond_with responder: ApiResponder end end \ No newline at end of file diff --git a/app/views/api_users/friend_index.rabl b/app/views/api_users/friend_index.rabl index 339c8928b..b9a4cc999 100644 --- a/app/views/api_users/friend_index.rabl +++ b/app/views/api_users/friend_index.rabl @@ -1,3 +1,3 @@ object @user.friends -attributes :id, :name, :online \ No newline at end of file +attributes :id, :name, :email, :online \ No newline at end of file diff --git a/app/views/api_users/friend_request_index.rabl b/app/views/api_users/friend_request_index.rabl new file mode 100644 index 000000000..5d5261574 --- /dev/null +++ b/app/views/api_users/friend_request_index.rabl @@ -0,0 +1,3 @@ +object @friend_requests + +extends "api_users/friend_request_show" \ No newline at end of file diff --git a/app/views/api_users/friend_request_show.rabl b/app/views/api_users/friend_request_show.rabl new file mode 100644 index 000000000..74daf6176 --- /dev/null +++ b/app/views/api_users/friend_request_show.rabl @@ -0,0 +1,3 @@ +object @friend_request + +attributes :id, :user_id, :friend_id, :accepted, :created_at \ No newline at end of file diff --git a/app/views/clients/index.html.erb b/app/views/clients/index.html.erb index 8f5789e41..9739fd67b 100644 --- a/app/views/clients/index.html.erb +++ b/app/views/clients/index.html.erb @@ -454,7 +454,8 @@ var messaging = new JK.Messaging(jk); messaging.register(); - window.JamServer.connect(); + var jam_server = JK.JamServer; + jam_server.connect(); }) diff --git a/config/routes.rb b/config/routes.rb index d7dbc0787..2b4a66d46 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,6 +7,7 @@ SampleApp::Application.routes.draw do resources :users resources :music_sessions + resources :friend_requests resources :sessions, only: [:new, :create, :destroy] @@ -34,16 +35,16 @@ SampleApp::Application.routes.draw do # users match '/users' => 'api_users#index', :via => :get - match '/users' => 'api_users#create', :via => :post match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail' - match '/users/:id' => 'api_users#edit', :via => :put + match '/users' => 'api_users#create', :via => :post + match '/users/:id' => 'api_users#update', :via => :put match '/users/:id' => 'api_users#destroy', :via => :delete # friend requests match '/users/:id/friend_requests' => 'api_users#friend_request_index', :via => :get - match '/users/:id/friend_requests' => 'api_users#friend_request_create', :via => :post - match '/users/:id/friends/:friend_request_id' => 'api_users#friend_request_show', :via => :get, :as => 'api_user_friend_request_detail' - match '/users/:id/friends/:friend_request_id' => 'api_users#friend_request_update', :via => :put + match '/friend_requests/:id' => 'api_users#friend_request_show', :via => :get, :as => 'api_friend_request_detail' + match '/friend_requests' => 'api_users#friend_request_create', :via => :post + match '/friend_requests/:id' => 'api_users#friend_request_update', :via => :put # friends match '/users/:id/friends' => 'api_users#friend_index', :via => :get