diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 1111f7b20..b9a7aeba0 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -671,6 +671,7 @@ message ServerGenericError { // but gives the client a chance to know why. message ServerRejectionError { optional string error_msg = 1; + optional string error_code = 2; } // route_to: client diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 57c14a97c..7ece76e28 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -197,8 +197,8 @@ module JamRuby end # create a server rejection error - def server_rejection_error(error_msg) - error = Jampb::ServerRejectionError.new(:error_msg => error_msg) + def server_rejection_error(error_msg, error_code) + error = Jampb::ServerRejectionError.new(:error_msg => error_msg, :error_code => error_code) Jampb::ClientMessage.new( :type => ClientMessage::Type::SERVER_REJECTION_ERROR, diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index b2cf8f678..9cdc68a1c 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1023,6 +1023,11 @@ module JamRuby return end + if connection.locidispid.nil? + @@log.warn("no locidispid for connection's ip_address: #{connection.ip_address}") + return + end + # we don't use a websocket login to update the user's record unless there is no addr if reason == JAM_REASON_LOGIN && last_jam_addr return diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 85fec6da1..040490d5f 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -51,6 +51,7 @@ var $messageContents = null; var $dialog = null; var $templateServerConnection = null; + var $templateNoLogin = null; var $templateDisconnected = null; var $currentDisplay = null; @@ -109,8 +110,10 @@ // we don't show any reconnect dialog on the initial connect; so we have this one-time flag // to cause reconnects in the case that the websocket is down on the initially connect - if (!server.noReconnect && - (initialConnectAttempt || !server.reconnecting)) { + if(server.noReconnect) { + renderLoginRequired(); + } + else if ((initialConnectAttempt || !server.reconnecting)) { server.reconnecting = true; initialConnectAttempt = false; @@ -272,9 +275,13 @@ function serverRejection(header, payload) { logger.warn("server rejected our websocket connection. reason=" + payload.error_msg) - if(payload.error_msg == 'max_user_connections') { + if(payload.error_code == 'max_user_connections') { context.JK.Banner.showAlert("Too Many Connections", "You have too many connections to the server. If you believe this is in error, please contact support.") } + else if(payload.error_code == 'invalid_login' || payload.error_code == 'empty_login') { + logger.debug(payload.error_code + ": no longer reconnecting") + server.noReconnect = true; // stop trying to log in!! + } } /////////////////// @@ -330,6 +337,23 @@ return {}; } + function renderLoginRequired() { + var $inSituContent = $(context._.template($templateNoLogin.html(), buildOptions(), { variable: 'data' })); + $inSituContent.find('a.disconnected-login').click(function() { + var redirectPath= '?redirect-to=' + encodeURIComponent(context.JK.locationPath()); + if(gon.isNativeClient) { + window.location.href = '/signin' + redirectPath; + } + else { + window.location.href = '/' + redirectPath; + } + return false; + }) + $messageContents.empty(); + $messageContents.append($inSituContent); + $inSituBannerHolder.addClass('active'); + return $inSituBannerHolder; + } function renderDisconnected() { var content = null; @@ -523,13 +547,22 @@ connectDeferred = new $.Deferred(); channelId = context.JK.generateUUID(); // create a new channel ID for every websocket connection + var rememberToken = $.cookie("remember_token"); + + if(isClientMode() && !rememberToken) { + server.noReconnect = true; + logger.debug("no login info; shutting down websocket"); + renderLoginRequired(); + connectDeferred.reject(); + return connectDeferred; + } // we will log in one of 3 ways: // browser: use session cookie, and auth with token // native: use session cookie, and use the token // latency_tester: ask for client ID from backend; no token (trusted) var params = { channel_id: channelId, - token: $.cookie("remember_token"), + token: rememberToken, client_type: isClientMode() ? context.JK.clientType() : 'latency_tester', client_id: isClientMode() ? (gon.global.env == "development" ? $.cookie('client_id') : null): context.jamClient.clientID, os: context.JK.GetOSAsString(), @@ -725,6 +758,7 @@ $messageContents = $inSituBannerHolder.find('.message-contents'); $dialog = $('#banner'); $templateServerConnection = $('#template-server-connection'); + $templateNoLogin = $('#template-no-login'); $templateDisconnected = $('#template-disconnected'); if ($inSituBanner.length != 1) { diff --git a/web/app/assets/stylesheets/client/jamServer.css.scss b/web/app/assets/stylesheets/client/jamServer.css.scss index 6eab98e23..0ab091d0e 100644 --- a/web/app/assets/stylesheets/client/jamServer.css.scss +++ b/web/app/assets/stylesheets/client/jamServer.css.scss @@ -45,7 +45,7 @@ } } - .reconnect-progress-msg { + .reconnect-progress-msg, .login-back-in-msg { margin-bottom:10px; } diff --git a/web/app/views/clients/_jamServer.html.haml b/web/app/views/clients/_jamServer.html.haml index 46d9d4c9b..bd3ffc332 100644 --- a/web/app/views/clients/_jamServer.html.haml +++ b/web/app/views/clients/_jamServer.html.haml @@ -9,4 +9,9 @@ %div.reconnect-progress-msg %span.reconnect-before= 'Reconnecting automatically in ' %span.reconnect-countdown= '{{data.countdown}}' - %a.disconnected-reconnect.reconnect-enabled{href:'#'} RECONNECT NOW \ No newline at end of file + %a.disconnected-reconnect.reconnect-enabled{href:'#'} RECONNECT NOW +%script{type: 'text/template', id: 'template-no-login'} + %div + %div.login-back-in-msg + %span= 'You will need to log back in' + %a.disconnected-login{href:'#'} GO TO SIGN IN \ No newline at end of file diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 43debd078..62b8d4f7c 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -120,7 +120,7 @@ console.log("websocket connected") }) .fail(function() { - console.log("websocket failed to connect") + //console.log("websocket failed to connect") }); }) diff --git a/web/app/views/shared/_ga.html.erb b/web/app/views/shared/_ga.html.erb index f22ac3a63..c4fb4d6a1 100644 --- a/web/app/views/shared/_ga.html.erb +++ b/web/app/views/shared/_ga.html.erb @@ -18,9 +18,9 @@ <% if Rails.env == "development" %> ga('create', '<%= Rails.application.config.ga_ua %>', { 'cookieDomain': 'none' }); <% else %> - ga('create', '<%= Rails.application.config.ga_ua %>', 'jamkazam.com'); + ga('create', '<%= Rails.application.config.ga_ua %>', 'jamkazam.com'); <% end %> - + ga('require', 'displayfeatures'); ga('send', 'pageview', { dimension1: '<%= ga_user_level %>', dimension2: '<%= ga_user_type %>' diff --git a/web/config/environments/test.rb b/web/config/environments/test.rb index d71e9d0b3..55df32b20 100644 --- a/web/config/environments/test.rb +++ b/web/config/environments/test.rb @@ -47,7 +47,7 @@ SampleApp::Application.configure do OmniAuth.config.test_mode = true config.websocket_gateway_enable = false - config.websocket_gateway_port = 6769 + config.websocket_gateway_port = 6759 config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket" config.websocket_gateway_connect_time_stale_client = 4 diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index 984180a66..95b6f42e1 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -76,7 +76,7 @@ Thread.new do require 'jam_websockets' begin JamWebsockets::Server.new.run( - :port => 6769, + :port => 6759, :emwebsocket_debug => false, :connect_time_stale_client => 4, :connect_time_expire_client => 6, diff --git a/websocket-gateway/config/application.yml b/websocket-gateway/config/application.yml index fcf36d31c..2d422c9ec 100644 --- a/websocket-gateway/config/application.yml +++ b/websocket-gateway/config/application.yml @@ -15,7 +15,7 @@ development: <<: *defaults test: - port: 6769 + port: 6759 verbose: true rabbitmq_host: localhost rabbitmq_port: 5672 diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index 2815431f2..0f755358e 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -245,7 +245,7 @@ module JamWebsockets @log.info "ending client session deliberately due to malformed client behavior. reason=#{e}" begin # wrap the message up and send it down - error_msg = @message_factory.server_rejection_error(e.to_s) + error_msg = @message_factory.server_rejection_error(e.to_s, e.error_code) send_to_client(client, error_msg) ensure cleanup_client(client) @@ -277,6 +277,7 @@ module JamWebsockets client.trusted = is_trusted client.onopen { |handshake| + # a unique ID for this TCP connection, to aid in debugging client.channel_id = handshake.query["channel_id"] @@ -291,7 +292,7 @@ module JamWebsockets end websocket_comm(client, nil) do - handle_login(client, handshake.query) + handle_login(client, handshake.query, handshake.headers["X-Forwarded-For"]) end } @@ -472,10 +473,10 @@ module JamWebsockets context end - def handle_latency_tester_login(client_id, client_type, client) + def handle_latency_tester_login(client_id, client_type, client, override_ip) # respond with LOGIN_ACK to let client know it was successful - remote_ip = extract_ip(client) + remote_ip = extract_ip(client, override_ip) heartbeat_interval, connection_stale_time, connection_expire_time = determine_connection_times(nil, client_type) latency_tester = LatencyTester.connect({ client_id: client_id, @@ -511,7 +512,7 @@ module JamWebsockets end end - def handle_login(client, options) + def handle_login(client, options, override_ip = nil) username = options["username"] password = options["password"] token = options["token"] @@ -524,7 +525,7 @@ module JamWebsockets @log.info("handle_login: client_type=#{client_type} token=#{token} client_id=#{client_id} channel_id=#{client.channel_id} udp_reachable=#{udp_reachable}") if client_type == Connection::TYPE_LATENCY_TESTER - handle_latency_tester_login(client_id, client_type, client) + handle_latency_tester_login(client_id, client_type, client, override_ip) return end @@ -541,7 +542,7 @@ module JamWebsockets # protect against this user swamping the server if user && Connection.where(user_id: user.id).count >= @max_connections_per_user @log.warn "user #{user.id}/#{user.email} unable to connect due to max_connections_per_user #{@max_connections_per_user}" - raise SessionError, 'max_user_connections' + raise SessionError, 'max_user_connections', 'max_user_connections' end # XXX This logic needs to instead be handled by a broadcast out to all websockets indicating dup @@ -569,7 +570,7 @@ module JamWebsockets client.client_id = client_id client.user_id = user.id if user - remote_ip = extract_ip(client) + remote_ip = extract_ip(client, override_ip) if user @@ -650,7 +651,7 @@ module JamWebsockets send_to_client(client, login_ack) end else - raise SessionError, 'invalid login' + raise SessionError.new('invalid login', 'invalid_login') end end @@ -766,7 +767,7 @@ module JamWebsockets return nil end else - raise SessionError, 'no login data was found in Login message' + raise SessionError.new('no login data was found in Login message', 'empty_login') end end @@ -882,8 +883,8 @@ module JamWebsockets end end - def extract_ip(client) - Socket.unpack_sockaddr_in(client.get_peername)[1] + def extract_ip(client, override_ip) + override_ip || Socket.unpack_sockaddr_in(client.get_peername)[1] end def periodical_flag_connections diff --git a/websocket-gateway/lib/jam_websockets/server.rb b/websocket-gateway/lib/jam_websockets/server.rb index 256c6b0bb..f03ecf11e 100644 --- a/websocket-gateway/lib/jam_websockets/server.rb +++ b/websocket-gateway/lib/jam_websockets/server.rb @@ -27,7 +27,7 @@ module JamWebsockets calling_thread = options[:calling_thread] trust_check = TrustCheck.new(trust_port, options[:cidr]) - @log.info "starting server #{host}:#{port} staleness_time=#{connect_time_stale_client}; reconnect time = #{connect_time_expire_client}, rabbitmq=#{rabbitmq_host}:#{rabbitmq_port}" + @log.info "starting server #{host}:#{port} staleness_time=#{connect_time_stale_client}; reconnect time = #{connect_time_expire_client}, rabbitmq=#{rabbitmq_host}:#{rabbitmq_port} gateway_name=#{gateway_name}" EventMachine.error_handler{|e| @log.error "unhandled error #{e}" diff --git a/websocket-gateway/lib/jam_websockets/session_error.rb b/websocket-gateway/lib/jam_websockets/session_error.rb index 4d8a82166..e321e13fd 100644 --- a/websocket-gateway/lib/jam_websockets/session_error.rb +++ b/websocket-gateway/lib/jam_websockets/session_error.rb @@ -1,4 +1,11 @@ -class SessionError < Exception +class SessionError < StandardError + + attr_accessor :error_code + + def initialize(msg, error_code = nil) + super(msg) + @error_code = error_code + end end diff --git a/websocket-gateway/script/package/upstart-run.sh b/websocket-gateway/script/package/upstart-run.sh index 0df30a0e7..f63efe935 100755 --- a/websocket-gateway/script/package/upstart-run.sh +++ b/websocket-gateway/script/package/upstart-run.sh @@ -24,7 +24,7 @@ main() # and bundle won't run because it thinks it has the wrong versions of gems rm -f Gemfile.lock - JAM_INSTANCE=$JAM_INSTANCE BUILD_NUMBER=$BUILD_NUMBER JAMENV=production exec bundle exec ruby -Ilib bin/websocket_gateway + GATEWAY_NAME="$GATEWAY_NAME" JAM_INSTANCE=$JAM_INSTANCE BUILD_NUMBER=$BUILD_NUMBER JAMENV=production exec bundle exec ruby -Ilib bin/websocket_gateway } [ "$#" -ne 1 ] && ( usage && exit 1 ) || main diff --git a/websocket-gateway/spec/jam_websockets/router_spec.rb b/websocket-gateway/spec/jam_websockets/router_spec.rb index 99e7a2f2f..75403152b 100644 --- a/websocket-gateway/spec/jam_websockets/router_spec.rb +++ b/websocket-gateway/spec/jam_websockets/router_spec.rb @@ -51,7 +51,7 @@ def login(router, user, password, client_id, token, client_type) args[0].should == client args[1].is_a?(Jampb::ClientMessage).should be_true end - router.should_receive(:extract_ip).at_least(:once).with(client).and_return("127.0.0.1") + router.should_receive(:extract_ip).at_least(:once).with(client, nil).and_return("127.0.0.1") client.should_receive(:onclose) client.should_receive(:onerror) @@ -61,6 +61,7 @@ def login(router, user, password, client_id, token, client_type) @router.new_client(client, false) handshake = double("handshake") handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_id" => client_id, "token" => token, "client_type" => client_type }) + handshake.should_receive(:headers).at_least(:once).and_return({}) client.onopenblock.call handshake client @@ -84,7 +85,7 @@ def login_latency_tester(router, latency_tester, client_id) args[0].should == client args[1].is_a?(Jampb::ClientMessage).should be_true end - router.should_receive(:extract_ip).at_least(:once).with(client).and_return("127.0.0.1") + router.should_receive(:extract_ip).at_least(:once).with(client, nil).and_return("127.0.0.1") client.should_receive(:onclose) client.should_receive(:onerror) @@ -93,6 +94,7 @@ def login_latency_tester(router, latency_tester, client_id) @router.new_client(client, true) handshake = double("handshake") handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_type" => "latency_tester", "client_id" => client_id }) + handshake.should_receive(:headers).at_least(:once).and_return({}) client.onopenblock.call handshake client @@ -240,7 +242,7 @@ describe Router do client = TestClient.new - error_msg = message_factory.server_rejection_error("invalid login") + error_msg = message_factory.server_rejection_error("invalid login", "invalid_login") @router.should_receive(:send_to_client).with(client, error_msg) client.should_receive(:close_websocket)