(function (context, $) { "use strict"; context.JK = context.JK || {}; context.JK.NetworkTest = function (app) { var NETWORK_TEST_TYPES = { RestPhase : 0, PktTest100NormalLatency : 1, PktTest200MediumLatency : 2, PktTest400LowLatency : 3, PktTestRateSweep : 4, RcvOnly : 5 } var STARTING_NUM_CLIENTS = 4; var PAYLOAD_SIZE = 100; var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2; var rest = context.JK.Rest(); var logger = context.JK.logger; var $step = null; var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess'; var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout'; var $startNetworkTestBtn = null; var $testResults = null; var $testScore = null; var $testText = null; var testedSuccessfully = false; var $scoringBar = null; var $goodMarker = null; var $goodLine = null; var $currentScore = null; var $scoredClients = null; var $subscore = null; var backendGuardTimeout = null; var serverClientId = ''; var scoring = false; var numClientsToTest = STARTING_NUM_CLIENTS; var testSummary = {attempts : [], final:null} var $self = $(this); var scoringZoneWidth = 100;//px var inGearWizard = false; var NETWORK_TEST_START = 'network_test.start'; var NETWORK_TEST_DONE = 'network_test.done'; var NETWORK_TEST_FAIL = 'network_test.fail'; function createSuccessCallbackName() { if(inGearWizard) { return TEST_SUCCESS_CALLBACK + 'ForGearWizard'; } else { return TEST_SUCCESS_CALLBACK + 'ForDialog'; } } function createTimeoutCallbackName() { if(inGearWizard) { return TEST_TIMEOUT_CALLBACK + 'ForGearWizard'; } else { return TEST_TIMEOUT_CALLBACK + 'ForDialog'; } } function reset() { serverClientId = ''; scoring = false; numClientsToTest = STARTING_NUM_CLIENTS; testSummary = {attempts : []}; configureStartButton(); $scoredClients.empty(); $testResults.removeClass('good acceptable bad testing'); $testText.empty(); $subscore.empty(); $currentScore.width(0); } function renderStartTest() { configureStartButton(); $testResults.addClass('testing'); $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); } function renderStopTest(score, text) { $scoredClients.html(score); $testText.html(text); $testResults.removeClass('testing'); } function postDiagnostic() { rest.createDiagnostic({ type: 'NETWORK_TEST_RESULT', data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary} }); } function appendContextualStatement() { if(inGearWizard) { return "You can skip this step for now." } else { return ''; } } function testFinished() { var attempt = getCurrentAttempt(); if(!testSummary.final) { testSummary.final = {reason : attempt.reason}; } var reason = testSummary.final.reason; var success = false; if(reason == "success") { renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.") testedSuccessfully = true; if(!testSummary.final.num_clients) { testSummary.final.num_clients = attempt.num_clients; } context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients); context.jamClient.SetNetworkTestScore(attempt.num_clients); if(testSummary.final.num_clients == 2) { $testResults.addClass('acceptable'); } else { $testResults.addClass('good'); } success = true; } else if(reason == "minimum_client_threshold") { context.jamClient.SetNetworkTestScore(0); renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.") } else if(reason == "unreachable") { context.jamClient.SetNetworkTestScore(0); renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information."); } else if(reason == "internal_error") { context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection."); renderStopTest('', ''); } else if(reason == "remote_peer_cant_test") { context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties."); renderStopTest('', ''); } else if(reason == 'backend_gone') { context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties."); renderStopTest('', ''); } else if(reason == "invalid_response") { context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.'); renderStopTest('', ''); } else if(reason == 'no_servers') { context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement()); renderStopTest('', ''); testedSuccessfully = true; } else if(reason == 'no_network') { context.JK.Banner.showAlert("Please try again later. Your network appears down."); renderStopTest('', ''); } else if(reason == "rest_api_error") { context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement()); testedSuccessfully = true; renderStopTest('', ''); } else if(reason == "timeout") { context.JK.alertSupportedNeeded("Communication with a network test servers timed out." + appendContextualStatement()); testedSuccessfully = true; renderStopTest('', ''); } else { context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection."); renderStopTest('', ''); } numClientsToTest = STARTING_NUM_CLIENTS; scoring = false; configureStartButton(); postDiagnostic(); if(success) { $self.triggerHandler(NETWORK_TEST_DONE) } else { $self.triggerHandler(NETWORK_TEST_FAIL) } } function getCurrentAttempt() { return testSummary.attempts[testSummary.attempts.length - 1]; } function backendTimedOut() { testSummary.final = {reason: 'backend_gone'} testFinished(); } function cancel() { clearBackendGuard(); } function clearBackendGuard() { if(backendGuardTimeout) { clearTimeout(backendGuardTimeout); backendGuardTimeout = null; } } function attemptTestPass() { var attempt = {}; attempt.payload_size = PAYLOAD_SIZE; attempt.duration = gon.ftue_network_test_duration; attempt.test_type = 'PktTest400LowLatency'; attempt.num_clients = numClientsToTest; attempt.server_client_id = serverClientId; attempt.received_progress = false; testSummary.attempts.push(attempt); //context.jamClient.StopNetworkTest(''); $testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session."); updateProgress(0, false); backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000); context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(), createTimeoutCallbackName(), NETWORK_TEST_TYPES.PktTest400LowLatency, gon.ftue_network_test_duration, numClientsToTest, PAYLOAD_SIZE); } function startNetworkTest(checkWireless) { if(scoring) return false; if(checkWireless) { // check if on Wifi 1st var isWireless = context.jamClient.IsMyNetworkWireless(); if(isWireless == -1) { logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.") } else if(isWireless == 1) { context.JK.Banner.showAlert({buttons: [ {name: 'RUN NETWORK TEST ANYWAY', click: function() {startNetworkTest(false)}}, {name: 'CANCEL', click: function() {}}], html: "
It appears that your computer is connected to your network using WiFi.
" + "We strongly advise against running the JamKazam application on a WiFi connection. " + "We recommend using a wired Ethernet connection from your computer to your router. " + "A WiFi connection is likely to cause significant issues in both latency and audio quality.
"}) return false; } } reset(); scoring = true; $self.triggerHandler(NETWORK_TEST_START); renderStartTest(); rest.getLatencyTester() .done(function(response) { // ensure there are no tests ongoing serverClientId = response.client_id; testSummary.serverClientId = serverClientId; logger.info("beginning network test against client_id: " + serverClientId); attemptTestPass(); }) .fail(function(jqXHR) { if(jqXHR.status == 404) { // means there are no network testers available. // we have to skip this part of the UI testSummary.final = {reason: 'no_servers'} } else { if(context.JK.isNetworkError(arguments)) { testSummary.final = {reason: 'no_network'} } else { testSummary.final = {reason: 'rest_api_error'} } } testFinished(); }) logger.info("starting network test"); return false; } function updateProgress(throughput, showSubscore) { var width = throughput * 100; $currentScore.stop().data('showSubscore', showSubscore); if(!showSubscore) { $subscore.text(''); } $currentScore.animate({ duration: 1000, width: width + '%' }, { step: function (now, fx) { if(showSubscore) { var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) ); $subscore.text((Math.round(newWidth * 10) / 10) + '%'); } } }).css('overflow', 'visible'); ; } function networkTestSuccess(data) { clearBackendGuard(); var attempt = getCurrentAttempt(); function refineTest(up) { if(up) { if(numClientsToTest == gon.ftue_network_test_max_clients) { attempt.reason = "success"; testFinished(); } else { numClientsToTest++; logger.debug("increasing number of clients to " + numClientsToTest); setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm } } else { // reduce numclients if we can if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) { // we are too low already. fail the user attempt.reason = "minimum_client_threshold"; testFinished(); } else if(numClientsToTest > STARTING_NUM_CLIENTS) { // this means we've gone up before... so don't go back down (i.e., creating a loop) attempt.reason = "success"; testSummary.final = { reason:'success', num_clients: numClientsToTest - 1 } testFinished(); } else { numClientsToTest--; logger.debug("reducing number of clients to " + numClientsToTest); setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm } } } attempt.backend_data = data; if(data.progress === true) { var animate = true; if(data.downthroughput && data.upthroughput) { if(data.downthroughput > 0 || data.upthroughput > 0) { attempt.received_progress = true; animate = true; } if(attempt.received_progress) { // take the lower var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput; updateProgress(throughput, true); } } } else { logger.debug("network test pass success. data: ", data); if(data.reason == "unreachable") { // STUN logger.debug("network test: unreachable (STUN issue or similar)"); attempt.reason = data.reason; testFinished(); } else if(data.reason == "internal_error") { // oops logger.debug("network test: internal_error (client had a unexpected problem)"); attempt.reason = data.reason; testFinished(); } else if(data.reason == "remote_peer_cant_test") { // old client logger.debug("network test: remote_peer_cant_test (old client)") attempt.reason = data.reason; testFinished(); } else { if(!data.downthroughput || !data.upthroughput) { // we have to assume this is bad. just not a reason we know about in code logger.debug("network test: no test data (unknown issue? " + data.reason + ")") attempt.reason = "invalid_response"; testFinished(); } else { // success... but we still have to verify if this data is within threshold if(data.downthroughput < gon.ftue_packet_rate_treshold) { logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); refineTest(false); } else if(data.upthroughput < gon.ftue_packet_rate_treshold) { logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); refineTest(false); } else { // true success. we can accept this score logger.debug("network test: success") refineTest(true); } } } // VRFS-1742 // context.jamClient.StopNetworkTest(serverClientId); } } function networkTestTimeout(data) { clearBackendGuard(); logger.warn("network timeout when testing latency test: " + data); var attempt = getCurrentAttempt(); attempt.reason = 'timeout'; attempt.backend_data = data; testFinished(); } function hasScoredNetworkSuccessfully() { return testedSuccessfully; } function configureStartButton() { if(scoring) { $startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey') } else { $startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange'); } } function initializeNextButtonState() { $dialog.setNextState(hasScoredNetworkSuccessfully() && !scoring); } function initializeBackButtonState() { $dialog.setBackState(!scoring); } function beforeHide() { clearBackendGuard(); } function initialize(_$step, _inGearWizard) { $step = _$step; inGearWizard = _inGearWizard; $startNetworkTestBtn = $step.find('.start-network-test'); if($startNetworkTestBtn.length == 0) throw 'no start network test button found in network-test' $testResults = $step.find('.network-test-results'); $testScore = $step.find('.network-test-score'); $testText = $step.find('.network-test-text'); $scoringBar = $step.find('.scoring-bar'); $goodMarker = $step.find('.good-marker'); $goodLine =$step.find('.good-line'); $currentScore = $step.find('.current-score'); $scoredClients= $step.find('.scored-clients'); $subscore = $step.find('.subscore'); $startNetworkTestBtn.on('click', startNetworkTest); // if this network test is instantiated anywhere else than the gearWizard, or a dialog, then this will have to be expanded if(inGearWizard) { context.JK.HandleNetworkTestSuccessForGearWizard = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback context.JK.HandleNetworkTestTimeoutForGearWizard = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback } else { context.JK.HandleNetworkTestSuccessForDialog = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback context.JK.HandleNetworkTestTimeoutForDialog = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback } } this.isScoring = function () { return scoring; }; this.hasScoredNetworkSuccessfully = hasScoredNetworkSuccessfully; this.initialize = initialize; this.reset = reset; this.cancel = cancel; this.NETWORK_TEST_START = NETWORK_TEST_START; this.NETWORK_TEST_DONE = NETWORK_TEST_DONE; this.NETWORK_TEST_FAIL = NETWORK_TEST_FAIL; return this; } })(window, jQuery);