<commit_before>angular.module('myApp').controller('reposController', ['$scope', '$rootScope', '$routeParams', '$filter', '$q', '$http', 'reposApi', 'ngTableParams', 'amMoment', function($scope, $rootScope, $routeParams, $filter, $q, $http, reposApi, ngTableParams, amMoment) {
    $scope.dataLoaded = false;
    $scope.OAuth_passed = false;
    $scope.reqCounter = 0;
    $scope.filteredTreeData = 0;
    $scope.itemsLoaded = false;
    $scope.tableInitialized = false;

    var data = [],
        build_date_errors = [],
        tests_passing_errors = [];

    $scope.getItemsLoaded = function() {
        // there are 2 additional requests
        // per row - 1st for build date, 2nd - for tests results
        var current = parseFloat($scope.reqCounter/2);
        var total = $scope.filteredTreeData.length;
        $scope.itemsLoaded = current >= total-1 ? true:false;
        return parseInt(current)+'/'+parseInt(total);
    };

    var getBuildInfo = function(element, scope) {
        if ($scope.itemsLoaded) {return}
        $http.get('https://api.travis-ci.org/repos/fontdirectory/' + scope.build.repoName, {headers: { 'Accept': 'application/vnd.travis-ci.2+json' }, nointercept: true}).then(function(dataResponse) {
            //dataResponse.data.repo has attrs
            // description: null
            // github_language: null
            // id: 2678960
            // last_build_duration: 598
            // last_build_finished_at: "2014-10-06T13:00:19Z"
            // last_build_id: 37177838
            // last_build_language: null
            // last_build_number: "11"
            // last_build_started_at: "2014-10-06T12:50:21Z"
            // last_build_state: "passed"
            // slug: "fontdirectory/arimo"
            scope.build.buildDateOrig = dataResponse.data.repo.last_build_finished_at;
            scope.build.buildStatus = dataResponse.data.repo.last_build_state;
            $scope.reqCounter++;
        }, function(error) {
            $scope.reqCounter++;
            build_date_errors.push(error.status + ' - ' + error.statusText + ': ' + error.config.url);
            scope.build.buildDateOrig = null;
            scope.build.buildStatus = null;
        });
    };

    var getTestsInfo = function(element, scope) {
        if ($scope.itemsLoaded) {return}
        $http.get('https://cdn.rawgit.com/fontdirectory/' + scope.build.repoName + '/gh-pages/summary.tests.json', {nointercept: true}).then(function(dataResponse) {
            var tests_data = dataResponse.data,
                totalTests = 0,
                successTests = 0;
            $scope.reqCounter++;
            for (d in tests_data) {
                totalTests += tests_data[d].success + tests_data[d].error + tests_data[d].failure;
                successTests += tests_data[d].success;
            }
            scope.build['totalTests'] = totalTests;
            scope.build['successTests'] = successTests;
            scope.build['passingTests'] = Math.round((successTests / totalTests) * 100) + '%'
        }, function(error) {
            $scope.reqCounter++;
            tests_passing_errors.push(error.status + ' - ' + error.statusText + ': ' + error.config.url);
            scope.build['totalTests'] = null;
            scope.build['successTests'] = null;
            scope.build['passingTests'] = null;
            scope.build['noTestsInfo'] = {status: error.status + ' - ' + error.statusText, url: error.config.url};
        });
    };

    $scope.authenticateWithOAuth = function() {
        OAuth.initialize('ZapNqQn-kP45Mq8tXYZi0u_G9bc');
        OAuth.popup('github', {cache: true})
            .done(function(success){
                // See the result below
                $scope.OAuth_passed = true;
                reposApi.getCollection(success['access_token']).then(function(dataResponse) {

                    $scope.filteredTreeData = dataResponse.data.tree.filter(function(item) {
                        return "160000" == item.mode && "commit" == item.type && item.path.split('/')[1] != 'fontbakery';
                    });

                    $scope.buildsTableParams = new ngTableParams({
                        // show first page
                        page: 1,
                        // count per page
                        count: $scope.filteredTreeData.length,
                        // initial sorting
                        sorting: {
                            repoPath: 'asc'
                        }
                    }, {
                        // hide page counts control
                        counts: [],
                        // length of data
                        total: $scope.filteredTreeData.length,
                        getData: function($defer, params) {
                            if ($scope.filteredTreeData.length != data.length) {
                                angular.forEach($scope.filteredTreeData, function(item, index) {
                                    var info = {};

                                    var path = item.path.split('/')[1];

                                    info['repoPath'] = item.path;
                                    info['repoName'] = path;
                                    info['repoLink'] = 'https://github.com/fontdirectory/' + path;
                                    info['travisLink'] = 'http://travis-ci.org/fontdirectory/' + path;
                                    info['travisImg'] = 'https://travis-ci.org/fontdirectory/' + path + '.svg';
                                    info['reportLink'] = 'https://fontdirectory.github.io/' + path;
                                    info['getBuildInfoCallback'] = getBuildInfo;
                                    info['getTestsInfoCallback'] = getTestsInfo;
                                    data.push(info);
                                });
                            }
                            var filteredData = $filter('filter')(data, $rootScope.repo_selected.name, function(val1, val2) {
                                return $scope.filterWithQuicksilverRanking(val1, val2)
                            });

                            var orderedData = params.sorting() ?
                                $filter('orderBy')(filteredData, params.orderBy()) :
                                data;
                            $defer.resolve(orderedData);
                            $scope.dataLoaded = true;
                        }
                    });
                }, function(error) {
                    $scope.alerts.addAlert(error.message);
                });
            })
            .fail(function(error) {
                $scope.OAuth_passed = false;
            });
    };

    $scope.init = function () {
        if (!$scope.OAuth_passed) {
            $scope.authenticateWithOAuth();
        }
    };

    $rootScope.$watch('repo_selected', function(repo_selected) {
        if ($scope.buildsTableParams) {
            $scope.buildsTableParams.reload();
        }
    }, true);

    $scope.$watch('reqCounter', function(reqCounter) {
        // force default sorting when all data is in table
        if (!$scope.tableInitialized && $scope.itemsLoaded && $scope.filteredTreeData.length == data.length) {
            $scope.tableInitialized = true;
            if ($scope.buildsTableParams) {
                $scope.buildsTableParams.sorting();
            }
            if (build_date_errors.length > 0) {
                $scope.alerts.addAlert('Failed to get information about last build dates for '+build_date_errors.length+' items. Please see table below.', 'warning');
            }
            if (tests_passing_errors.length > 0) {
                $scope.alerts.addAlert('Failed to get information about tests statistics for '+tests_passing_errors.length+' items. Please see table below.', 'warning');
            }
        }
    });

    $scope.init();

}]);<commit_msg>Better error messages in reposController.js<commit_after>angular.module('myApp').controller('reposController', ['$scope', '$rootScope', '$routeParams', '$filter', '$q', '$http', 'reposApi', 'ngTableParams', 'amMoment', function($scope, $rootScope, $routeParams, $filter, $q, $http, reposApi, ngTableParams, amMoment) {
    $scope.dataLoaded = false;
    $scope.OAuth_passed = false;
    $scope.reqCounter = 0;
    $scope.filteredTreeData = 0;
    $scope.itemsLoaded = false;
    $scope.tableInitialized = false;

    var data = [],
        build_date_errors = [],
        tests_passing_errors = [];

    $scope.getItemsLoaded = function() {
        // there are 2 additional requests
        // per row - 1st for build date, 2nd - for tests results
        var current = parseFloat($scope.reqCounter/2);
        var total = $scope.filteredTreeData.length;
        $scope.itemsLoaded = current >= total-1 ? true:false;
        return parseInt(current)+'/'+parseInt(total);
    };

    var getBuildInfo = function(element, scope) {
        if ($scope.itemsLoaded) {return}
        $http.get('https://api.travis-ci.org/repos/fontdirectory/' + scope.build.repoName, {headers: { 'Accept': 'application/vnd.travis-ci.2+json' }, nointercept: true}).then(function(dataResponse) {
            //dataResponse.data.repo has attrs
            // description: null
            // github_language: null
            // id: 2678960
            // last_build_duration: 598
            // last_build_finished_at: "2014-10-06T13:00:19Z"
            // last_build_id: 37177838
            // last_build_language: null
            // last_build_number: "11"
            // last_build_started_at: "2014-10-06T12:50:21Z"
            // last_build_state: "passed"
            // slug: "fontdirectory/arimo"
            scope.build.buildDateOrig = dataResponse.data.repo.last_build_finished_at;
            scope.build.buildStatus = dataResponse.data.repo.last_build_state;
            $scope.reqCounter++;
        }, function(error) {
            $scope.reqCounter++;
            build_date_errors.push(error.status + ' - ' + error.statusText + ': ' + error.config.url);
            scope.build.buildDateOrig = null;
            scope.build.buildStatus = null;
        });
    };

    var getTestsInfo = function(element, scope) {
        if ($scope.itemsLoaded) {return}
        $http.get('https://cdn.rawgit.com/fontdirectory/' + scope.build.repoName + '/gh-pages/summary.tests.json', {nointercept: true}).then(function(dataResponse) {
            var tests_data = dataResponse.data,
                totalTests = 0,
                successTests = 0;
            $scope.reqCounter++;
            for (d in tests_data) {
                totalTests += tests_data[d].success + tests_data[d].error + tests_data[d].failure;
                successTests += tests_data[d].success;
            }
            scope.build['totalTests'] = totalTests;
            scope.build['successTests'] = successTests;
            scope.build['passingTests'] = Math.round((successTests / totalTests) * 100) + '%'
        }, function(error) {
            $scope.reqCounter++;
            tests_passing_errors.push(error.status + ' - ' + error.statusText + ': ' + error.config.url);
            scope.build['totalTests'] = null;
            scope.build['successTests'] = null;
            scope.build['passingTests'] = null;
            scope.build['noTestsInfo'] = {status: error.status + ' - ' + error.statusText, url: error.config.url};
        });
    };

    $scope.authenticateWithOAuth = function() {
        OAuth.initialize('ZapNqQn-kP45Mq8tXYZi0u_G9bc');
        OAuth.popup('github', {cache: true})
            .done(function(success){
                // See the result below
                $scope.OAuth_passed = true;
                reposApi.getCollection(success['access_token']).then(function(dataResponse) {

                    $scope.filteredTreeData = dataResponse.data.tree.filter(function(item) {
                        return "160000" == item.mode && "commit" == item.type && item.path.split('/')[1] != 'fontbakery';
                    });

                    $scope.buildsTableParams = new ngTableParams({
                        // show first page
                        page: 1,
                        // count per page
                        count: $scope.filteredTreeData.length,
                        // initial sorting
                        sorting: {
                            repoPath: 'asc'
                        }
                    }, {
                        // hide page counts control
                        counts: [],
                        // length of data
                        total: $scope.filteredTreeData.length,
                        getData: function($defer, params) {
                            if ($scope.filteredTreeData.length != data.length) {
                                angular.forEach($scope.filteredTreeData, function(item, index) {
                                    var info = {};

                                    var path = item.path.split('/')[1];

                                    info['repoPath'] = item.path;
                                    info['repoName'] = path;
                                    info['repoLink'] = 'https://github.com/fontdirectory/' + path;
                                    info['travisLink'] = 'http://travis-ci.org/fontdirectory/' + path;
                                    info['travisImg'] = 'https://travis-ci.org/fontdirectory/' + path + '.svg';
                                    info['reportLink'] = 'https://fontdirectory.github.io/' + path;
                                    info['getBuildInfoCallback'] = getBuildInfo;
                                    info['getTestsInfoCallback'] = getTestsInfo;
                                    data.push(info);
                                });
                            }
                            var filteredData = $filter('filter')(data, $rootScope.repo_selected.name, function(val1, val2) {
                                return $scope.filterWithQuicksilverRanking(val1, val2)
                            });

                            var orderedData = params.sorting() ?
                                $filter('orderBy')(filteredData, params.orderBy()) :
                                data;
                            $defer.resolve(orderedData);
                            $scope.dataLoaded = true;
                        }
                    });
                }, function(error) {
                    $scope.alerts.addAlert(error.message);
                });
            })
            .fail(function(error) {
                $scope.OAuth_passed = false;
            });
    };

    $scope.init = function () {
        if (!$scope.OAuth_passed) {
            $scope.authenticateWithOAuth();
        }
    };

    $rootScope.$watch('repo_selected', function(repo_selected) {
        if ($scope.buildsTableParams) {
            $scope.buildsTableParams.reload();
        }
    }, true);

    $scope.$watch('reqCounter', function(reqCounter) {
        // force default sorting when all data is in table
        if (!$scope.tableInitialized && $scope.itemsLoaded && $scope.filteredTreeData.length == data.length) {
            $scope.tableInitialized = true;
            if ($scope.buildsTableParams) {
                $scope.buildsTableParams.sorting();
            }
            if (build_date_errors.length > 0) {
                $scope.alerts.addAlert('Build dates for '+build_date_errors.length+' projects could not be found, so those projects have no date in the table below. Try rebuliding them.', 'warning');
            }
            if (tests_passing_errors.length > 0) {
                $scope.alerts.addAlert('Test results for '+tests_passing_errors.length+' projects could not be found, so those projects have an orange N/A result in the table below. Try rebuliding them.', 'warning');
            }
        }
    });

    $scope.init();

}]);
<|endoftext|>


<commit_before>(function($) {

	//Set styles and mask
	//Fast blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterFast">'+
								'<feGaussianBlur stdDeviation="5" />'+
							'</filter>'+
						'</svg>');
	}
	//Medium blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterMedium">'+
								'<feGaussianBlur stdDeviation="3" />'+
							'</filter>'+
						'</svg>');
	}
	//Slow blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterSlow">'+
								'<feGaussianBlur stdDeviation="1" />'+
							'</filter>'+
						'</svg>');
	}
	//Fade mask
	if( $("mask#easySlotMachineFadeSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<mask id="easySlotMachineFadeMask" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">'+
								'<linearGradient id="easySlotMachineFadeGradient" gradientUnits="objectBoundingBox" x="0" y="0">'+
									'<stop stop-color="white" stop-opacity="0" offset="0"></stop>'+
									'<stop stop-color="white" stop-opacity="1" offset="0.25"></stop>'+
									'<stop stop-color="white" stop-opacity="1" offset="0.75"></stop>'+
									'<stop stop-color="white" stop-opacity="0" offset="1"></stop>'+
								'</linearGradient>'+
								'<rect x="0" y="-1" width="1" height="1" transform="rotate(90)" fill="url(#easySlotMachineFadeGradient)"></rect>'+
							'</mask>'+
						'</svg>');
	}

	$("body").append("<style>"+
							".easySlotMachineBlurFast{-webkit-filter: blur(5px);-moz-filter: blur(5px);-o-filter: blur(5px);-ms-filter: blur(5px);filter: blur(5px);filter: url(#easySlotMachineBlurFilterFast);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='5')}"+
							".easySlotMachineBlurMedium{-webkit-filter: blur(3px);-moz-filter: blur(3px);-o-filter: blur(3px);-ms-filter: blur(3px);filter: blur(3px);filter: url(#easySlotMachineBlurFilterMedium);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='3')}"+
							".easySlotMachineBlurSlow{-webkit-filter: blur(1px);-moz-filter: blur(1px);-o-filter: blur(1px);-ms-filter: blur(1px);filter: blur(1px);filter: url(#easySlotMachineBlurFilterSlow);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='1')}"+
							".easySlotMachineGradient{"+
								"-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(0,0,0,0)), color-stop(25%, rgba(0,0,0,1)), color-stop(75%, rgba(0,0,0,1)), color-stop(100%, rgba(0,0,0,0)) );"+
								"mask: url(#easySlotMachineFadeMask);"+
							"}"+
						"</style>");

	/**
	  * @desc makes Slot Machine animation effect
	  * @param object settings - Plugin configuration params
	  * @return jQuery node - Return jQuery selector with some new functions (shuffle, stop, next, auto)
	*/
	$.fn.easySlotMachine = function(settings){

		var defaults = {
				active	: 0, //Active element
				delay	: 200, //Animation time
				repeat	: false //Repeat each 2000ms
			},
			settings = $.extend(defaults, settings),
			$slot = $(this),
			$titles = $slot.children(),
			$container,
			maxTop,
			_timer = null,
			_currentAnim = null,
			_forceStop = false,
			_active = {
				index : settings.active,
				el	  : $titles.get( settings.active )
			};

		//Get element offset top
		function _getOffset(index){
			var offset = 0;
			for(var i=0; i<index; i++){
				offset += $( $titles.get(i) ).height();
			}
			return -offset;
		}

		//Get random element
		function _getRandom(){
			var rnd;
			do{
				rnd = Math.floor( Math.random() * $titles.length );
			}while( rnd==_active.index && rnd>=0 );

			//Choose element
			var choosen = {
					index : rnd,
					el : $titles.get( rnd )
				};
			return choosen;
		}

		function _setActive(elWithIndex){
			//Update last choosen element index
			_active = elWithIndex;
		}

		//Get next element
		function _getNext(){
			var nextIndex = _active.index+1<$titles.length ? _active.index+1 : 0
			var nextObj = {
				index : nextIndex,
				el	  : $titles.get(nextIndex)
			}
			return nextObj;
		};

		//From jQuery easing
		jQuery.extend( jQuery.easing, {
			easeOutBounce: function (x, t, b, c, d) {
				if ((t/=d) < (1/2.75)) {
					return c*(7.5625*t*t) + b;
				} else if (t < (2/2.75)) {
					return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
				} else if (t < (2.5/2.75)) {
					return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
				} else {
					return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
				}
			},
		});

		function _setAnimationFX(speed, fade){
			$container.add( $titles ).removeClass("easySlotMachineBlurFast easySlotMachineBlurMedium easySlotMachineBlurSlow");
			switch( speed ){
				case 'fast':
					$container.add( $titles ).addClass("easySlotMachineBlurFast");
					break;
				case 'medium':
					$container.add( $titles ).addClass("easySlotMachineBlurMedium");
					break;
				case 'slow':
					$container.add( $titles ).addClass("easySlotMachineBlurSlow");
					break;
			}

			if( fade!==true || speed==="stop" ){
				$slot.removeClass("easySlotMachineGradient");
			}else{
				$slot.addClass("easySlotMachineGradient");
			}
		}

		function _resetPosition(){
			//Reset top position
			$container.css("margin-top", _getOffset(_active.index));
		}

		//Rock the machine!
		function _shuffle( count ){

			//Infinite animation
			if( count===undefined ){

				//Set animation effects
				_setAnimationFX("fast", true);

				var delay = settings.delay / 2;

				if( _isVisible() ){

					//Perform animation
					_currentAnim = $container.animate({
						marginTop : _maxTop
					}, delay, function(){

						//Remove animation var
						_currentAnim = null;

						//Reset top position
						$container.css("margin-top", 0);

					});

				}else{

					_setAnimationFX("stop");

					_resetPosition();

				}

				//Oncomplete animation
				setTimeout(function(){

					if( _forceStop===false ){

						//Repeat animation
						_shuffle();

					}

				}, delay + 25);

			//Stop animation after {count} repeats
			}else{

				//Perform fast animation
				if( count>=1 ){

					var delay = settings.delay;

					if( count>1 ){

						//Set animation effects
						_setAnimationFX("fast", true);

						delay /= 2;

					}else{

						//Set animation effects
						_setAnimationFX("medium", true);

					}

					if( _isVisible() ){

						//Perform animation
						_currentAnim = $container.animate({
							marginTop : _maxTop
						}, delay, function(){

							//Remove animation var
							_currentAnim = null;

							//Reset top position
							$container.css("margin-top", 0);

						});

					}else{

						_setAnimationFX("stop");

						_resetPosition();

					}

					//Oncomplete animation
					setTimeout(function(){

						//Repeat animation
						_shuffle( count-1 );

					}, delay + 25);

				}else{

					//Stop NOW!
					_stop(true);

				}

			}

		}

		function _stop( nowOrRepeations ){

			//Stop animation
			if( _currentAnim!==null ){
				_currentAnim.stop();
			}

			//Get element
			var rnd;
			if( settings.repeat ){
				rnd = _getNext();
			}else{
				rnd = _getRandom();
			}

			//Stop animation NOW!!!!!!!
			if( nowOrRepeations===true || nowOrRepeations<=1 ){

				_setAnimationFX("slow", true);

				//get random element offset
				var offset = _getOffset(rnd.index);

				//Exception: first element
				if( rnd.index===0 ){
					$container.css("margin-top", -$( rnd.el ).height() / 2 );
				}

				var delay = 75 * $titles.length - rnd.index;

				if( _isVisible() ){

					_setActive( rnd );

					//Perform animation
					$container.animate({
						marginTop : offset
					}, delay, "easeOutBounce");

				}else{

					_setAnimationFX("stop");

					_resetPosition();

				}

				//Oncomplete animation
				setTimeout(function(){

					_setAnimationFX("stop");

				}, delay + 25);

			//Stop animation sloooooooowly
			}else{

				_shuffle(nowOrRepeations || 3);

			}

		}

		function _isVisible(){
			//Stop animation if element is [above||below] screen, best for performance
			var above = $slot.offset().top > $(window).scrollTop() + $(window).height(),
				below = $(window).scrollTop() > $slot.height() + $slot.offset().top;

			return !above && !below;
		}

		function _auto(){

			if( _forceStop===false ){

				_timer = setTimeout(function(){

					if( _forceStop===false ){

						_shuffle(3);

					}

					_timer = _auto();

				}, settings.repeat + 1000);

			}

		}

		$slot.css("overflow", "hidden");

		//Wrap elements inside $container
		$titles.wrapAll("<div class='slotMachineContainer' />");
		$container = $slot.find(".slotMachineContainer");

		//Set max top offset
		_maxTop = - $container.height();

		//Show active element
		$container.css("margin-top", _getOffset(settings.active) );

		//Start auto animation
		if( settings.repeat!==false ){

			_auto();

		}

		//Public methods

		//Starts shuffling animation
		$slot.shuffle = function( count ){

			_forceStop = false;

			_shuffle(count);

		};

		$slot.stop = function( nowOrRepeations ){

			if( settings.repeat!==false && _timer!==null ){

				_forceStop = true;

				clearTimeout(_timer);

			}

			_stop(nowOrRepeations);

		};

		$slot.next = function(){

			$slot.stop(true);

		}

		$slot.auto = _auto;

		return $slot;

	};
})(jQuery);<commit_msg>Code methods fully commented<commit_after>(function($) {

	//Set required styles, filters and masks

	//Fast blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterFast">'+
								'<feGaussianBlur stdDeviation="5" />'+
							'</filter>'+
						'</svg>');
	}

	//Medium blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterMedium">'+
								'<feGaussianBlur stdDeviation="3" />'+
							'</filter>'+
						'</svg>');
	}

	//Slow blur
	if( $("filter#easySlotMachineBlurSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<filter id="easySlotMachineBlurFilterSlow">'+
								'<feGaussianBlur stdDeviation="1" />'+
							'</filter>'+
						'</svg>');
	}

	//Fade mask
	if( $("mask#easySlotMachineFadeSVG").length<=0 ){
		$("body").append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">'+
							'<mask id="easySlotMachineFadeMask" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">'+
								'<linearGradient id="easySlotMachineFadeGradient" gradientUnits="objectBoundingBox" x="0" y="0">'+
									'<stop stop-color="white" stop-opacity="0" offset="0"></stop>'+
									'<stop stop-color="white" stop-opacity="1" offset="0.25"></stop>'+
									'<stop stop-color="white" stop-opacity="1" offset="0.75"></stop>'+
									'<stop stop-color="white" stop-opacity="0" offset="1"></stop>'+
								'</linearGradient>'+
								'<rect x="0" y="-1" width="1" height="1" transform="rotate(90)" fill="url(#easySlotMachineFadeGradient)"></rect>'+
							'</mask>'+
						'</svg>');
	}

	//CSS classes
	$("body").append("<style>"+
							".easySlotMachineBlurFast{-webkit-filter: blur(5px);-moz-filter: blur(5px);-o-filter: blur(5px);-ms-filter: blur(5px);filter: blur(5px);filter: url(#easySlotMachineBlurFilterFast);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='5')}"+
							".easySlotMachineBlurMedium{-webkit-filter: blur(3px);-moz-filter: blur(3px);-o-filter: blur(3px);-ms-filter: blur(3px);filter: blur(3px);filter: url(#easySlotMachineBlurFilterMedium);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='3')}"+
							".easySlotMachineBlurSlow{-webkit-filter: blur(1px);-moz-filter: blur(1px);-o-filter: blur(1px);-ms-filter: blur(1px);filter: blur(1px);filter: url(#easySlotMachineBlurFilterSlow);filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='1')}"+
							".easySlotMachineGradient{"+
								"-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(0,0,0,0)), color-stop(25%, rgba(0,0,0,1)), color-stop(75%, rgba(0,0,0,1)), color-stop(100%, rgba(0,0,0,0)) );"+
								"mask: url(#easySlotMachineFadeMask);"+
							"}"+
						"</style>");

	//Required easing functions
	if( typeof $.easing.easeOutBounce!=="function" ){
		//From jQuery easing, extend jQuery animations functions
		$.extend( jQuery.easing, {
			easeOutBounce: function (x, t, b, c, d) {
				if ((t/=d) < (1/2.75)) {
					return c*(7.5625*t*t) + b;
				} else if (t < (2/2.75)) {
					return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
				} else if (t < (2.5/2.75)) {
					return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
				} else {
					return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
				}
			},
		});
	}

	/**
	  * @desc PUBLIC - Makes Slot Machine animation effect
	  * @param object settings - Plugin configuration params
	  * @return jQuery node - Returns jQuery selector with some new functions (shuffle, stop, next, auto, active)
	*/
	$.fn.easySlotMachine = function(settings){

		var defaults = {
				active	: 0, //Active element [int]
				delay	: 200, //Animation time [int]
				repeat	: false //Repeat delay [false||int]
			},
			settings = $.extend(defaults, settings), //Plugin settings
			$slot = $(this), //jQuery selector
			$titles = $slot.children(), //Slot Machine elements
			$container, //Container to wrap $titles
			maxTop, //Max marginTop offset
			_timer = null, //Timeout recursive function to handle auto (settings.repeat)
			_currentAnim = null, //Current playing jQuery animation
			_forceStop = false, //Force execution for some functions
			_active = { //Current active element
				index : settings.active,
				el	  : $titles.get( settings.active )
			};

		/**
		  * @desc PRIVATE - Get element offset top
		  * @param int index - Element position
		  * @return int - Negative offset in px
		*/
		function _getOffset( index ){
			var offset = 0;
			for(var i=0; i<index; i++){
				offset += $( $titles.get(i) ).height();
			}
			return -offset;
		}

		/**
		  * @desc PRIVATE - Get random element different than last shown
		  * @return object - Element index and HTML node
		*/
		function _getRandom(){
			var rnd;
			do{
				rnd = Math.floor( Math.random() * $titles.length );
			}while( rnd==_active.index && rnd>=0 );

			//Choose element
			var choosen = {
					index : rnd,
					el : $titles.get( rnd )
				};
			return choosen;
		}

		/**
		  * @desc PRIVATE - Set currently showing element and makes active
		  * @param object elWithIndex - Element index and HTML node
		*/
		function _setActive( elWithIndex ){
			//Update last choosen element index
			_active = elWithIndex;
		}

		/**
		  * @desc PRIVATE - Get the next element
		  * @return int - Element index and HTML node
		*/
		function _getNext(){
			var nextIndex = _active.index+1<$titles.length ? _active.index+1 : 0
			var nextObj = {
				index : nextIndex,
				el	  : $titles.get(nextIndex)
			}
			return nextObj;
		};

		/**
		  * @desc PRIVATE - Set CSS classes to make speed effect
		  * @param string speed - Element speed [fast||medium||slow]
		  * @param string||boolean fade - Set fade gradient effect
		*/
		function _setAnimationFX(speed, fade){
			$container.add( $titles ).removeClass("easySlotMachineBlurFast easySlotMachineBlurMedium easySlotMachineBlurSlow");
			switch( speed ){
				case 'fast':
					$container.add( $titles ).addClass("easySlotMachineBlurFast");
					break;
				case 'medium':
					$container.add( $titles ).addClass("easySlotMachineBlurMedium");
					break;
				case 'slow':
					$container.add( $titles ).addClass("easySlotMachineBlurSlow");
					break;
			}

			if( fade!==true || speed==="stop" ){
				$slot.removeClass("easySlotMachineGradient");
			}else{
				$slot.addClass("easySlotMachineGradient");
			}
		}

		/**
		  * @desc PRIVATE - Reset active element position
		*/
		function _resetPosition(){
			$container.css("margin-top", _getOffset(_active.index));
		}

		/**
		  * @desc PRIVATE - Starts shuffling the elements
		  * @param int count - Number of shuffles (undefined to make infinite animation
		*/
		function _shuffle( count ){

			//Infinite animation
			if( count===undefined ){

				//Set animation effects
				_setAnimationFX("fast", true);

				var delay = settings.delay / 2;

				if( _isVisible() ){

					//Perform animation
					_currentAnim = $container.animate({
						marginTop : _maxTop
					}, delay, function(){

						//Remove animation var
						_currentAnim = null;

						//Reset top position
						$container.css("margin-top", 0);

					});

				}else{

					_setAnimationFX("stop");

					_resetPosition();

				}

				//Oncomplete animation
				setTimeout(function(){

					if( _forceStop===false ){

						//Repeat animation
						_shuffle();

					}

				}, delay + 25);

			//Stop animation after {count} repeats
			}else{

				//Perform fast animation
				if( count>=1 ){

					var delay = settings.delay;

					if( count>1 ){

						//Set animation effects
						_setAnimationFX("fast", true);

						delay /= 2;

					}else{

						//Set animation effects
						_setAnimationFX("medium", true);

					}

					if( _isVisible() ){

						//Perform animation
						_currentAnim = $container.animate({
							marginTop : _maxTop
						}, delay, function(){

							//Remove animation var
							_currentAnim = null;

							//Reset top position
							$container.css("margin-top", 0);

						});

					}else{

						_setAnimationFX("stop");

						_resetPosition();

					}

					//Oncomplete animation
					setTimeout(function(){

						//Repeat animation
						_shuffle( count-1 );

					}, delay + 25);

				}else{

					//Stop NOW!
					_stop(true);

				}

			}

		}

		/**
		  * @desc PRIVATE - Stop shuffling the elements
		  * @param int||boolean nowOrRepeations - Number of repeations to stop (true to stop NOW)
		*/
		function _stop( nowOrRepeations ){

			//Stop animation
			if( _currentAnim!==null ){
				_currentAnim.stop();
			}

			//Get element
			var rnd;
			if( settings.repeat ){
				rnd = _getNext();
			}else{
				rnd = _getRandom();
			}

			//Stop animation NOW!!!!!!!
			if( nowOrRepeations===true || nowOrRepeations<=1 ){

				_setAnimationFX("slow", true);

				//get random element offset
				var offset = _getOffset(rnd.index);

				//Exception: first element
				if( rnd.index===0 ){
					$container.css("margin-top", -$( rnd.el ).height() / 2 );
				}

				var delay = 75 * $titles.length - rnd.index;

				if( _isVisible() ){

					_setActive( rnd );

					//Perform animation
					$container.animate({
						marginTop : offset
					}, delay, "easeOutBounce");

				}else{

					_setAnimationFX("stop");

					_resetPosition();

				}

				//Oncomplete animation
				setTimeout(function(){

					_setAnimationFX("stop");

				}, delay + 25);

			//Stop animation sloooooooowly
			}else{

				_shuffle(nowOrRepeations || 3);

			}

		}

		/**
		  * @desc PRIVATE - Checks if the machine is on the screen
		  * @return int - Returns true if machine is on the screen
		*/
		function _isVisible(){
			//Stop animation if element is [above||below] screen, best for performance
			var above = $slot.offset().top > $(window).scrollTop() + $(window).height(),
				below = $(window).scrollTop() > $slot.height() + $slot.offset().top;

			return !above && !below;
		}

		/**
		  * @desc PRIVATE - Start auto shufflings, animation stops each 3 repeations. Then restart animation recursively
		*/
		function _auto(){

			if( _forceStop===false ){

				_timer = setTimeout(function(){

					if( _forceStop===false ){

						_shuffle(3);

					}

					_timer = _auto();

				}, settings.repeat + 1000);

			}

		}

		$slot.css("overflow", "hidden");

		//Wrap elements inside $container
		$titles.wrapAll("<div class='slotMachineContainer' />");
		$container = $slot.find(".slotMachineContainer");

		//Set max top offset
		_maxTop = - $container.height();

		//Show active element
		$container.css("margin-top", _getOffset(settings.active) );

		//Start auto animation
		if( settings.repeat!==false ){

			_auto();

		}

		//Public methods

		/**
		  * @desc PUBLIC - Starts shuffling the elements
		  * @param int count - Number of shuffles (undefined to make infinite animation
		*/
		$slot.shuffle = function( count ){

			_forceStop = false;

			_shuffle(count);

		};

		/**
		  * @desc PUBLIC - Stop shuffling the elements
		  * @param int||boolean nowOrRepeations - Number of repeations to stop (true to stop NOW)
		*/
		$slot.stop = function( nowOrRepeations ){

			if( settings.repeat!==false && _timer!==null ){

				_forceStop = true;

				clearTimeout(_timer);

			}

			_stop(nowOrRepeations);

		};

		/**
		  * @desc PUBLIC - SELECT next element relative to the current active element
		*/
		$slot.next = function(){

			$slot.stop(true);

		}

		/**
		  * @desc PUBLIC - Get selected element
		  * @return object - Element index and HTML node
		*/
		$slot.active = function(){
			return _getActive();
		}

		/**
		  * @desc PUBLIC - Start auto shufflings, animation stops each 3 repeations. Then restart animation recursively
		*/
		$slot.auto = _auto;

		return $slot;

	};

})(jQuery);<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var util = require('./util')
  , transports = require('./transports')
  , EventEmitter = require('./event-emitter')

/**
 * Module exports.
 */

module.exports = Socket;

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts = arguments[1] || {};
    opts.host = uri.host;
    opts.secure = uri.scheme == 'https' || uri.scheme == 'wss';
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};

  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.query = opts.query || {};
  this.upgrade = false !== opts.upgrade;
  this.path = opts.path || '/engine.io'
  this.forceJSONP = !!opts.forceJSONP;
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];

  this.open();
};

/**
 * Inherits from EventEmitter.
 */

util.inherits(Socket, EventEmitter);

/**
 * Creates transport of the given type.
 *
 * @param {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  // debug: creating transport "%s", name
  var query = clone(this.query)
  query.transport = name;

  if (this.sid) {
    query.sid = this.sid;
  }

  var transport = new transports[name]({
      host: this.host
    , port: this.port
    , secure: this.secure
    , path: this.path
    , query: query
    , forceJSONP: this.forceJSONP
  });

  return transport;
};

function clone (obj) {
  var o = {};
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = obj[i];
    }
  }
  return o;
}

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  this.readyState = 'opening';
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  this.setTransport(transport);
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  if (this.transport) {
    // debug: clearing existing transport
    this.transport.removeAllListeners();
  }

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('packet', function (packet) {
      self.onPacket(packet);
    })
    .on('error', function (e) {
      self.onError(e);
    })
    .on('close', function () {
      self.onClose('transport close');
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  // debug: probing transport "%s", name
  var transport = this.createTransport(name, { probe: 1 })
    , self = this

  transport.once('open', function () {
    // debug: probe transport "%s" opened - pinging, name
    transport.send({ type: 'ping', data: 'probe' });
    transport.once('message', function (msg) {
      if ('pong' == msg.type && 'probe' == msg.data) {
        // debug: probe transport "%s" pong - upgrading, name
        self.upgrading = true;
        self.emit('upgrading', name);

        // debug: pausing current transport "%s", self.transport.name
        self.transport.pause(function () {
          self.setTransport(self.transport);
          self.upgrading = false;
          self.flush();
          self.emit('upgrade', name);
        });
      } else {
        // debug: probe transport "%s" failed, name
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  this.once('close', function () {
    // debug: socket closed prematurely - aborting probe
    transport.close();
  });

  this.once('upgrading', function (to) {
    if (to != name) {
      // debug: probe for "%s" succeeded - aborting "%s", to, name
      transport.close();
    }
  });
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  // debug: socket open
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
  this.flush();

  if (this.upgrade && this.transport.pause) {
    // debug: starting upgrade probes
    for (var i = 0, l = this.upgrades.length; i < l; i++) {
      this.probe(this.upgrades[i]);
    }
  }
};

/**
 * Handles a packet.
 *
 * @api private
 */

Socket.prototype.onPacket = function (packet) {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    // debug: socket receive: type "%s" | data "%s", packet.type, packet.data
    switch (packet.type) {
      case 'open':
        this.onHandshake(util.parseJSON(packet.data));
        break;

      case 'ping':
        this.sendPacket('pong');
        this.setPingTimeout();
        break;

      case 'error':
        var err = new Error('server error');
        err.code = packet.data;
        this.emit('error', err);
        break;

      case 'message':
        this.emit('message', packet.data);
        this.onmessage && this.onmessage.call(this, packet.data);
        break;
    }
  } else {
    // debug: packet received with socket readyState "%s", this.readyState
  }
};

/**
 * Called upon handshake completion.
 *
 * @param {Object} handshake obj
 * @api private
 */

Socket.prototype.onHandshake = function (data) {
  this.emit('handshake', data);
  this.sid = data.sid;
  this.transport.query.sid = data.sid;
  this.upgrades = data.upgrades;
  this.pingTimeout = data.pingTimeout;
  this.pingInterval = data.pingInterval;
  this.onOpen();
  this.setPingTimeout();
};

/**
 * Clears and sets a ping timeout based on the expected ping interval.
 *
 * @api private
 */

Socket.prototype.setPingTimeout = function () {
  clearTimeout(this.pingTimeoutTimer);
  var self = this;
  this.pingTimeoutTimer = setTimeout(function () {
    self.onClose('ping timeout');
  }, this.pingInterval);
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if (this.writeBuffer.length) {
    // debug: flushing %d packets in socket, this.writeBuffer.length
    this.transport.sendMany(this.writeBuffer);
    this.writeBuffer = [];
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.sendPacket('message', msg);
  return this;
};

/**
 * Sends a packet.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.sendPacket = function (type, data) {
  var packet = { type: type, data: data };
  if ('open' != this.readyState || this.upgrading) {
    // debug: socket send - buffering packet: type "%s" | data "%s", type, data
    this.writeBuffer.push(packet);
  } else {
    // debug: socket send - transporting: type "%s" | data "%s", type, data
    this.transport.send(packet);
  }
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    this.onClose('forced close');
    // debug: socket closing - telling transport to close
    this.transport.close();
  }

  return this;
};

/**
 * Called upon transport error
 *
 * @api private
 */

Socket.prototype.onError = function (err) {
  this.emit('error', err);
  this.onClose('transport error', err);
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function (reason, desc) {
  if ('closed' != this.readyState) {
    // debug: socket close
    this.readyState = 'closed';
    this.emit('close', reason, desc);
    this.onclose && this.onclose.call(this);
  }
};
<commit_msg>Pre-emptively assign an UID for every request.<commit_after>
/**
 * Module dependencies.
 */

var util = require('./util')
  , transports = require('./transports')
  , EventEmitter = require('./event-emitter')

/**
 * Module exports.
 */

module.exports = Socket;

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts = arguments[1] || {};
    opts.host = uri.host;
    opts.secure = uri.scheme == 'https' || uri.scheme == 'wss';
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};

  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.query = opts.query || {};
  this.query.uid = rnd();
  this.upgrade = false !== opts.upgrade;
  this.path = opts.path || '/engine.io'
  this.forceJSONP = !!opts.forceJSONP;
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];

  this.open();
};

/**
 * Inherits from EventEmitter.
 */

util.inherits(Socket, EventEmitter);

/**
 * Creates transport of the given type.
 *
 * @param {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  // debug: creating transport "%s", name
  var query = clone(this.query)
  query.transport = name;

  if (this.sid) {
    query.sid = this.sid;
  }

  var transport = new transports[name]({
      host: this.host
    , port: this.port
    , secure: this.secure
    , path: this.path
    , query: query
    , forceJSONP: this.forceJSONP
  });

  return transport;
};

function clone (obj) {
  var o = {};
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = obj[i];
    }
  }
  return o;
}

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  this.readyState = 'opening';
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  this.setTransport(transport);
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  if (this.transport) {
    // debug: clearing existing transport
    this.transport.removeAllListeners();
  }

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('packet', function (packet) {
      self.onPacket(packet);
    })
    .on('error', function (e) {
      self.onError(e);
    })
    .on('close', function () {
      self.onClose('transport close');
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  // debug: probing transport "%s", name
  var transport = this.createTransport(name, { probe: 1 })
    , self = this

  transport.once('open', function () {
    // debug: probe transport "%s" opened - pinging, name
    transport.send({ type: 'ping', data: 'probe' });
    transport.once('message', function (msg) {
      if ('pong' == msg.type && 'probe' == msg.data) {
        // debug: probe transport "%s" pong - upgrading, name
        self.upgrading = true;
        self.emit('upgrading', name);

        // debug: pausing current transport "%s", self.transport.name
        self.transport.pause(function () {
          self.setTransport(self.transport);
          self.upgrading = false;
          self.flush();
          self.emit('upgrade', name);
        });
      } else {
        // debug: probe transport "%s" failed, name
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  this.once('close', function () {
    // debug: socket closed prematurely - aborting probe
    transport.close();
  });

  this.once('upgrading', function (to) {
    if (to != name) {
      // debug: probe for "%s" succeeded - aborting "%s", to, name
      transport.close();
    }
  });
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  // debug: socket open
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
  this.flush();

  if (this.upgrade && this.transport.pause) {
    // debug: starting upgrade probes
    for (var i = 0, l = this.upgrades.length; i < l; i++) {
      this.probe(this.upgrades[i]);
    }
  }
};

/**
 * Handles a packet.
 *
 * @api private
 */

Socket.prototype.onPacket = function (packet) {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    // debug: socket receive: type "%s" | data "%s", packet.type, packet.data
    switch (packet.type) {
      case 'open':
        this.onHandshake(util.parseJSON(packet.data));
        break;

      case 'ping':
        this.sendPacket('pong');
        this.setPingTimeout();
        break;

      case 'error':
        var err = new Error('server error');
        err.code = packet.data;
        this.emit('error', err);
        break;

      case 'message':
        this.emit('message', packet.data);
        this.onmessage && this.onmessage.call(this, packet.data);
        break;
    }
  } else {
    // debug: packet received with socket readyState "%s", this.readyState
  }
};

/**
 * Called upon handshake completion.
 *
 * @param {Object} handshake obj
 * @api private
 */

Socket.prototype.onHandshake = function (data) {
  this.emit('handshake', data);
  this.sid = data.sid;
  this.transport.query.sid = data.sid;
  this.upgrades = data.upgrades;
  this.pingTimeout = data.pingTimeout;
  this.pingInterval = data.pingInterval;
  this.onOpen();
  this.setPingTimeout();
};

/**
 * Clears and sets a ping timeout based on the expected ping interval.
 *
 * @api private
 */

Socket.prototype.setPingTimeout = function () {
  clearTimeout(this.pingTimeoutTimer);
  var self = this;
  this.pingTimeoutTimer = setTimeout(function () {
    self.onClose('ping timeout');
  }, this.pingInterval);
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if (this.writeBuffer.length) {
    // debug: flushing %d packets in socket, this.writeBuffer.length
    this.transport.sendMany(this.writeBuffer);
    this.writeBuffer = [];
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.sendPacket('message', msg);
  return this;
};

/**
 * Sends a packet.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.sendPacket = function (type, data) {
  var packet = { type: type, data: data };
  if ('open' != this.readyState || this.upgrading) {
    // debug: socket send - buffering packet: type "%s" | data "%s", type, data
    this.writeBuffer.push(packet);
  } else {
    // debug: socket send - transporting: type "%s" | data "%s", type, data
    this.transport.send(packet);
  }
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    this.onClose('forced close');
    // debug: socket closing - telling transport to close
    this.transport.close();
  }

  return this;
};

/**
 * Called upon transport error
 *
 * @api private
 */

Socket.prototype.onError = function (err) {
  this.emit('error', err);
  this.onClose('transport error', err);
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function (reason, desc) {
  if ('closed' != this.readyState) {
    // debug: socket close
    this.readyState = 'closed';
    this.emit('close', reason, desc);
    this.onclose && this.onclose.call(this);
  }
};

/**
 * Generates a random uid.
 *
 * @api private
 */

function rnd () {
  return String(Math.random()).substr(5) + String(Math.random()).substr(5);
}
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/*
 * A bucket that can be used to store spherical objects.  Manages the addition
 * and removal of the spheres, and stacks them as the are added, and manages
 * the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the
 * code to understand the "contract" between the bucket and the spheres.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // Includes
  var Vector2 = require( 'DOT/Vector2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Bucket = require( 'PHETCOMMON/model/Bucket' );

  function SphereBucket( options ) {
    Bucket.call( this, options );
    this.sphereRadius = options.sphereRadius || 10;
    this.yOffset = -this.sphereRadius * 0.4; // Empirically determined, for positioning particles inside the bucket.
    this.particles = [];
  }

  // Inherit from base type.
  inherit( Bucket, SphereBucket );

  SphereBucket.prototype.addParticleFirstOpen = function( particle, animate ) {
    particle.destination = this._getFirstOpenLocation();
    if ( !animate ) {
      particle.position = particle.destination;
    }
    this.particles.push( particle );
    var thisBucket = this;
    particle.userControlledProperty.once( function( userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } );
  };

  SphereBucket.prototype.addParticleNearestOpen = function( particle, animate ) {
    particle.destination = this.getNearestOpenLocation( particle.destination );
    if ( !animate ) {
      particle.position = particle.destination;
    }
    this.particles.push( particle );
    var thisBucket = this;
    particle.userControlledProperty.once( function( userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } );
  };

  SphereBucket.prototype.removeParticle = function( particle ) {
    if ( this.particles.indexOf( particle ) === -1 ) {
      console.log( "Error: Attempt to remove particle not contained in bucket, ignoring." );
      return;
    }
    this.particles = _.without( this.particles, particle );
    this.relayoutBucketParticles();
  };

  SphereBucket.prototype.containsParticle = function( particle ) {
    return this.particles.indexOf( particle ) !== -1;
  };

  SphereBucket.prototype.extractClosestParticle = function( location ) {
    var closestParticle = null;
    this.particles.forEach( function( particle ) {
      if ( closestParticle === null || closestParticle.position.distance( location ) > particle.position.distance( location ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {
      this.removeParticle( closestParticle );
    }
    return closestParticle;
  };

  SphereBucket.prototype._isPositionOpen = function( position ) {
    var positionOpen = true;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var particle = this.particles[ i ];
      if ( particle.destination.equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  };

  SphereBucket.prototype._getFirstOpenLocation = function() {
    var openLocation = Vector2.ZERO;
    var usableWidth = this.size.width - 2 * this.sphereRadius;
    var offsetFromBucketEdge = this.sphereRadius * 2;
    var numParticlesInLayer = Math.floor( usableWidth / ( this.sphereRadius * 2 ) );
    var row = 0;
    var positionInLayer = 0;
    var found = false;
    while ( !found ) {
      var testLocation = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.sphereRadius,
        this._getYPositionForLayer( row ) );
      if ( this._isPositionOpen( testLocation ) ) {
        // We found a location that is open.
        openLocation = testLocation;
        found = true;
        continue;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this.sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this.sphereRadius;
          }
        }
      }
    }
    return openLocation;
  };

  SphereBucket.prototype._getLayerForYPosition = function( yPosition ) {
    return Math.abs( Math.round( ( yPosition - ( this.position.y + this.yOffset ) ) / ( this.sphereRadius * 2 * 0.866 ) ) );
  };

  /*
   * Get the nearest open location to the provided current location.  This
   * is used for particle stacking.
   *
   * @param location
   * @return
   */
  SphereBucket.prototype.getNearestOpenLocation = function( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    var highestOccupiedLayer = 0;
    var self = this;
    _.each( this.particles, function( particle ) {
      var layer = self._getLayerForYPosition( particle.destination.y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open locations in the occupied layers.
    var openLocations = [];
    var placeableWidth = this.size.width - 2 * this.sphereRadius;
    var offsetFromBucketEdge = ( this.size.width - placeableWidth ) / 2 + this.sphereRadius;
    var numParticlesInLayer = Math.floor( placeableWidth / ( this.sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( var layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open locations in the current layer.
      for ( var positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        var testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.sphereRadius,
          this._getYPositionForLayer( layer ) );
        if ( this._isPositionOpen( testPosition ) ) {

          // We found a location that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {
            // This is a valid open location.
            openLocations.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this.sphereRadius;
      if ( numParticlesInLayer === 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no locations that are open within it, this algorithm
        // classifies the locations directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this.sphereRadius;
      }
    }

    // Find the closest open location to the provided current location.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    var closestOpenLocation = openLocations[0] || Vector2.ZERO;

    _.each( openLocations, function( location ) {
      if ( location.distance( position ) < closestOpenLocation.distance( position ) ) {
        // This location is closer.
        closestOpenLocation = location;
      }
    } );
    return closestOpenLocation;
  };

  SphereBucket.prototype._getYPositionForLayer = function( layer ) {
    return this.position.y + this.yOffset + layer * this.sphereRadius * 2 * 0.866;
  };

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   */
  SphereBucket.prototype._isDangling = function( particle ) {
    var onBottomRow = particle.destination.y === this.position.y + this.yOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destination ) < 2;
  };

  SphereBucket.prototype.countSupportingParticles = function( position ) {
    var count = 0;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var p = this.particles[i];
      if ( p.destination.y < position.y && // Must be in a lower layer
           p.destination.distance( position ) < this.sphereRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  };

  SphereBucket.prototype.relayoutBucketParticles = function() {
    var particleMoved;
    do {
      for ( var i = 0; i < this.particles.length; i++ ) {
        particleMoved = false;
        var particle = this.particles[i];
        if ( this._isDangling( particle ) ) {
          particle.destination = this.getNearestOpenLocation( particle.destination );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  };

  return SphereBucket;
} );
<commit_msg>Fixed an issue where the bucket drag handler was trying to remove the same particle twice.<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/*
 * A bucket that can be used to store spherical objects.  Manages the addition
 * and removal of the spheres, and stacks them as the are added, and manages
 * the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the
 * code to understand the "contract" between the bucket and the spheres.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // Includes
  var Vector2 = require( 'DOT/Vector2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Bucket = require( 'PHETCOMMON/model/Bucket' );

  function SphereBucket( options ) {
    Bucket.call( this, options );
    this.sphereRadius = options.sphereRadius || 10;
    this.yOffset = -this.sphereRadius * 0.4; // Empirically determined, for positioning particles inside the bucket.
    this.particles = [];
  }

  // Inherit from base type.
  inherit( Bucket, SphereBucket );

  SphereBucket.prototype.addParticleFirstOpen = function( particle, animate ) {
    particle.destination = this._getFirstOpenLocation();
    if ( !animate ) {
      particle.position = particle.destination;
    }
    this.particles.push( particle );
    var thisBucket = this;
    particle.userControlledProperty.once( function( userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } );
  };

  SphereBucket.prototype.addParticleNearestOpen = function( particle, animate ) {
    particle.destination = this.getNearestOpenLocation( particle.destination );
    if ( !animate ) {
      particle.position = particle.destination;
    }
    this.particles.push( particle );
    var thisBucket = this;
    particle.userControlledProperty.once( function( userControlled ) {
      if ( userControlled ) {
        thisBucket.removeParticle( particle );
      }
    } );
  };

  SphereBucket.prototype.removeParticle = function( particle ) {
    if ( this.particles.indexOf( particle ) === -1 ) {
      debugger;
      console.log( "Error: Attempt to remove particle not contained in bucket, ignoring." );
      return;
    }
    this.particles = _.without( this.particles, particle );
    this.relayoutBucketParticles();
  };

  SphereBucket.prototype.containsParticle = function( particle ) {
    return this.particles.indexOf( particle ) !== -1;
  };

  SphereBucket.prototype.extractClosestParticle = function( location ) {
    var closestParticle = null;
    this.particles.forEach( function( particle ) {
      if ( closestParticle === null || closestParticle.position.distance( location ) > particle.position.distance( location ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {
      // The particle is removed by setting 'userControlled' to true.  This
      // relies on the listener that was added when the particle was placed
      // into the bucket.
      closestParticle.userControlled = true;
    }
    return closestParticle;
  };

  SphereBucket.prototype._isPositionOpen = function( position ) {
    var positionOpen = true;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var particle = this.particles[ i ];
      if ( particle.destination.equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  };

  SphereBucket.prototype._getFirstOpenLocation = function() {
    var openLocation = Vector2.ZERO;
    var usableWidth = this.size.width - 2 * this.sphereRadius;
    var offsetFromBucketEdge = this.sphereRadius * 2;
    var numParticlesInLayer = Math.floor( usableWidth / ( this.sphereRadius * 2 ) );
    var row = 0;
    var positionInLayer = 0;
    var found = false;
    while ( !found ) {
      var testLocation = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.sphereRadius,
        this._getYPositionForLayer( row ) );
      if ( this._isPositionOpen( testLocation ) ) {
        // We found a location that is open.
        openLocation = testLocation;
        found = true;
        continue;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this.sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this.sphereRadius;
          }
        }
      }
    }
    return openLocation;
  };

  SphereBucket.prototype._getLayerForYPosition = function( yPosition ) {
    return Math.abs( Math.round( ( yPosition - ( this.position.y + this.yOffset ) ) / ( this.sphereRadius * 2 * 0.866 ) ) );
  };

  /*
   * Get the nearest open location to the provided current location.  This
   * is used for particle stacking.
   *
   * @param location
   * @return
   */
  SphereBucket.prototype.getNearestOpenLocation = function( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    var highestOccupiedLayer = 0;
    var self = this;
    _.each( this.particles, function( particle ) {
      var layer = self._getLayerForYPosition( particle.destination.y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open locations in the occupied layers.
    var openLocations = [];
    var placeableWidth = this.size.width - 2 * this.sphereRadius;
    var offsetFromBucketEdge = ( this.size.width - placeableWidth ) / 2 + this.sphereRadius;
    var numParticlesInLayer = Math.floor( placeableWidth / ( this.sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( var layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open locations in the current layer.
      for ( var positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        var testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this.sphereRadius,
          this._getYPositionForLayer( layer ) );
        if ( this._isPositionOpen( testPosition ) ) {

          // We found a location that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {
            // This is a valid open location.
            openLocations.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this.sphereRadius;
      if ( numParticlesInLayer === 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no locations that are open within it, this algorithm
        // classifies the locations directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this.sphereRadius;
      }
    }

    // Find the closest open location to the provided current location.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    var closestOpenLocation = openLocations[0] || Vector2.ZERO;

    _.each( openLocations, function( location ) {
      if ( location.distance( position ) < closestOpenLocation.distance( position ) ) {
        // This location is closer.
        closestOpenLocation = location;
      }
    } );
    return closestOpenLocation;
  };

  SphereBucket.prototype._getYPositionForLayer = function( layer ) {
    return this.position.y + this.yOffset + layer * this.sphereRadius * 2 * 0.866;
  };

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   */
  SphereBucket.prototype._isDangling = function( particle ) {
    var onBottomRow = particle.destination.y === this.position.y + this.yOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destination ) < 2;
  };

  SphereBucket.prototype.countSupportingParticles = function( position ) {
    var count = 0;
    for ( var i = 0; i < this.particles.length; i++ ) {
      var p = this.particles[i];
      if ( p.destination.y < position.y && // Must be in a lower layer
           p.destination.distance( position ) < this.sphereRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  };

  SphereBucket.prototype.relayoutBucketParticles = function() {
    var particleMoved;
    do {
      for ( var i = 0; i < this.particles.length; i++ ) {
        particleMoved = false;
        var particle = this.particles[i];
        if ( this._isDangling( particle ) ) {
          particle.destination = this.getNearestOpenLocation( particle.destination );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  };

  return SphereBucket;
} );
<|endoftext|>


<commit_before>var SockJS = function(url, protocols_whitelist, options) {
    var that = this;
    that._options = {devel: false, debug: false, info: undefined};
    if (options) {
        utils.objectExtend(that._options, options);
    }
    that._base_url = utils.amendUrl(url);
    that._server = that._options.server || utils.random_number_string(1000);
    if (typeof protocols_whitelist === 'string') {
        protocols_whitelist = [protocols_whitelist];
    } else if (!utils.isArray(protocols_whitelist)) {
        protocols_whitelist = [];
    }
    that._protocols = [];
    that.protocol = null;
    that.readyState = SockJS.CONNECTING;
    var ir = createInfoReceiver(that._base_url);
    ir.onfinish = function(info, rtt) {
        if (info) {
            if (that._options.info) {
                // Override if user supplies the option
                info = that._options.info;
            }
            that._applyInfo(info, rtt, protocols_whitelist);
            that._didClose();
        } else {
            that._didClose(1002, 'Can\'t connect to server', true);
        }
    };
};
// Inheritance
SockJS.prototype = new REventTarget();

SockJS.version = "<!-- version -->";

SockJS.CONNECTING = 0;
SockJS.OPEN = 1;
SockJS.CLOSING = 2;
SockJS.CLOSED = 3;

SockJS.prototype._debug = function() {
    if (this._options.debug)
        utils.log.apply(utils, arguments);
};

SockJS.prototype._dispatchOpen = function() {
    var that = this;
    if (that.readyState === SockJS.CONNECTING) {
        if (that._transport_tref) {
            clearTimeout(that._transport_tref);
            that._transport_tref = null;
        }
        that.readyState = SockJS.OPEN;
        that.dispatchEvent(new SimpleEvent("open"));
    } else {
        // The server might have been restarted, and lost track of our
        // connection.
        that._didClose(1006, "Server lost session");
    }
};

SockJS.prototype._dispatchMessage = function(data) {
    var that = this;
    if (that.readyState !== SockJS.OPEN)
            return;
    that.dispatchEvent(new SimpleEvent("message", {data: data}));
};

SockJS.prototype._dispatchHeartbeat = function(data) {
    var that = this;
    if (that.readyState !== SockJS.OPEN)
        return;
    that.dispatchEvent(new SimpleEvent('heartbeat', {}));
};

SockJS.prototype._didClose = function(code, reason, force) {
    var that = this;
    if (that.readyState !== SockJS.CONNECTING &&
        that.readyState !== SockJS.OPEN &&
        that.readyState !== SockJS.CLOSING)
            throw new Error('INVALID_STATE_ERR');
    if (that._transport)
        that._transport.doCleanup();
    that._transport = null;

    var close_event = new SimpleEvent("close", {
        code: code,
        reason: reason,
        wasClean: utils.userSetCode(code)});

    if (!utils.userSetCode(code) &&
        that.readyState === SockJS.CONNECTING && !force) {
        if (that._try_next_protocol(close_event)) {
            return;
        }
        close_event = new SimpleEvent("close", {code: 2000,
                                                reason: "All transports failed",
                                                wasClean: false,
                                                last_event: close_event});
    }
    that.readyState = SockJS.CLOSED;

    utils.delay(function() {
                   that.dispatchEvent(close_event);
                });
};

SockJS.prototype._didMessage = function(data) {
    var that = this;
    var type = data.slice(0, 1);
    switch(type) {
    case 'o':
        that._dispatchOpen();
        break;
    case 'a':
        var payload = JSON.parse(data.slice(1) || '[]');
        for(var i=0; i < payload.length; i++){
            that._dispatchMessage(payload[i]);
        }
        break;
    case 'm':
        var payload = JSON.parse(data.slice(1) || 'null');
        that._dispatchMessage(payload);
        break;
    case 'c':
        var payload = JSON.parse(data.slice(1) || '[]');
        that._didClose(payload[0], payload[1]);
        break;
    case 'h':
        that._dispatchHeartbeat();
        break;
    }
};

SockJS.prototype._try_next_protocol = function(close_event) {
    var that = this;
    if (that.protocol) {
        that._debug('Closed transport:', that.protocol, ''+close_event);
        that.protocol = null;
    }
    if (that._transport_tref) {
        clearTimeout(that._transport_tref);
        that._transport_tref = null;
    }

    while(1) {
        var protocol = that.protocol = that._protocols.shift();
        if (!protocol) {
            return false;
        }
        // Some protocols require access to `body`, what if were in
        // the `head`?
        if (SockJS[protocol] &&
            SockJS[protocol].need_body === true &&
            !_document.body) {
            that._protocols.unshift(protocol);
            that.protocol = 'waiting-for-load';
            utils.attachEvent('load', function(){
                that._try_next_protocol();
            });
            return true;
        }

        if (!SockJS[protocol] ||
              !SockJS[protocol].enabled(that._options)) {
            that._debug('Skipping transport:', protocol);
        } else {
            var roundTrips = SockJS[protocol].roundTrips || 1;
            var to = ((that._options.rto || 0) * roundTrips) || 5000;
            that._transport_tref = utils.delay(to, function() {
                if (that.readyState === SockJS.CONNECTING) {
                    // I can't understand how it is possible to run
                    // this timer, when the state is CLOSED, but
                    // apparently in IE everythin is possible.
                    that._didClose(2007, "Transport timeouted");
                }
            });

            var connid = utils.random_string(8);
            var trans_url = that._base_url + '/' + that._server + '/' + connid;
            that._debug('Opening transport:', protocol, ' url:'+trans_url,
                        ' RTO:'+that._options.rto);
            that._transport = new SockJS[protocol](that, trans_url,
                                                   that._base_url);
            return true;
        }
    }
};

SockJS.prototype.close = function(code, reason) {
    var that = this;
    if (code && !utils.userSetCode(code))
        throw new Error("INVALID_ACCESS_ERR");
    if(that.readyState !== SockJS.CONNECTING &&
       that.readyState !== SockJS.OPEN) {
        return false;
    }
    that.readyState = SockJS.CLOSING;
    that._didClose(code || 1000, reason || "Normal closure");
    return true;
};

SockJS.prototype.send = function(data) {
    var that = this;
    if (that.readyState === SockJS.CONNECTING)
        throw new Error('INVALID_STATE_ERR');
    if (that.readyState === SockJS.OPEN) {
        that._transport.doSend(utils.quote('' + data));
    }
    return true;
};

SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) {
    var that = this;
    that._options.info = info;
    that._options.rtt = rtt;
    that._options.rto = utils.countRTO(rtt);
    var probed = utils.probeProtocols();
    that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
};
<commit_msg>Make it possible to override rtt.<commit_after>var SockJS = function(url, protocols_whitelist, options) {
    var that = this;
    that._options = {devel: false, debug: false, info: undefined, rtt: undefined};
    if (options) {
        utils.objectExtend(that._options, options);
    }
    that._base_url = utils.amendUrl(url);
    that._server = that._options.server || utils.random_number_string(1000);
    if (typeof protocols_whitelist === 'string' &&
        protocols_whitelist.length > 0) {
        protocols_whitelist = [protocols_whitelist];
    } else if (!utils.isArray(protocols_whitelist)) {
        protocols_whitelist = null;
    }
    that._protocols = [];
    that.protocol = null;
    that.readyState = SockJS.CONNECTING;
    var ir = createInfoReceiver(that._base_url);
    ir.onfinish = function(info, rtt) {
        if (info) {
            if (that._options.info) {
                // Override if user supplies the option
                info = that._options.info;
            }
            if (that._options.rtt) {
                rtt = that._options.rtt;
            }
            that._applyInfo(info, rtt, protocols_whitelist);
            that._didClose();
        } else {
            that._didClose(1002, 'Can\'t connect to server', true);
        }
    };
};
// Inheritance
SockJS.prototype = new REventTarget();

SockJS.version = "<!-- version -->";

SockJS.CONNECTING = 0;
SockJS.OPEN = 1;
SockJS.CLOSING = 2;
SockJS.CLOSED = 3;

SockJS.prototype._debug = function() {
    if (this._options.debug)
        utils.log.apply(utils, arguments);
};

SockJS.prototype._dispatchOpen = function() {
    var that = this;
    if (that.readyState === SockJS.CONNECTING) {
        if (that._transport_tref) {
            clearTimeout(that._transport_tref);
            that._transport_tref = null;
        }
        that.readyState = SockJS.OPEN;
        that.dispatchEvent(new SimpleEvent("open"));
    } else {
        // The server might have been restarted, and lost track of our
        // connection.
        that._didClose(1006, "Server lost session");
    }
};

SockJS.prototype._dispatchMessage = function(data) {
    var that = this;
    if (that.readyState !== SockJS.OPEN)
            return;
    that.dispatchEvent(new SimpleEvent("message", {data: data}));
};

SockJS.prototype._dispatchHeartbeat = function(data) {
    var that = this;
    if (that.readyState !== SockJS.OPEN)
        return;
    that.dispatchEvent(new SimpleEvent('heartbeat', {}));
};

SockJS.prototype._didClose = function(code, reason, force) {
    var that = this;
    if (that.readyState !== SockJS.CONNECTING &&
        that.readyState !== SockJS.OPEN &&
        that.readyState !== SockJS.CLOSING)
            throw new Error('INVALID_STATE_ERR');
    if (that._transport)
        that._transport.doCleanup();
    that._transport = null;

    var close_event = new SimpleEvent("close", {
        code: code,
        reason: reason,
        wasClean: utils.userSetCode(code)});

    if (!utils.userSetCode(code) &&
        that.readyState === SockJS.CONNECTING && !force) {
        if (that._try_next_protocol(close_event)) {
            return;
        }
        close_event = new SimpleEvent("close", {code: 2000,
                                                reason: "All transports failed",
                                                wasClean: false,
                                                last_event: close_event});
    }
    that.readyState = SockJS.CLOSED;

    utils.delay(function() {
                   that.dispatchEvent(close_event);
                });
};

SockJS.prototype._didMessage = function(data) {
    var that = this;
    var type = data.slice(0, 1);
    switch(type) {
    case 'o':
        that._dispatchOpen();
        break;
    case 'a':
        var payload = JSON.parse(data.slice(1) || '[]');
        for(var i=0; i < payload.length; i++){
            that._dispatchMessage(payload[i]);
        }
        break;
    case 'm':
        var payload = JSON.parse(data.slice(1) || 'null');
        that._dispatchMessage(payload);
        break;
    case 'c':
        var payload = JSON.parse(data.slice(1) || '[]');
        that._didClose(payload[0], payload[1]);
        break;
    case 'h':
        that._dispatchHeartbeat();
        break;
    }
};

SockJS.prototype._try_next_protocol = function(close_event) {
    var that = this;
    if (that.protocol) {
        that._debug('Closed transport:', that.protocol, ''+close_event);
        that.protocol = null;
    }
    if (that._transport_tref) {
        clearTimeout(that._transport_tref);
        that._transport_tref = null;
    }

    while(1) {
        var protocol = that.protocol = that._protocols.shift();
        if (!protocol) {
            return false;
        }
        // Some protocols require access to `body`, what if were in
        // the `head`?
        if (SockJS[protocol] &&
            SockJS[protocol].need_body === true &&
            !_document.body) {
            that._protocols.unshift(protocol);
            that.protocol = 'waiting-for-load';
            utils.attachEvent('load', function(){
                that._try_next_protocol();
            });
            return true;
        }

        if (!SockJS[protocol] ||
              !SockJS[protocol].enabled(that._options)) {
            that._debug('Skipping transport:', protocol);
        } else {
            var roundTrips = SockJS[protocol].roundTrips || 1;
            var to = ((that._options.rto || 0) * roundTrips) || 5000;
            that._transport_tref = utils.delay(to, function() {
                if (that.readyState === SockJS.CONNECTING) {
                    // I can't understand how it is possible to run
                    // this timer, when the state is CLOSED, but
                    // apparently in IE everythin is possible.
                    that._didClose(2007, "Transport timeouted");
                }
            });

            var connid = utils.random_string(8);
            var trans_url = that._base_url + '/' + that._server + '/' + connid;
            that._debug('Opening transport:', protocol, ' url:'+trans_url,
                        ' RTO:'+that._options.rto);
            that._transport = new SockJS[protocol](that, trans_url,
                                                   that._base_url);
            return true;
        }
    }
};

SockJS.prototype.close = function(code, reason) {
    var that = this;
    if (code && !utils.userSetCode(code))
        throw new Error("INVALID_ACCESS_ERR");
    if(that.readyState !== SockJS.CONNECTING &&
       that.readyState !== SockJS.OPEN) {
        return false;
    }
    that.readyState = SockJS.CLOSING;
    that._didClose(code || 1000, reason || "Normal closure");
    return true;
};

SockJS.prototype.send = function(data) {
    var that = this;
    if (that.readyState === SockJS.CONNECTING)
        throw new Error('INVALID_STATE_ERR');
    if (that.readyState === SockJS.OPEN) {
        that._transport.doSend(utils.quote('' + data));
    }
    return true;
};

SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) {
    var that = this;
    that._options.info = info;
    that._options.rtt = rtt;
    that._options.rto = utils.countRTO(rtt);
    var probed = utils.probeProtocols();
    that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
};
<|endoftext|>