<commit_before>'use strict';

var Parser = module.exports = {};

function ScalarNode(value) {
  this.type = 'text';
  this.value = value;
}

function VariableNode(anchor) {
  this.type = 'variable';
  this.anchor = anchor;
}

function PluralNode(anchor, forms) {
  this.type = 'plural';
  this.anchor = anchor;
  this.forms = forms;
}

// finds macros in the translations
/*var MACROS_REGEXP = new RegExp(
  "(^|[^\\\\])" + // [1] match begining of string or any char except \ (backslash)
    "([#%])" + // [2] match operation notation # or %
    "{((?:[^\\\\}]|\\})+)}" + // [3] arguments
    "(?::([_$a-z](?:[_$a-z0-9.]*?[_$a-z0-9])?))?" + // [4] plural anchor
    "(?:[^_$a-z0-9]|$)",
  "i"
);*/

// catch valib variable and object accessor notation
var VAR_REGEXP_SOURCE = '([a-z_$](?:[a-z0-9_$]*|\\.[a-z_$][a-z0-9_$]*)*)';

var MACROS_REGEXP = new RegExp(
  '(^|.*?[^\\\\])' +            // either nothing, or anything which doesn't end with \ (backslash)
    '(?:' +
      '#{' +                    // interpolation
        VAR_REGEXP_SOURCE +     // interpolating variable
      '}' +
    '|' +
      '%{' +                    // pluralization
        '(.*?[^\\\\])(?=})' +   // word forms
      '}:' +
      VAR_REGEXP_SOURCE +       // controlling variable
    ')',
  'i'
);

// used to split arguments of plural
var FORMS_SEPARATOR_REGEXP = new RegExp("(^|[^\\\\])[|]", 'g');

// used to unescape critical chars
var UNESCAPE_CHARS = new RegExp("\\\\([#%}])", 'g');
function unescapeString(str) {
  return str.replace(UNESCAPE_CHARS, '$1');
}

function parseForms(str) {
  return str
    // split by non-escaped '|'
    .replace(FORMS_SEPARATOR_REGEXP, '$1( . )( . )')
    .split('( . )( . )')
    // strip escaping
    .map(unescapeString);
}

// parses string into array of nodes
Parser.parse = function parse(str) {
  var nodes = [], match;

  while (!!str.length) {
    match = str.match(MACROS_REGEXP);

    if (null === match) {
      nodes.push(new ScalarNode(unescapeString(str)));
      break;
    }

    // we have scalars before macros
    if (match[1] && 0 !== match[1].length) {
      nodes.push(new ScalarNode(unescapeString(match[1])));
    }

    // got variable node
    if (undefined !== match[2]) {
      nodes.push(new VariableNode(match[2]));
    // got plurals
    } else {
      nodes.push(new PluralNode(match[4], parseForms(match[3])));
    }

    // remove processed data
    str = str.slice(match.index + match[0].length);
  }

  return nodes;
};

// export RegExps for unit testing
Parser.MACROS_REGEXP = MACROS_REGEXP;
Parser.VAR_REGEXP_SOURCE = VAR_REGEXP_SOURCE;
<commit_msg>Fix unescape regexp<commit_after>'use strict';

var Parser = module.exports = {};

function ScalarNode(value) {
  this.type = 'text';
  this.value = value;
}

function VariableNode(anchor) {
  this.type = 'variable';
  this.anchor = anchor;
}

function PluralNode(anchor, forms) {
  this.type = 'plural';
  this.anchor = anchor;
  this.forms = forms;
}

// catch valid variable and object accessor notation
var ANCHOR_REGEXP = new RegExp(
  '([a-z_$](?:[a-z0-9_$]*|\\.[a-z_$][a-z0-9_$]*)*)',
  'i'
);

// finds macros in the translations
/*var MACROS_REGEXP = new RegExp(
  "(^|[^\\\\])" + // [1] match begining of string or any char except \ (backslash)
    "([#%])" + // [2] match operation notation # or %
    "{((?:[^\\\\}]|\\})+)}" + // [3] arguments
    "(?::([_$a-z](?:[_$a-z0-9.]*?[_$a-z0-9])?))?" + // [4] plural anchor
    "(?:[^_$a-z0-9]|$)",
  "i"
);*/
var MACROS_REGEXP = new RegExp(
  '(^|.*?[^\\\\])' +            // either nothing, or anything which doesn't end with \ (backslash)
    '(?:' +
      '#{' +                    // interpolation
        ANCHOR_REGEXP.source +  // interpolating variable
      '}' +
    '|' +
      '%{' +                    // pluralization
        '(.*?[^\\\\])(?=})' +   // word forms
      '}:' +
      ANCHOR_REGEXP.source +    // controlling variable
    ')',
  'i'
);

// used to unescape critical chars
var UNESCAPE_CHARS = new RegExp('\\\\([#%}|\\\\])', 'g');
function unescapeString(str) {
  return str.replace(UNESCAPE_CHARS, '$1');
}

// used to split arguments of plural
var FORMS_SEPARATOR_REGEXP = new RegExp("(^|[^\\\\])[|]", 'g');
function parseForms(str) {
  return str
    // split by non-escaped '|'
    .replace(FORMS_SEPARATOR_REGEXP, '$1( . )( . )')
    .split('( . )( . )')
    // strip escaping
    .map(unescapeString);
}

// parses string into array of nodes
Parser.parse = function parse(str) {
  var nodes = [], match;

  while (!!str.length) {
    match = str.match(MACROS_REGEXP);

    if (null === match) {
      nodes.push(new ScalarNode(unescapeString(str)));
      break;
    }

    // we have scalars before macros
    if (match[1] && 0 !== match[1].length) {
      nodes.push(new ScalarNode(unescapeString(match[1])));
    }

    // got variable node
    if (undefined !== match[2]) {
      nodes.push(new VariableNode(match[2]));
    // got plurals
    } else {
      nodes.push(new PluralNode(match[4], parseForms(match[3])));
    }

    // remove processed data
    str = str.slice(match.index + match[0].length);
  }

  return nodes;
};

// export RegExps for unit testing
Parser.MACROS_REGEXP = MACROS_REGEXP;
Parser.ANCHOR_REGEXP = ANCHOR_REGEXP;
<|endoftext|>


<commit_before>SECOND = 1000;
MINUTE = SECOND * 60;

API_URL = "http://api.openweathermap.org/data/2.5/weather?lat={0}&lon={1}&mode=json";

get_location_and_show_weather = function() {
	navigatior.geolocation.getCurrentPosition(function(e) {
		get_weather(e.coords.longitude, e.coords.latitude);
	});

	setTimeout(get_location_and_show_weather, 5 * MINUTE);
};

get_weather = function(longitute, latitute) {
	var request = new XMLHttpRequest();

	request.open("GET", API_URL.format(longitute, latitute), true);

	request.onload = function(e) {
		if(request.readyState == 4 && request.status == 200) {
			var response = JSON.parse(request.responseText);

			var pebble_data = {};

			pebble_data.icon_id = response.weather.icon;
			pebble_data.icon = get_icon_name_by_id(response.weather.icon);
			pebble_data.temp_kelvin = Number(response.main.temp).toFixed(0);
			pebble_data.temp_celsius = convert_kelvin_to_celsius(Number(response.main.temp)).toFixed(0);
			pebble_data.temp_fahrenheit = convert_kelvin_to_fahrenheit(Number(response.main.temp)).toFixed(0);

			Pebble.sendAppMessage(pebble_data);
		} else {
			console.log("ERROR: connection to weather api failed with code: " + request.status);
		}
	};

	request.send(null);
};

get_icon_name_by_id = function(id) {
	switch(id) {
		case "01d":
			return "clear_day";
		case "01n":
			return "clear_night";
		case "02d":
		case "02n":
		case "03d":
		case "03n":
		case "04d":
		case "04n":
			return "cloudy";
		case "09d":
		case "09n":
			return "shower_rain";
		case "10d":
		case "10n":
			return "rain";
		case "11d":
		case "11n":
			return "thunderstorm";
		case "13d":
		case "13n":
			return "snow";
		case "50d":
		case "50n":
			return "mist";
		default:
			return "error";

	}
};

convert_kelvin_to_celsius = function(kelvin) {
	return kelvin - 273.15;
};

convert_kelvin_to_fahrenheit = function(kelvin) {
	return (kelvin * 1.8) 459.67;
};

Pebble.addEventListener("ready", function(e) {
	setTimeout(get_location_and_show_weather, 1000);
});
<commit_msg>fix pebblekit js code<commit_after>SECOND = 1000;
MINUTE = SECOND * 60;

API_URL = "http://api.openweathermap.org/data/2.5/weather?lat={0}&lon={1}&mode=json";

get_location_and_show_weather = function() {
	navigator.geolocation.getCurrentPosition(function(e) {
		get_weather(e.coords.longitude, e.coords.latitude);
	});

	setTimeout(get_location_and_show_weather, 5 * MINUTE);
};

get_weather = function(longitute, latitute) {
	var request = new XMLHttpRequest();

	request.open("GET", API_URL.format(longitute, latitute), true);

	request.onload = function(e) {
		if(request.readyState == 4 && request.status == 200) {
			var response = JSON.parse(request.responseText);

			console.log("ANSWER FROM SERVER: " + request.responseText);

			var pebble_data = {
				"0": get_icon_id(response.weather[0].icon),
				"1": Number(Number(response.main.temp).toFixed(0)),
				"2": Number(convert_kelvin_to_celsius(Number(response.main.temp)).toFixed(0)),
				"3": Number(convert_kelvin_to_fahrenheit(Number(response.main.temp)).toFixed(0))
			};

			Pebble.sendAppMessage(pebble_data);
			console.log("Send: " + JSON.stringify(pebble_data, 4) + " to pebble");
		} else {
			console.log("ERROR: connection to weather api failed with code: " + request.status);
		}
	};

	request.send(null);
};

get_icon_id = function(key) {
	switch(key) {
		// clear_day
		case "01d":
			return 0;
		// clear_night
		case "01n":
			return 1;
		// cloudy
		case "02d":
		case "02n":
		case "03d":
		case "03n":
		case "04d":
		case "04n":
			return 2;
		// shower_rain
		case "09d":
		case "09n":
			return 3;
		// rain
		case "10d":
		case "10n":
			return 4;
		case "11d":
		case "11n":
		// thunderstorm
			return 5;
		// snow
		case "13d":
		case "13n":
			return 6;
		// mist
		case "50d":
		case "50n":
			return 7;
		// error
		default:
			return 8;

	}
};

convert_kelvin_to_celsius = function(kelvin) {
	return kelvin - 273.15;
};

convert_kelvin_to_fahrenheit = function(kelvin) {
	return (kelvin * 1.8) - 459.67;
};

// String.format seems to be undefined in pebble
if(!String.prototype.format) {
	String.prototype.format = function() {
		var format_replace = function(match, number) {
			return typeof(arguments[number] != undefined ? arguments[number] : match);
		};

		return this.replace(/{(\d+)}/g, format_replace)
	};
}

Pebble.addEventListener("ready", function(e) {
	setTimeout(get_location_and_show_weather, 1000);
});
<|endoftext|>


<commit_before>Pebble.addEventListener('ready', function() {
	console.log('PebbleKit JS ready!');
});

Pebble.addEventListener('showConfiguration', function() {
	var url = 'http://e0acbef6.ngrok.io';

	console.log('Showing configuration page: ' + url);

	Pebble.openURL(url);
});

Pebble.addEventListener('webviewclosed', function(e) {
	var configData = JSON.parse(decodeURIComponent(e.response));

	console.log('Configuration page returned: ' + JSON.stringify(configData));

	if (configData.backgroundColor) {
		Pebble.sendAppMessage({
			backgroundColor : parseInt(configData.backgroundColor, 16),
			heartColor : parseInt(configData.heartColor)
		}, function() {
			console.log("send successful!");
		}, function() {
			console.log("send failed!");
		});
	}
});
<commit_msg>update where configuration page is currently hosted<commit_after>Pebble.addEventListener('ready', function() {
	console.log('PebbleKit JS ready!');
});

Pebble.addEventListener('showConfiguration', function() {
	var url = 'http://armstrongwill.com/pebbleconfigurationpages/minimalhearts/index.html';

	console.log('Showing configuration page: ' + url);

	Pebble.openURL(url);
});

Pebble.addEventListener('webviewclosed', function(e) {
	var configData = JSON.parse(decodeURIComponent(e.response));

	console.log('Configuration page returned: ' + JSON.stringify(configData));

	if (configData.backgroundColor) {
		Pebble.sendAppMessage({
			backgroundColor : parseInt(configData.backgroundColor, 16),
			heartColor : parseInt(configData.heartColor)
		}, function() {
			console.log("send successful!");
		}, function() {
			console.log("send failed!");
		});
	}
});
<|endoftext|>


<commit_before>// Ajax module to query the APIs
Ajax = (function() {
    var requestObject = new XMLHttpRequest();
    var _formatData = function(data) {
        data.type = data.type || "GET";
        return data;
    };

    var _request = function(data, okCallback, errorCallback) {
        data = _formatData(data);
        requestObject.open(data.type, data.url, true);
        requestObject.onreadystatechange = function(e) {
            if (requestObject.readyState == 4) {
                var response = JSON.parse(requestObject.responseText);
                if (requestObject.status == 200) {
                    console.log(requestObject);
                    okCallback(response);
                } else {

                    console.log(response);
                    errorCallback(response);
                }
            }
        };
        requestObject.send();
    };

    return {
        request: function(data, okCallback, errorCallback) {
            _request(data, okCallback, errorCallback);
        }
    };
}());

// View, the interface for the messages
var View = function() {
    this.messages = {
        iss: {
            title: "ISS Alert",
            visibility: {
                "true": function(direction) {
                    return "The ISS is now visible on the " + direction + "!, take a moment to watch the sky, take a picture, and tweet it to #ISeeTheISS!";
                },
                "false": function() {
                    return "The ISS is no longer visible around you!";
                }
            }
        }
    };
};

View.prototype = {
    notify: function(object, visibility, direction) {
        object = object.toLowerCase();
        visibility = visibility.toString();
        var messages = this.messages[object];

        var title = messages.title;
        var text = messages.visibility[visibility](direction);
        Pebble.showSimpleNotificationOnPebble(title, text);
    }
};

// The user model that handles self localisation, and notifications
var User = function(opts) {
    this.lastCoordUpdate = null;
    this.iss = {
        visible: false,
        direction: null
    };
    this.view = opts.view;
    this.coords = {};
};

User.prototype = {
    getLocation: function() {
        if (this.staleCoordinates()) {
            navigator.geolocation.getCurrentPosition(this.foundCoordinates.bind(this));
        }
        return this.coords;
    },

    foundCoordinates: function(position) {
        this.lastCoordUpdate = position.timestamp;
        this.coords = {
            'latitude': position.coords.latitude,
            'longitude': position.coords.longitude,
            'altitude': position.coords.altitude
        };
    },

    staleCoordinates: function() {
        var now = new Date().getTime();
        return (now - this.lastCoordUpdate > 30000); //five minutes
    },

    setiss: function(iss) {
        if (this.iss.visible != iss.visible || this.iss.direction != iss.direction) {
            this.iss.visible = iss.visible;
            this.iss.direction = iss.direction;
            this.view.notify("ISS", this.iss.visible);
        }
    },
};

// Sets everything in motion
Pebble.addEventListener("ready",
    function(e) {
        var view = new View();
        var user = new User({
            view: view
        });
        var iSS = new ISS({
            user: user
        });
    });<commit_msg>Namespace everything<commit_after>// Ajax module to query the APIs
Ajax = (function() {
    var requestObject = new XMLHttpRequest();
    var _formatData = function(data) {
        data.type = data.type || "GET";
        return data;
    };

    var _request = function(data, okCallback, errorCallback) {
        data = _formatData(data);
        requestObject.open(data.type, data.url, true);
        requestObject.onreadystatechange = function(e) {
            if (requestObject.readyState == 4) {
                var response = JSON.parse(requestObject.responseText);
                if (requestObject.status == 200) {
                    console.log(requestObject);
                    okCallback(response);
                } else {

                    console.log(response);
                    errorCallback(response);
                }
            }
        };
        requestObject.send();
    };

    return {
        request: function(data, okCallback, errorCallback) {
            _request(data, okCallback, errorCallback);
        }
    };
}());

// View, the interface for the messages
var View = function() {
    this.messages = {
        iss: {
            title: "ISS Alert",
            visibility: {
                "true": function(direction) {
                    return "The ISS is now visible on the " + direction + "!, take a moment to watch the sky, take a picture, and tweet it to #ISeeTheISS!";
                },
                "false": function() {
                    return "The ISS is no longer visible around you!";
                }
            }
        }
    };
};

View.prototype = {
    notify: function(object, visibility, direction) {
        object = object.toLowerCase();
        visibility = visibility.toString();
        var messages = this.messages[object];

        var title = messages.title;
        var text = messages.visibility[visibility](direction);
        Pebble.showSimpleNotificationOnPebble(title, text);
    }
};

// The user model that handles self localisation, and notifications
var User = function(opts) {
    this.lastCoordUpdate = null;
    this.iss = {
        visible: false,
        direction: null
    };
    this.view = opts.view;
    this.coords = {};
};

User.prototype = {
    getLocation: function() {
        if (this.staleCoordinates()) {
            navigator.geolocation.getCurrentPosition(this.foundCoordinates.bind(this));
        }
        return this.coords;
    },

    foundCoordinates: function(position) {
        this.lastCoordUpdate = position.timestamp;
        this.coords = {
            'latitude': position.coords.latitude,
            'longitude': position.coords.longitude,
            'altitude': position.coords.altitude
        };
    },

    staleCoordinates: function() {
        var now = new Date().getTime();
        return (now - this.lastCoordUpdate > 30000); //five minutes
    },

    setiss: function(iss) {
        if (this.iss.visible != iss.visible || this.iss.direction != iss.direction) {
            this.iss.visible = iss.visible;
            this.iss.direction = iss.direction;
            this.view.notify("ISS", this.iss.visible);
        }
    },
};

// Global namespacing
SatAlert = {}

// Sets everything in motion
Pebble.addEventListener("ready",
    function(e) {
        SatAlert.view = new View();
        SatAlert.user = new User({
            view: SatAlert.view
        });
        SatAlert.iss = new ISS({
            user: SatAlert.user
        });
    });<|endoftext|>


<commit_before>function formatSMile(v) {
	if(!v || v == "") return "--.--";
	return v.toFixed(2);
}

function formatKnots(v) {
	if(!v || v == "") return "--.-";
	return v.toFixed(1);
}

function formatBft(v) {
	if(!v || v == "") return "-";
	return v.toFixed(0);
}

function formatAngle(v) {
	if(!v || v == "") return "---°";
	var x = v.toFixed(0);
	if (x < -100)
		return '-'+ x*-1 +"°";
	if (x < -10)
		return '-0'+ x*-1 +"°";
	if (x < 0)
		return '-00'+ x*-1 +"°";
	if (x < 10)
		return '00'+ x +"°";
	else if (x < 100)
		return '0'+ x +"°";
	else
		return x + '°';
}

function formatTime(v) {
	if(!v || v == "") return "--:--";

	var d = Math.floor(v / (60 * 60 * 24));
	var div_days = v % (60 * 60 * 24);
	var h = Math.floor(div_days / (60 * 60));
	var div_min = div_days % (60 * 60);
	var m = Math.floor(div_min / 60);
	var div_sec = div_min % 60;
	var sec = Math.ceil(div_sec);

	var time_str = "";
	if (d > 0) {
		if (d < 10)
			time_str += '0';
		time_str += d.toFixed(0);
		time_str += ':';
		if (h < 10)
				time_str += '0';
		time_str += h.toFixed(0);
		return time_str;
	} else if (h > 0) {
		if (h < 10)
			time_str += '0';
		time_str += h.toFixed(0);
		time_str += ':';
		if (m < 10)
				time_str += '0';
		time_str += m.toFixed(0);
		return time_str;
	} else {
		if (m < 10)
				time_str += '0';
		time_str += m.toFixed(0);
		time_str += ':';
		if (s < 10)
				time_str += '0';
		time_str += s.toFixed(0);
		return time_str;
	}
}

function formatMeter(v) {
	if(!v || v == "") return "--.-";
	return v.toFixed(1);
}

function formatPercent(v) {
	if(!v || v == "") return "---%";
	return v.toFixed(1);
}

Pebble.addEventListener("appmessage",
function(e) {
  console.log("Received message: " + JSON.stringify(e.payload));
	if (e.payload.URL) {
		console.log("URL: " + e.payload.URL);
		var timerID = setInterval(reload,1000);
		function reload() {
			var req = new XMLHttpRequest();
			req.open('GET', e.payload.URL.trim() + "?SPEED,DEPTH,HDG,AWA,BTW,DTW,TTG,COG,XTE,SOG,TWD,TWS,BFT,TARGET_SPEED,TARGET_SPEED_PERCENT,STARTTIME_INTERVAL1970", true);
			req.onload = function(e) {
				if (req.readyState == 4) {
			  	if(req.status == 200) {
			       var response = JSON.parse(req.responseText);

			       var speed = formatKnots(response.SPEED);
			       var depth = formatMeter(response.DEPTH);
			       var hdg = formatAngle(response.HDG);
			       var awa = formatAngle(response.AWA);
			       var btw = formatAngle(response.BTW);
			       var dtw = formatSMile(response.DTW);
			       var ttg = formatTime(response.TTG);
						 var cog = formatAngle(response.COG);
						 var xte = formatKnots(response.XTE);
						 var sog = formatKnots(response.SOG);
						 var twd = formatAngle(response.TWD);
						 var tws = formatKnots(response.TWS);
						 var bft = formatBft(response.BFT);
						 var target_speed = formatKnots(response.TARGET_SPEED);
						 var target_speed_percent = formatPercent(response.TARGET_SPEED_PERCENT);
						 var starttime_interval1970 = formatPercent(response.STARTTIME_INTERVAL1970);

			       Pebble.sendAppMessage({"SPEED":speed,
						 											 	"DEPTH":depth,
						 												"HDG":hdg,
																		"AWA":awa,
																		"BTW":btw,
																		"DTW":dtw,
																		"TTG":ttg,
																		"COG":cog,
																		"XTE":xte,
																		"SOG":sog,
																		"TWD":twd,
																		"TWS":tws,
																		"BFT":bft,
																		"TARGET_SPEED":target_speed,
																		"TARGET_SPEED_PERCENT":target_speed_percent,
																		"STARTTIME_INTERVAL1970": starttime_interval1970
																	});
			     }
			   }
			 }
			 req.send(null);
	 	}
	}
});<commit_msg>Format fixes.<commit_after>function formatSMile(v) {
	if(!v || v == "") return "--.--";
	if (v < 100)
		return v.toFixed(2);
	else
		return v.toFixed(0);
}

function formatKnots(v) {
	if(!v || v == "") {
		if (v == 0)
			return "0.0";
		return "--.-";
	}
	return v.toFixed(1);
}

function formatBft(v) {
	if(!v || v == "") return "-";
	return v.toFixed(0);
}

function formatAngle(v) {
	if(!v || v == "") return "---°";
	var x = v.toFixed(0);
	if (x < -100)
		return '-'+ x*-1 +"°";
	if (x < -10)
		return '-0'+ x*-1 +"°";
	if (x < 0)
		return '-00'+ x*-1 +"°";
	if (x < 10)
		return '00'+ x +"°";
	else if (x < 100)
		return '0'+ x +"°";
	else
		return x + '°';
}

function formatTime(v) {
	if(!v || v == "") return "--:--";

	var d = Math.floor(v / (60 * 60 * 24));
	var div_days = v % (60 * 60 * 24);
	var h = Math.floor(div_days / (60 * 60));
	var div_min = div_days % (60 * 60);
	var m = Math.floor(div_min / 60);
	var div_sec = div_min % 60;
	var sec = Math.ceil(div_sec);

	var time_str = "";
	if (d > 0) {
		if (d < 10)
			time_str += '0';
		time_str += d.toFixed(0);
		time_str += ':';
		if (h < 10)
				time_str += '0';
		time_str += h.toFixed(0);
		return time_str;
	} else if (h > 0) {
		if (h < 10)
			time_str += '0';
		time_str += h.toFixed(0);
		time_str += ':';
		if (m < 10)
				time_str += '0';
		time_str += m.toFixed(0);
		return time_str;
	} else {
		if (m < 10)
				time_str += '0';
		time_str += m.toFixed(0);
		time_str += ':';
		if (s < 10)
				time_str += '0';
		time_str += s.toFixed(0);
		return time_str;
	}
}

function formatMeter(v) {
	if(!v || v == "") return "--.-";
	return v.toFixed(1);
}

function formatPercent(v) {
	if(!v || v == "") return "---%";
	return v.toFixed(1);
}

Pebble.addEventListener("appmessage",
function(e) {
  console.log("Received message: " + JSON.stringify(e.payload));
	if (e.payload.URL) {
		console.log("URL: " + e.payload.URL);
		var timerID = setInterval(reload,1000);
		function reload() {
			var req = new XMLHttpRequest();
			req.open('GET', e.payload.URL.trim() + "?Speed,Depth,HDG,AWA,BTW,DTW,TTG,COG,XTE,SOG,TWD,TWS,BFT,TARGET_SPEED,TARGET_SPEED_PERCENT,STARTTIME_INTERVAL1970", true);
			req.onload = function(e) {
				if (req.readyState == 4) {
			  	if(req.status == 200) {
			       var response = JSON.parse(req.responseText);

			       var speed = formatKnots(response.Speed);
			       var depth = formatMeter(response.Depth);
			       var hdg = formatAngle(response.HDG);
			       var awa = formatAngle(response.AWA);
			       var btw = formatAngle(response.BTW);
			       var dtw = formatSMile(response.DTW);
			       var ttg = formatTime(response.TTG);
						 var cog = formatAngle(response.COG);
						 var xte = formatKnots(response.XTE);
						 var sog = formatKnots(response.SOG);
						 var twd = formatAngle(response.TWD);
						 var tws = formatKnots(response.TWS);
						 var bft = formatBft(response.BFT);
						 var target_speed = formatKnots(response.TARGET_SPEED);
						 var target_speed_percent = formatPercent(response.TARGET_SPEED_PERCENT);
						 var starttime_interval1970 = formatPercent(response.STARTTIME_INTERVAL1970);

			       Pebble.sendAppMessage({"SPEED":speed,
						 											 	"DEPTH":depth,
						 												"HDG":hdg,
																		"AWA":awa,
																		"BTW":btw,
																		"DTW":dtw,
																		"TTG":ttg,
																		"COG":cog,
																		"XTE":xte,
																		"SOG":sog,
																		"TWD":twd,
																		"TWS":tws,
																		"BFT":bft,
																		"TARGET_SPEED":target_speed,
																		"TARGET_SPEED_PERCENT":target_speed_percent,
																		"STARTTIME_INTERVAL1970": starttime_interval1970
																	});
			     }
			   }
			 }
			 req.send(null);
	 	}
	}
});<|endoftext|>


<commit_before>/*jshint -W069*/

var tltoken = '';

function get_day_half() {
    return (new Date().getHours() > 12) ? 'PM' : 'AM';
}

function gen_time_string(hoursinfuture) {
    console.log('--------------------------------------------------------');
    console.log('generating time string for ' + hoursinfuture);
    var now = new Date();
    console.log('generating time string for ' + hoursinfuture + ' - date is: ' + now.toLocaleString());
    now.setHours((now.getHours() - (now.getHours() % 12)) + hoursinfuture);
    console.log('generating time string for ' + hoursinfuture + ' - date is: ' + now.toLocaleString());
    var time = now.toISOString().substring(0, 10);
    console.log('generating time string for ' + hoursinfuture + ': ' + time.toLocaleString());
    console.log('hours: ' + now.getHours());
    time += '-TP-';
    time += (now.getHours() >= 12) ? 'PM' : 'AM';
    console.log('generated time string for ' + hoursinfuture + ': ' + time.toLocaleString());
    console.log('--------------------------------------------------------');
    return time;
}

function send_tl_token(token) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'set_timeline_token?uid=' + Pebble.getAccountToken() +
                    '&tltoken=' + token);
    xhr.send();
    // It's possible that this fails.
    // Failing is ignored for a reason:
    //     If this fails, that means that
    //     the user doesn't exist.
    //     On the next launch, the app will
    //     retry sending the timeline token.
}

function send_pin(data) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log('Pin push response: ' + this.responseText);
            console.log('Pin push status code: ' + this.status);
        }
    };
    xhr.open('PUT', 'https://timeline-api.getpebble.com/v1/user/pins/' + data.id);
    xhr.setRequestHeader('X-User-Token', tltoken);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
    console.log(JSON.stringify(data));
}

function send_twice_daily_pin(timestr) {
    console.log('Send twice daily pin - timestr:', timestr);
    var hourofday = timestr.slice(-2) == "AM" ? 6 : 12;
    var xhr = new XMLHttpRequest();
    var timeExploded = timestr.slice(0, 10).split('-');
    var pinTime = new Date(timeExploded[0], timeExploded[1] - 1,
                           timeExploded[2], hourofday, 0, 0, 0);
    xhr.onreadystatechange = function() {
        if (this.readyState == 4) {
            if (this.status == 200) {
                var json = JSON.parse(this.responseText);
                var challenge = json[0];
                if (!('username' in challenge)) {
                    // Challenge is actually useless.
                    // The user should get some friends.
                    return;
                }
                var pin = {
                    'id': 'stepup-challenge-pin-' + timestr,
                    'time': pinTime.toISOString(),
                    'layout': {
                        'type': 'genericPin',
                        'title': 'StepUp Health Challenge!',
                        'backgroundColor': 'vividcerulean',
                        'subtitle': 'Beat: ' + challenge.username,
                        'body': challenge.username + ' reached ' + challenge.steps +
                                ' steps last ' +
                                (timestr.slice(-2) == 'AM' ? 'morning' : 'afternoon') +
                                '. Can you one-up them?',
                        // 'headings': ['Debug data:'],
                        // 'paragraphs': ['time: ' + pinTime.toISOString() +
                        //                ' (' + timestr + '); pushed at: ' + new Date().toLocaleString() + ' (' + gen_time_string(0) + ')'],
                        'tinyIcon': 'system://images/STOCKS_EVENT'
                    }
                };
                console.log(JSON.stringify(challenge));
                console.log(JSON.stringify(pin));
                send_pin(pin);
            } else {
                console.log('vvvv Failure getting user info vvvv');
                console.log(this.responseText);
                console.log('^^^^ Failure getting user info ^^^^');
            }
        } else {
            console.error(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'get_active_friends?uid=' + Pebble.getAccountToken() +
                    '&dayhalf=' + timestr.slice(-2));
    xhr.send();
}

function send_applicable_pins() {
    send_twice_daily_pin(gen_time_string(12));
    send_twice_daily_pin(gen_time_string(24));
}

function send_hist_data(data) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log(this.responseText);
        }
    };
    var time = gen_time_string(0);
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'add_data_point?uid=' + Pebble.getAccountToken() +
                    '&steps=' + data + '&timeperiod=' + time);
    xhr.send();
}

function fetch_other_user_data() {
    var xhr = new XMLHttpRequest();
    var dayhalf = get_day_half();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            if (this.status == 200) {
                var data = {'MSG_KEY_DATA_SIZE': 0};
                var json = JSON.parse(this.responseText);
                if (json.length > 0) {
                    data['MSG_KEY_DATA_SIZE'] = Math.min(5, json.length);
                    for (var i = 0; i < Math.min(5, json.length); i++) {
                        data[100 + i] = json[i].username;
                        data[200 + i] = json[i].steps;
                    }
                }
                console.log(JSON.stringify(data));
                Pebble.sendAppMessage(data);
            }
        } else {
            console.error(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'get_active_friends?uid=' + Pebble.getAccountToken() +
                    '&dayhalf=' + dayhalf);
    xhr.send();
}

Pebble.addEventListener('appmessage', function(data) {
    console.log('AppMessage received');
    console.log(data.payload[25973]);
    send_hist_data(data.payload[25973]);
});

Pebble.addEventListener('showConfiguration', function() {
    var url = 'https://step-up-health.github.io/step-up-config-page/#' +
        Pebble.getAccountToken() + ':' + tltoken;
    console.log('Showing configuration page: ' + url);
    Pebble.openURL(url);
});

Pebble.addEventListener('ready', function() {
    send_applicable_pins();
    var uid = Pebble.getAccountToken();
    console.log('Hello');
    localStorage.tlTokenSent = localStorage.tlTokenSent || false;
    Pebble.getTimelineToken(function(token) {
        tltoken = token;
        send_tl_token(token);
    }, function(error) {
        console.error(error);
    });
    fetch_other_user_data();
});

Pebble.addEventListener('webviewclosed', function() {
    console.log('sending tl token because webviewclosed');
    Pebble.getTimelineToken(function(token) {
        console.log('sent tl token.');
        tltoken = token;
        send_tl_token(token);
    }, function(error) {
        console.error(error);
    });
});
<commit_msg>Remove superfluous gen_time_string logs<commit_after>/*jshint -W069*/

var tltoken = '';

function get_day_half() {
    return (new Date().getHours() > 12) ? 'PM' : 'AM';
}

function gen_time_string(hoursinfuture) {
    var now = new Date();
    now.setHours((now.getHours() - (now.getHours() % 12)) + hoursinfuture);
    var time = now.toISOString().substring(0, 10);
    time += '-TP-';
    time += (now.getHours() >= 12) ? 'PM' : 'AM';
    return time;
}

function send_tl_token(token) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'set_timeline_token?uid=' + Pebble.getAccountToken() +
                    '&tltoken=' + token);
    xhr.send();
    // It's possible that this fails.
    // Failing is ignored for a reason:
    //     If this fails, that means that
    //     the user doesn't exist.
    //     On the next launch, the app will
    //     retry sending the timeline token.
}

function send_pin(data) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log('Pin push response: ' + this.responseText);
            console.log('Pin push status code: ' + this.status);
        }
    };
    xhr.open('PUT', 'https://timeline-api.getpebble.com/v1/user/pins/' + data.id);
    xhr.setRequestHeader('X-User-Token', tltoken);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
    console.log(JSON.stringify(data));
}

function send_twice_daily_pin(timestr) {
    console.log('Send twice daily pin - timestr:', timestr);
    var hourofday = timestr.slice(-2) == "AM" ? 6 : 12;
    var xhr = new XMLHttpRequest();
    var timeExploded = timestr.slice(0, 10).split('-');
    var pinTime = new Date(timeExploded[0], timeExploded[1] - 1,
                           timeExploded[2], hourofday, 0, 0, 0);
    xhr.onreadystatechange = function() {
        if (this.readyState == 4) {
            if (this.status == 200) {
                var json = JSON.parse(this.responseText);
                var challenge = json[0];
                if (!('username' in challenge)) {
                    // Challenge is actually useless.
                    // The user should get some friends.
                    return;
                }
                var pin = {
                    'id': 'stepup-challenge-pin-' + timestr,
                    'time': pinTime.toISOString(),
                    'layout': {
                        'type': 'genericPin',
                        'title': 'StepUp Health Challenge!',
                        'backgroundColor': 'vividcerulean',
                        'subtitle': 'Beat: ' + challenge.username,
                        'body': challenge.username + ' reached ' + challenge.steps +
                                ' steps last ' +
                                (timestr.slice(-2) == 'AM' ? 'morning' : 'afternoon') +
                                '. Can you one-up them?',
                        // 'headings': ['Debug data:'],
                        // 'paragraphs': ['time: ' + pinTime.toISOString() +
                        //                ' (' + timestr + '); pushed at: ' + new Date().toLocaleString() + ' (' + gen_time_string(0) + ')'],
                        'tinyIcon': 'system://images/STOCKS_EVENT'
                    }
                };
                console.log(JSON.stringify(challenge));
                console.log(JSON.stringify(pin));
                send_pin(pin);
            } else {
                console.log('vvvv Failure getting user info vvvv');
                console.log(this.responseText);
                console.log('^^^^ Failure getting user info ^^^^');
            }
        } else {
            console.error(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'get_active_friends?uid=' + Pebble.getAccountToken() +
                    '&dayhalf=' + timestr.slice(-2));
    xhr.send();
}

function send_applicable_pins() {
    send_twice_daily_pin(gen_time_string(12));
    send_twice_daily_pin(gen_time_string(24));
}

function send_hist_data(data) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            console.log(this.responseText);
        }
    };
    var time = gen_time_string(0);
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'add_data_point?uid=' + Pebble.getAccountToken() +
                    '&steps=' + data + '&timeperiod=' + time);
    xhr.send();
}

function fetch_other_user_data() {
    var xhr = new XMLHttpRequest();
    var dayhalf = get_day_half();
    xhr.onreadystatechange = function() {
        if (this.readyState == this.DONE) {
            if (this.status == 200) {
                var data = {'MSG_KEY_DATA_SIZE': 0};
                var json = JSON.parse(this.responseText);
                if (json.length > 0) {
                    data['MSG_KEY_DATA_SIZE'] = Math.min(5, json.length);
                    for (var i = 0; i < Math.min(5, json.length); i++) {
                        data[100 + i] = json[i].username;
                        data[200 + i] = json[i].steps;
                    }
                }
                console.log(JSON.stringify(data));
                Pebble.sendAppMessage(data);
            }
        } else {
            console.error(this.responseText);
        }
    };
    xhr.open('GET', 'https://pythonbackend-stepupforpebble.rhcloud.com/' +
                    'get_active_friends?uid=' + Pebble.getAccountToken() +
                    '&dayhalf=' + dayhalf);
    xhr.send();
}

Pebble.addEventListener('appmessage', function(data) {
    console.log('AppMessage received');
    console.log(data.payload[25973]);
    send_hist_data(data.payload[25973]);
});

Pebble.addEventListener('showConfiguration', function() {
    var url = 'https://step-up-health.github.io/step-up-config-page/#' +
        Pebble.getAccountToken() + ':' + tltoken;
    console.log('Showing configuration page: ' + url);
    Pebble.openURL(url);
});

Pebble.addEventListener('ready', function() {
    send_applicable_pins();
    var uid = Pebble.getAccountToken();
    console.log('Hello');
    localStorage.tlTokenSent = localStorage.tlTokenSent || false;
    Pebble.getTimelineToken(function(token) {
        tltoken = token;
        send_tl_token(token);
    }, function(error) {
        console.error(error);
    });
    fetch_other_user_data();
});

Pebble.addEventListener('webviewclosed', function() {
    console.log('sending tl token because webviewclosed');
    Pebble.getTimelineToken(function(token) {
        console.log('sent tl token.');
        tltoken = token;
        send_tl_token(token);
    }, function(error) {
        console.error(error);
    });
});
<|endoftext|>


<commit_before>var EventEmitter  = require('events').EventEmitter;
var util          = require('util');
var utils        = require('./utils');

//------------------------------------------------------------------------------
/**
    Constructor

    Keys for cfg:

      url

    @param  remote    The Remote object
    @param  cfg       Configuration parameters.
*/

var Server = function (remote, opts) {
  EventEmitter.call(this);

  if ('object' !== typeof opts || 'string' !== typeof opts.url) {
    throw new Error('Invalid server configuration.');
  }

  var self = this;

  this._remote         = remote;
  this._opts           = opts;

  this._ws             = void(0);
  this._connected      = false;
  this._should_connect = false;
  this._state          = void(0);

  this._id             = 0;
  this._retry          = 0;

  this._requests       = { };

  this.on('message', function(message) {
    self._handle_message(message);
  });

  this.on('response_subscribe', function(message) {
    self._handle_response_subscribe(message);
  });
};

//------------------------------------------------------------------------------

util.inherits(Server, EventEmitter);

function to_set(list) {
  var result = { };
  for (var i=0; i<list.length; i++) {
    result[list[i]] = true;
  }
  return result;
}
/**
 * Server states that we will treat as the server being online.
 *
 * Our requirements are that the server can process transactions and notify
 * us of changes.
 */
Server.online_states = to_set([
  'syncing',
  'tracking',
  'proposing',
  'validating',
  'full'
]);

Server.prototype.connect = function () {
  var self = this;

  // We don't connect if we believe we're already connected. This means we have
  // recently received a message from the server and the WebSocket has not
  // reported any issues either. If we do fail to ping or the connection drops,
  // we will automatically reconnect.
  if (this._connected === true) return;

  if (this._remote.trace) console.log('server: connect: %s', this._cfg.url);

  // Ensure any existing socket is given the command to close first.
  if (this._ws) this._ws.close();

  // We require this late, because websocket shims may be loaded after
  // ripple-lib.
  var WebSocket = require('ws');
  var ws = this._ws = new WebSocket(this._opts.url);

  this._should_connect = true;

  self.emit('connecting');

  ws.onopen = function () {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    self.emit('socket_open');

    // Subscribe to events
    var request = self._remote._server_prepare_subscribe();
    self.request(request);
  };

  ws.onerror = function (e) {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    if (self._remote.trace) console.log('server: onerror: %s', e.data || e);

    // Most connection errors for WebSockets are conveyed as 'close' events with
    // code 1006. This is done for security purposes and therefore unlikely to
    // ever change.

    // This means that this handler is hardly ever called in practice. If it is,
    // it probably means the server's WebSocket implementation is corrupt, or
    // the connection is somehow producing corrupt data.

    // Most WebSocket applications simply log and ignore this error. Once we
    // support for multiple servers, we may consider doing something like
    // lowering this server's quality score.

    // However, in Node.js this event may be triggered instead of the close
    // event, so we need to handle it.
    handleConnectionClose();
  };

  // Failure to open.
  ws.onclose = function () {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    if (self._remote.trace) console.log('server: onclose: %s', ws.readyState);

    handleConnectionClose();
  };

  function handleConnectionClose() {
    self.emit('socket_close');
    self._set_state('offline');

    // Prevent additional events from this socket
    ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {};

    // Should we be connected?
    if (!self._should_connect) return;

    // Delay and retry.
    self._retry      += 1;
    self._retry_timer = setTimeout(function () {
      if (self._remote.trace) console.log('server: retry');

      if (!self._should_connect) return;
      self.connect();
    }, self._retry < 40
        ? 1000/20           // First, for 2 seconds: 20 times per second
        : self._retry < 40+60
          ? 1000            // Then, for 1 minute: once per second
          : self._retry < 40+60+60
            ? 10*1000       // Then, for 10 minutes: once every 10 seconds
            : 30*1000);     // Then: once every 30 seconds
  }

  ws.onmessage = function (msg) {
    self.emit('message', msg.data);
  };
};

Server.prototype.disconnect = function () {
  this._should_connect = false;
  this._set_state('offline');
  if (this.ws) {
    this.ws.close();
  }
};

/**
 * Submit a Request object to this server.
 */
Server.prototype.request = function (request) {
  var self  = this;

  // Only bother if we are still connected.
  if (self._ws) {
    request.message.id = self._id;

    self._requests[request.message.id] = request;

    // Advance message ID
    self._id++;

    if (self._state === 'online' ||
        (request.message.command === 'subscribe' && self._ws.readyState === 1)) {
      if (self._remote.trace) {
        utils.logObject('server: request: %s', request.message);
      }

      self._ws.send(JSON.stringify(request.message));
    } else {
      // XXX There are many ways to make self smarter.
      self.once('connect', function () {
        if (self._remote.trace) {
          utils.logObject('server: request: %s', request.message);
        }
        self._ws.send(JSON.stringify(request.message));
      });
    }
  } else {
    if (self._remote.trace) {
      utils.logObject('server: request: DROPPING: %s', request.message);
    }
  }
};

Server.prototype._set_state = function (state) {
  if (state !== this._state) {
    this._state = state;

    this.emit('state', state);

    if (state === 'online') {
      this._connected = true;
      this.emit('connect');
    } else if (state === 'offline') {
      this._connected = false;
      this.emit('disconnect');
    }
  }
};

Server.prototype._handle_message = function (json) {
  var self = this;

  var message = JSON.parse(json);

  if (message.type === 'response') {
    // A response to a request.
    var request = self._requests[message.id];

    delete self._requests[message.id];

    if (!request) {
      if (self._remote.trace) utils.logObject('server: UNEXPECTED: %s', message);
    } else if ('success' === message.status) {
      if (self._remote.trace) utils.logObject('server: response: %s', message);

      request.emit('success', message.result);
      self.emit('response_'+request.message.command, message.result, request, message);
      self._remote.emit('response_'+request.message.command, message.result, request, message);
    } else if (message.error) {
      if (self._remote.trace) utils.logObject('server: error: %s', message);

      request.emit('error', {
        'error'         : 'remoteError',
        'error_message' : 'Remote reported an error.',
        'remote'        : message
      });
    }
  } else if (message.type === 'serverStatus') {
    // This message is only received when online. As we are connected, it is the definative final state.
    self._set_state(Server.online_states[message.server_status] ? 'online' : 'offline');
  }
};

Server.prototype._handle_response_subscribe = function (message) {
  var self = this;

  self._server_status = message.server_status;

  if (Server.online_states[message.server_status]) {
    self._set_state('online');
  }
};

exports.Server = Server;

// vim:sw=2:sts=2:ts=8:et
<commit_msg>Fix .disconnect<commit_after>var EventEmitter  = require('events').EventEmitter;
var util          = require('util');
var utils        = require('./utils');

//------------------------------------------------------------------------------
/**
    Constructor

    Keys for cfg:

      url

    @param  remote    The Remote object
    @param  cfg       Configuration parameters.
*/

var Server = function (remote, opts) {
  EventEmitter.call(this);

  if ('object' !== typeof opts || 'string' !== typeof opts.url) {
    throw new Error('Invalid server configuration.');
  }

  var self = this;

  this._remote         = remote;
  this._opts           = opts;

  this._ws             = void(0);
  this._connected      = false;
  this._should_connect = false;
  this._state          = void(0);

  this._id             = 0;
  this._retry          = 0;

  this._requests       = { };

  this.on('message', function(message) {
    self._handle_message(message);
  });

  this.on('response_subscribe', function(message) {
    self._handle_response_subscribe(message);
  });
};

//------------------------------------------------------------------------------

util.inherits(Server, EventEmitter);

/**
 * Server states that we will treat as the server being online.
 *
 * Our requirements are that the server can process transactions and notify
 * us of changes.
 */
Server.online_states = [
    'syncing'
  , 'tracking'
  , 'proposing'
  , 'validating'
  , 'full'
];

Server.prototype._is_online = function (status) {
  return Server.online_states.indexOf(status) !== -1;
};

Server.prototype._set_state = function (state) {
  if (state !== this._state) {
    this._state = state;

    this.emit('state', state);

    if (state === 'online') {
      this._connected = true;
      this.emit('connect');
    } else if (state === 'offline') {
      this._connected = false;
      this.emit('disconnect');
    }
  }
};

Server.prototype.connect = function () {
  var self = this;

  // We don't connect if we believe we're already connected. This means we have
  // recently received a message from the server and the WebSocket has not
  // reported any issues either. If we do fail to ping or the connection drops,
  // we will automatically reconnect.
  if (this._connected === true) return;

  if (this._remote.trace) console.log('server: connect: %s', this._cfg.url);

  // Ensure any existing socket is given the command to close first.
  if (this._ws) this._ws.close();

  // We require this late, because websocket shims may be loaded after
  // ripple-lib.
  var WebSocket = require('ws');
  var ws = this._ws = new WebSocket(this._opts.url);

  this._should_connect = true;

  self.emit('connecting');

  ws.onopen = function () {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    self.emit('socket_open');

    // Subscribe to events
    var request = self._remote._server_prepare_subscribe();
    self.request(request);
  };

  ws.onerror = function (e) {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    if (self._remote.trace) console.log('server: onerror: %s', e.data || e);

    // Most connection errors for WebSockets are conveyed as 'close' events with
    // code 1006. This is done for security purposes and therefore unlikely to
    // ever change.

    // This means that this handler is hardly ever called in practice. If it is,
    // it probably means the server's WebSocket implementation is corrupt, or
    // the connection is somehow producing corrupt data.

    // Most WebSocket applications simply log and ignore this error. Once we
    // support for multiple servers, we may consider doing something like
    // lowering this server's quality score.

    // However, in Node.js this event may be triggered instead of the close
    // event, so we need to handle it.
    handleConnectionClose();
  };

  // Failure to open.
  ws.onclose = function () {
    // If we are no longer the active socket, simply ignore any event
    if (ws !== self._ws) return;

    if (self._remote.trace) console.log('server: onclose: %s', ws.readyState);

    handleConnectionClose();
  };

  function handleConnectionClose() {
    self.emit('socket_close');
    self._set_state('offline');

    // Prevent additional events from this socket
    ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {};

    // Should we be connected?
    if (!self._should_connect) return;

    // Delay and retry.
    self._retry      += 1;
    self._retry_timer = setTimeout(function () {
      if (self._remote.trace) console.log('server: retry');

      if (!self._should_connect) return;
      self.connect();
    }, self._retry < 40
        ? 1000/20           // First, for 2 seconds: 20 times per second
        : self._retry < 40+60
          ? 1000            // Then, for 1 minute: once per second
          : self._retry < 40+60+60
            ? 10*1000       // Then, for 10 minutes: once every 10 seconds
            : 30*1000);     // Then: once every 30 seconds
  }

  ws.onmessage = function (msg) {
    self.emit('message', msg.data);
  };
};

Server.prototype.disconnect = function () {
  this._should_connect = false;
  this._set_state('offline');
  if (this._ws) {
    this._ws.close();
  }
};

/**
 * Submit a Request object to this server.
 */
Server.prototype.request = function (request) {
  var self  = this;

  // Only bother if we are still connected.
  if (self._ws) {
    request.message.id = self._id;

    self._requests[request.message.id] = request;

    // Advance message ID
    self._id++;

    if (self._state === 'online' ||
        (request.message.command === 'subscribe' && self._ws.readyState === 1)) {
      if (self._remote.trace) {
        utils.logObject('server: request: %s', request.message);
      }

      self._ws.send(JSON.stringify(request.message));
    } else {
      // XXX There are many ways to make self smarter.
      self.once('connect', function () {
        if (self._remote.trace) {
          utils.logObject('server: request: %s', request.message);
        }
        self._ws.send(JSON.stringify(request.message));
      });
    }
  } else {
    if (self._remote.trace) {
      utils.logObject('server: request: DROPPING: %s', request.message);
    }
  }
};

Server.prototype._handle_message = function (json) {
  var self = this;

  var message = JSON.parse(json);

  if (message.type === 'response') {
    // A response to a request.
    var request = self._requests[message.id];

    delete self._requests[message.id];

    if (!request) {
      if (self._remote.trace) utils.logObject('server: UNEXPECTED: %s', message);
    } else if ('success' === message.status) {
      if (self._remote.trace) utils.logObject('server: response: %s', message);

      request.emit('success', message.result);
      self.emit('response_'+request.message.command, message.result, request, message);
      self._remote.emit('response_'+request.message.command, message.result, request, message);
    } else if (message.error) {
      if (self._remote.trace) utils.logObject('server: error: %s', message);

      request.emit('error', {
        'error'         : 'remoteError',
        'error_message' : 'Remote reported an error.',
        'remote'        : message
      });
    }
  } else if (message.type === 'serverStatus') {
    // This message is only received when online. As we are connected, it is the definative final state.
    self._set_state(self._is_online(message.server_status) ? 'online' : 'offline');
  }
};

Server.prototype._handle_response_subscribe = function (message) {
  var self = this;

  self._server_status = message.server_status;

  if (self._is_online(message.server_status)) {
    self._set_state('online');
  }
};

exports.Server = Server;

// vim:sw=2:sts=2:ts=8:et
<|endoftext|>


<commit_before>'use strict'

module.exports = function(shariff) {
  return {
    blank: true,
    popup: false,
    shareText: 'Info',
    name: 'info',
    faName: 'fa-info',
    title: {
      'de': 'weitere Informationen',
      'en': 'more information',
      'es': 'más informaciones',
      'fr': 'plus d\'informations',
      'it': 'maggiori informazioni',
      'da': 'flere oplysninger',
      'nl': 'verdere informatie',
      'zh': '更多信息'
    },
    shareUrl: shariff.getInfoUrl()
  }
}
<commit_msg>info.js: Add translations, correct capitalisation<commit_after>'use strict'

module.exports = function(shariff) {
  return {
    blank: true,
    popup: false,
    shareText: 'Info',
    name: 'info',
    faName: 'fa-info',
    title: {
      'bg': 'Повече информация',
      'da': 'Flere oplysninger',
      'de': 'Weitere Informationen',
      'en': 'More information',
      'es': 'Más informaciones',
      'fi': 'Lisätietoja',
      'fr': 'Plus d\'informations',
      'hr': 'Više informacija',
      'hu': 'Több információ',
      'it': 'Maggiori informazioni',
      'ja': '詳しい情報',
      'ko': '추가 정보',
      'nl': 'Verdere informatie',
      'no': 'Mer informasjon',
      'pl': 'Więcej informacji',
      'pt': 'Mais informações',
      'ro': 'Mai multe informatii',
      'ru': 'Больше информации',
      'sk': 'Viac informácií',
      'sl': 'Več informacij',
      'sr': 'Više informacija',
      'sv': 'Mer information',
      'tr': 'Daha fazla bilgi',
      'zh': '更多信息'
    },
    shareUrl: shariff.getInfoUrl()
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:Pear
 * @description
 * # Pear service
 * Provides controllers with a data model for pear to pear app
 * It serves as an abstraction between Pear data and backend (SwellRT)
 */

angular.module('Pear2Pear')
  .factory('pear', [
           '$rootScope', 'swellRT', '$q', '$timeout', 'base64',
           function($rootScope, swellRT, $q, $timeout, base64) {

    var proxy = {
      communities: {}
    };

    // FIXME model prototype
    var urlId = function(id) {
      return base64.encode(id);
    };

    // map of opened projects
    var openedProjects = {};

    var def = $q.defer();

    var communities = {

      all: function() {
        return proxy.communities;
      },

      find: function(urlId) {
        var id = base64.decode(urlId);
        var community = proxy.communities[id];

        return {
          community: community,
          projects: {
            all: function() {
              var promises = {};
              angular.forEach(community.projects, function(val){
                var projDef = $q.defer();
                promises[val] = projDef.promise;
                if (!openedProjects[val]){
                  window.SwellRT.openModel(val, function(model){
                    $timeout(function(){
                      var pr = swellRT.proxy(model);
                      openedProjects[val] = pr;
                      projDef.resolve(pr);
                    });
                  });
                } else {
                  projDef.resolve(openedProjects[val]);
                }
              });

              var projsDef = $q.all(promises);
              return projsDef;
            },
            destroy: function(projId){
              var i = community.projects.indexOf(projId);
              if (i > -1){
                community.projects.splice(i,1);
              }
            }
          }
        };
      },
      create: function(data, callback) {
        var id = window.SwellRT.createModel(function(model){
          var p = swellRT.proxy(model);
          p.name = data.name;
          p.id = id;
          p.projects = [];
          proxy.communities[id] = p;
          callback({
            community: p,
            projects: {
              // TODO avoid repeated function
              all: function() {
                var promises = {};
                angular.forEach(community.projects, function(val){
                  var projDef = $q.defer();
                  promises[val] = projDef.promise;
                  if (!openedProjects[val]){
                    window.SwellRT.openModel(val, function(model){
                      $timeout(function(){
                        var pr = swellRT.proxy(model);
                        openedProjects[val] = pr;
                        projDef.resolve(pr);
                      });
                    });
                  } else {
                    projDef.resolve(openedProjects[val]);
                  }
                });

                var projsDef = $q.all(promises);
                return projsDef;
              }
            }});
        });
      },
      destroy: function(urlId) {
        var id = base64.decode(urlId);

        delete proxy.communities[id];

        return urlId;
      }
    };

    var projects = {

      find: function(urlId) {
        var id = base64.decode(urlId);

        def = $q.defer();

        if (!openedProjects[id]) {
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            openedProjects[id] = pr;
            def.resolve(openedProjects[id]);
          }, function(error){
            def.reject(error);
          });
        } else {
          def.resolve(openedProjects[id]);
        }
        return def.promise;
      },
      create: function(callback) {
        var id = window.SwellRT.createModel(function(model){
          var p = {
            proj : {}
          };
          proxy.proj = swellRT.proxy(model);
          $timeout(function(){
            proxy.proj['id'] = id;
            proxy.proj['title'] = '';
            proxy.proj['chat'] = [];
            proxy.proj['pad'] = new swellRT.TextObject();
            proxy.proj['needs'] = [];
            proxy.proj['promoter'] = users.current();
            openedProjects[id] = proxy.proj;
            callback(proxy.proj);
          });
        });
      }
    };

    var users = {
      current: function() {
        return window.sessionStorage.getItem('userId');
      },
      setCurrent: function(name) {
        return window.sessionStorage.setItem('userId', name);
      },
      isCurrent: function(user) {
        return user === users.current();
      },
      loggedIn: function() {
        return users.current() !== 'undefined' && users.current() !== null;
      }
    };

    var addChatMessage = function(projectId, message) {
      projects.find(projectId).then(function(project){
        project.chat.push({
          text: message,
          who: users.current(),
          time: (new Date()).toJSON()
        });
      }, function(error){
        console.log(error);
      });
    };

    window.onSwellRTReady = function () {
      window.SwellRT.startSession(
        SwellRTConfig.server, SwellRTConfig.user, SwellRTConfig.pass,
        function() {
          // Open Community List
          window.SwellRT.openModel(
            SwellRTConfig.communityListWaveId,
            function(model) {
              proxy.communities = swellRT.proxy(model);
              if (!proxy.communities){
                proxy['communities'] = {};
              }
              def.resolve(proxy.model);
            },
            function(error){
              console.log(error);
            });
          },

        function(error) {
          console.log(error);
        });
      // to avoid multiple calls
      window.onSwellRTReady = null;
    };

    if (window.SwellRT && typeof window.onSwellRTReady === 'function') {
      window.onSwellRTReady();
    }

    return {
      communities: communities,
      projects: projects,
      users: users,
      urlId: urlId,
      addChatMessage: addChatMessage,
      onLoad: function(f) {
        def.promise.then(f);
      }
    };
  }]);
<commit_msg>Deal with undefined ids in pear.urlId<commit_after>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:Pear
 * @description
 * # Pear service
 * Provides controllers with a data model for pear to pear app
 * It serves as an abstraction between Pear data and backend (SwellRT)
 */

angular.module('Pear2Pear')
  .factory('pear', [
           '$rootScope', 'swellRT', '$q', '$timeout', 'base64',
           function($rootScope, swellRT, $q, $timeout, base64) {

    var proxy = {
      communities: {}
    };

    // FIXME model prototype
    var urlId = function(id) {
      if (id === undefined) { return ''; }

      return base64.encode(id);
    };

    // map of opened projects
    var openedProjects = {};

    var def = $q.defer();

    var communities = {

      all: function() {
        return proxy.communities;
      },

      find: function(urlId) {
        var id = base64.decode(urlId);
        var community = proxy.communities[id];

        return {
          community: community,
          projects: {
            all: function() {
              var promises = {};
              angular.forEach(community.projects, function(val){
                var projDef = $q.defer();
                promises[val] = projDef.promise;
                if (!openedProjects[val]){
                  window.SwellRT.openModel(val, function(model){
                    $timeout(function(){
                      var pr = swellRT.proxy(model);
                      openedProjects[val] = pr;
                      projDef.resolve(pr);
                    });
                  });
                } else {
                  projDef.resolve(openedProjects[val]);
                }
              });

              var projsDef = $q.all(promises);
              return projsDef;
            },
            destroy: function(projId){
              var i = community.projects.indexOf(projId);
              if (i > -1){
                community.projects.splice(i,1);
              }
            }
          }
        };
      },
      create: function(data, callback) {
        var id = window.SwellRT.createModel(function(model){
          var p = swellRT.proxy(model);
          p.name = data.name;
          p.id = id;
          p.projects = [];
          proxy.communities[id] = p;
          callback({
            community: p,
            projects: {
              // TODO avoid repeated function
              all: function() {
                var promises = {};
                angular.forEach(community.projects, function(val){
                  var projDef = $q.defer();
                  promises[val] = projDef.promise;
                  if (!openedProjects[val]){
                    window.SwellRT.openModel(val, function(model){
                      $timeout(function(){
                        var pr = swellRT.proxy(model);
                        openedProjects[val] = pr;
                        projDef.resolve(pr);
                      });
                    });
                  } else {
                    projDef.resolve(openedProjects[val]);
                  }
                });

                var projsDef = $q.all(promises);
                return projsDef;
              }
            }});
        });
      },
      destroy: function(urlId) {
        var id = base64.decode(urlId);

        delete proxy.communities[id];

        return urlId;
      }
    };

    var projects = {

      find: function(urlId) {
        var id = base64.decode(urlId);

        def = $q.defer();

        if (!openedProjects[id]) {
          window.SwellRT.openModel(id, function(model){
            var pr = swellRT.proxy(model);
            openedProjects[id] = pr;
            def.resolve(openedProjects[id]);
          }, function(error){
            def.reject(error);
          });
        } else {
          def.resolve(openedProjects[id]);
        }
        return def.promise;
      },
      create: function(callback) {
        var id = window.SwellRT.createModel(function(model){
          var p = {
            proj : {}
          };
          proxy.proj = swellRT.proxy(model);
          $timeout(function(){
            proxy.proj['id'] = id;
            proxy.proj['title'] = '';
            proxy.proj['chat'] = [];
            proxy.proj['pad'] = new swellRT.TextObject();
            proxy.proj['needs'] = [];
            proxy.proj['promoter'] = users.current();
            openedProjects[id] = proxy.proj;
            callback(proxy.proj);
          });
        });
      }
    };

    var users = {
      current: function() {
        return window.sessionStorage.getItem('userId');
      },
      setCurrent: function(name) {
        return window.sessionStorage.setItem('userId', name);
      },
      isCurrent: function(user) {
        return user === users.current();
      },
      loggedIn: function() {
        return users.current() !== 'undefined' && users.current() !== null;
      }
    };

    var addChatMessage = function(projectId, message) {
      projects.find(projectId).then(function(project){
        project.chat.push({
          text: message,
          who: users.current(),
          time: (new Date()).toJSON()
        });
      }, function(error){
        console.log(error);
      });
    };

    window.onSwellRTReady = function () {
      window.SwellRT.startSession(
        SwellRTConfig.server, SwellRTConfig.user, SwellRTConfig.pass,
        function() {
          // Open Community List
          window.SwellRT.openModel(
            SwellRTConfig.communityListWaveId,
            function(model) {
              proxy.communities = swellRT.proxy(model);
              if (!proxy.communities){
                proxy['communities'] = {};
              }
              def.resolve(proxy.model);
            },
            function(error){
              console.log(error);
            });
          },

        function(error) {
          console.log(error);
        });
      // to avoid multiple calls
      window.onSwellRTReady = null;
    };

    if (window.SwellRT && typeof window.onSwellRTReady === 'function') {
      window.onSwellRTReady();
    }

    return {
      communities: communities,
      projects: projects,
      users: users,
      urlId: urlId,
      addChatMessage: addChatMessage,
      onLoad: function(f) {
        def.promise.then(f);
      }
    };
  }]);
<|endoftext|>


<commit_before>import 'whatwg-fetch';
import assign from 'object-assign';
import BluebirdPromise from 'bluebird';
import { stringify as stringifyQS } from 'querystring';
import LoginStore from '../stores/LoginStore';

// return Bluebird promises from native fetch calls, too
function fetch(...args) {
  return BluebirdPromise.resolve(window.fetch(...args));
}

function makeUrl(path, params = {}) {
  let uri = path;
  const token = LoginStore.getToken();

  const query = token ? { token, ...params } : params;
  if (query) {
    // heh…
    uri += (uri.indexOf('?') !== -1 ? '&' : '?') + stringifyQS(query);
  }

  return uri;
}

function rejectNonOK(response) {
  if (response.status !== 200) {
    return response.json().then(message => {
      const error = assign(new Error(message), { response });
      return Promise.reject(error);
    });
  }
  return response;
}

export function get(uri, data) {
  return fetch(makeUrl(uri, data), {
    method: 'get',
    credentials: 'same-origin'
  }).then(rejectNonOK);
}

export function post(uri, data) {
  return fetch(makeUrl(uri), {
    method: 'post',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}

export function put(uri, data) {
  return fetch(makeUrl(uri), {
    method: 'put',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}

export function del(uri, data) {
  return fetch(makeUrl(uri), {
    method: 'delete',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}
<commit_msg>request: log debug info<commit_after>import 'whatwg-fetch';
import assign from 'object-assign';
import BluebirdPromise from 'bluebird';
import { stringify as stringifyQS } from 'querystring';
import LoginStore from '../stores/LoginStore';

const debug = require('debug')('uwave:util:request');

// return Bluebird promises from native fetch calls, too
function fetch(...args) {
  return BluebirdPromise.resolve(window.fetch(...args));
}

function makeUrl(path, params = {}) {
  let uri = path;
  const token = LoginStore.getToken();

  const query = token ? { token, ...params } : params;
  if (query) {
    // heh…
    uri += (uri.indexOf('?') !== -1 ? '&' : '?') + stringifyQS(query);
  }

  return uri;
}

function rejectNonOK(response) {
  if (response.status !== 200) {
    return response.json().then(message => {
      const error = assign(new Error(message), { response });
      return Promise.reject(error);
    });
  }
  return response;
}

export function get(uri, data) {
  debug('get', uri, stringifyQS(data));
  return fetch(makeUrl(uri, data), {
    method: 'get',
    credentials: 'same-origin'
  }).then(rejectNonOK);
}

export function post(uri, data) {
  debug('post', uri, JSON.stringify(data));
  return fetch(makeUrl(uri), {
    method: 'post',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}

export function put(uri, data) {
  debug('put', uri, JSON.stringify(data));
  return fetch(makeUrl(uri), {
    method: 'put',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}

export function del(uri, data) {
  debug('del', uri, JSON.stringify(data));
  return fetch(makeUrl(uri), {
    method: 'delete',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin',
    body: JSON.stringify(data)
  }).then(rejectNonOK);
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    test: {
      files: ['test/**/*.js']
    },
    lint: {
      files: ['grunt.js', 'tasks/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: '<config:lint.files>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true,
        es5: true,

        strict: false
      },
      globals: {}
    }
  });

  // Load local tasks.
  grunt.loadTasks('tasks');

  // Default task.
  grunt.registerTask('default', 'lint test');

};
<commit_msg>Removed test from base grunt.js<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    lint: {
      files: ['grunt.js', 'tasks/**/*.js', 'test/**/*.js']
    },
    watch: {
      files: '<config:lint.files>',
      tasks: 'default'
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        node: true,
        es5: true,

        strict: false
      },
      globals: {}
    }
  });

  // Load local tasks.
  grunt.loadTasks('tasks');

  // Default task.
  grunt.registerTask('default', 'lint');

};
<|endoftext|>


<commit_before>// scrollEvents version 1.0.0
//
// Ryan P.C. McQuen | Everett, WA | ryan.q@linux.com
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version, with the following exception:
// the text of the GPL license may be omitted.
//
// This program is distributed in the hope that it will be useful, but
// without any warranty; without even the implied warranty of
// merchantability or fitness for a particular purpose. Compiling,
// interpreting, executing or merely reading the text of the program
// may result in lapses of consciousness and/or very being, up to and
// including the end of all existence and the Universe as we know it.
// See the GNU General Public License for more details.
//
// You may have received a copy of the GNU General Public License along
// with this program (most likely, a file named COPYING).  If not, see
// <https://www.gnu.org/licenses/>.
(function (win, doc) {
  'use strict';
  if (win.scrollEvents) {
    // better to throw a new Error than just a string
    throw new Error('You already have a global variable named scrollEvents.');
  }
  // slightly modified/simplified version of underscore.js's throttle (v1.8.3)
  function throttle(func, wait) {
    var timeout = null,
      previous = 0,
      later = function () {
        previous = Date.now();
        timeout = null;
        func();
      };
    return function () {
      var now = Date.now();
      if (!previous) previous = now;
      var remaining = wait - (now - previous);
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        func();
      } else if (!timeout) {
        timeout = setTimeout(later, remaining);
      }
    };
  }
  // select all elements matching selector
  function qsa(selector) {
    return doc.querySelectorAll(selector);
  }

  // Function.prototype.call shortcut
  var call = qsa.call,
    // alias forEach since we use it so much
    each = call.bind([].forEach),
    // slice shortcut
    slice = call.bind([].slice),
    // array to hold all event listeners
    listeners = [],
    // run all listeners
    runner = throttle(function() {
      each(listeners, function(listener) {
        listener();
      });
    }, 50);

  // add scroll listener
  function addEventListener(fn) {
    if (!listeners.length) {
      // add single dom event listener that will run all registered listeners
      win.addEventListener('scroll', runner);
    }

    listeners.push(fn);

    return function removeEventListener() {
      // find current fn index
      var index = listeners.indexOf(fn);
      // if it is still there,
      // remove
      if (index > -1) {

        listeners.splice(index, 1);
      }

      // if no more listeners left,
      // unbind
      if (!listeners.length) {
        win.removeEventListener('scroll', runner);
      }
    };
  }

  // all scrollEvents public methods
  // both static and prototypes
  var methods = {
      /**
       * Changes element class.
       */
      changeClass: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeClass.');
        }

        return function (el, below) {
          var classes = el.classList;
          classes.toggle(initial, !below);
          classes.toggle(changed, below);
        };
      },

      /**
       * Changes element html content.
       */
      changeHTML: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeHTML.');
        }

        return function (el, below) {
          el.innerHTML = below ? changed : initial;
        };
      },

      /**
       * Changes element.style.property value.
       */
      changeStyle: function (property, initial, changed) {
        if (arguments.length < 3) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeStyle.');
        }

        return function (el, below) {
          el.style[property] = below ? changed : initial;
        };
      },

      /**
       * Changes element.textContent.
       */
      changeText: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeText.');
        }

        return function (el, below) {
          el.textContent = below ? changed : initial;
        };
      }
    },

    // all method names
    methodNames = Object.keys(methods),
    // when* methods (instance only)
    whenMethods = {
      /**
       * Generic method
       */
      when: function(breakPoint) {
        var type;
        if(!breakPoint) {
          throw new Error('breakPoint is required for scrollEvents.when.');
        }
        type = typeof breakPoint;

        switch (type) {
          case 'function': return breakPoint;
          case 'string': return whenMethods.whenElement.apply(whenMethods, arguments);
          case 'number': return whenMethods.whenDistance.apply(whenMethods, arguments);
          default: throw new Error('Unknown breakPoint type "' + type + '" for scrollEvents.when.');
        }
      },

      /**
       * When scroll distance reaches the breakpoint
       */
      whenDistance: function(breakPoint) {
        if(typeof breakPoint !==  'number') {
          throw new Error('breakPoint should be a number for scrollEvents.whenDistance.');
        }
        return function() {
          return breakPoint;
        };
      },

      /**
       * When element selector enters the view port or hits top.
       */
      whenElement: function(selector, hitsTop) {
        if(typeof selector !== 'string') {
          throw new Error('breakPoint should be a string for scrollEvents.whenElement.');
        }

        selector = doc.querySelector(selector);

        // we want user be expicit whether she/he wants to trigger event when element hits top
        if(hitsTop === true) {
          return function() {
            return selector.offsetTop;
          };
        }
        // enters the viewport
        return function() {
          // all modern browser support window.innerHeright
          return selector.offsetTop - win.innerHeight;
        };
      },

      /**
       * When element enters the viewport.
       */
      whenElementEnters: function(selector) {
        return whenMethods.whenElement(selector, false);
      },

      /**
       * When element hits the viewport top.
       */
      whenElementHitsTop: function(selector) {
        return whenMethods.whenElement(selector, true);
      }
    },
    // all when* method names
    whenMethodNames = Object.keys(whenMethods);

  function scrollSpy(selector) {
    var spy = {},
      // all registered callbacks changers.
      callbacks = [],
      // default breakpoint getter
      defaultBreakPoint;

    // spy requires selector.
    if(!selector) {
      throw new Error('Selector is required to apply scroll events to.');
    }

    // enhance spy object with public methods.
    methodNames.forEach(function (name) {
      // original method
      var method = methods[name];

      // make function that checks if we cross the breakPoint
      function makeBreakPointChecker(args) {
        var params = slice(args, method.length),
          breakPoint = params.length;

        breakPoint = breakPoint ? whenMethods.when.apply(whenMethods, params) : defaultBreakPoint;

        return function() {
          return win.pageYOffset > breakPoint();
        };
      }

      spy[name] = function () {
        // previous value
        var previous,
          args = arguments,
          // make isBelow checker
          isBelow = makeBreakPointChecker(args),
          // make el changer
          callback = method.apply(spy, args);

        // add to callback list
        callbacks.push(function (elements) {
          // get current value
          var current = isBelow();

          // check if it was actually changed
          if (current !== previous) {
            // save for the next call
            previous = current;

            // check if another callback has already made dom query
            if (!elements) {
              // query the DOM
              elements = qsa(selector);
            }
            // apply changer to each element
            each(elements, function (el) {
              callback(el, current);
            });
          }

          // return elements for the next callback to avoid multiple queries
          return elements;
        });

        return spy;
      };
    });

    // enhance spy object with when* methods
    whenMethodNames.forEach(function(name) {
      var method = whenMethods[name];

      method = method.apply.bind(method, methodNames);

      spy[name] = function() {
        defaultBreakPoint = method(arguments);
        return spy;
      };
    });

    // run all callbacks passing elements around
    function listener() {
      var elements;

      each(callbacks, function (cb) {
        elements = cb(elements);
      });
    }

    // does nothing but returns current spy
    function noop() {
      return spy;
    }

    // create on method that binds scroll listener for current spy
    spy.on = function on() {
      // add DOM listener
      var unbinder = addEventListener(listener);

      // avoid multiple inits
      spy.on = noop;

      // make a turn off method
      spy.off = function off() {
        // unbind
        unbinder();

        // noop to avoid multiple unbinds.
        spy.off = noop;

        // restore on method
        spy.on = on;

        return spy;
      };

      return spy;
    };

    // init
    spy.on();
    // init default breakpoint getter
    spy.when(scrollSpy.breakPoint, !scrollSpy.useViewportHeight);

    return spy;
  }

  // default breakPoint
  scrollSpy.breakPoint = 10;

  // add the option to use the viewport height,
  // so that events can trigger when objects first
  // enter the viewport, rather than when they
  // hit the top of the page, ONLY activates
  // when breakPoint is a selector
  //
  // default is true until i am convinced that false
  // is more intuitive
  scrollSpy.useViewportHeight = true;

  // enhance scrollSpy with static methods
  methodNames.forEach(function(name) {
    scrollSpy[name] = function(selector/*, rest...*/) {
      var rest = slice(arguments, 1),
        // create a spy
        spy = scrollSpy(selector);

      // and invoke corresponding method on it
      return spy[name].apply(spy, rest);
    };
  });

  // export
  win.scrollEvents = scrollSpy;
})(window, document);
<commit_msg>typo slipped through<commit_after>// scrollEvents version 1.0.0
//
// Ryan P.C. McQuen | Everett, WA | ryan.q@linux.com
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version, with the following exception:
// the text of the GPL license may be omitted.
//
// This program is distributed in the hope that it will be useful, but
// without any warranty; without even the implied warranty of
// merchantability or fitness for a particular purpose. Compiling,
// interpreting, executing or merely reading the text of the program
// may result in lapses of consciousness and/or very being, up to and
// including the end of all existence and the Universe as we know it.
// See the GNU General Public License for more details.
//
// You may have received a copy of the GNU General Public License along
// with this program (most likely, a file named COPYING).  If not, see
// <https://www.gnu.org/licenses/>.
(function (win, doc) {
  'use strict';
  if (win.scrollEvents) {
    // better to throw a new Error than just a string
    throw new Error('You already have a global variable named scrollEvents.');
  }
  // slightly modified/simplified version of underscore.js's throttle (v1.8.3)
  function throttle(func, wait) {
    var timeout = null,
      previous = 0,
      later = function () {
        previous = Date.now();
        timeout = null;
        func();
      };
    return function () {
      var now = Date.now();
      if (!previous) previous = now;
      var remaining = wait - (now - previous);
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        func();
      } else if (!timeout) {
        timeout = setTimeout(later, remaining);
      }
    };
  }
  // select all elements matching selector
  function qsa(selector) {
    return doc.querySelectorAll(selector);
  }

  // Function.prototype.call shortcut
  var call = qsa.call,
    // alias forEach since we use it so much
    each = call.bind([].forEach),
    // slice shortcut
    slice = call.bind([].slice),
    // array to hold all event listeners
    listeners = [],
    // run all listeners
    runner = throttle(function() {
      each(listeners, function(listener) {
        listener();
      });
    }, 50);

  // add scroll listener
  function addEventListener(fn) {
    if (!listeners.length) {
      // add single dom event listener that will run all registered listeners
      win.addEventListener('scroll', runner);
    }

    listeners.push(fn);

    return function removeEventListener() {
      // find current fn index
      var index = listeners.indexOf(fn);
      // if it is still there,
      // remove
      if (index > -1) {

        listeners.splice(index, 1);
      }

      // if no more listeners left,
      // unbind
      if (!listeners.length) {
        win.removeEventListener('scroll', runner);
      }
    };
  }

  // all scrollEvents public methods
  // both static and prototypes
  var methods = {
      /**
       * Changes element class.
       */
      changeClass: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeClass.');
        }

        return function (el, below) {
          var classes = el.classList;
          classes.toggle(initial, !below);
          classes.toggle(changed, below);
        };
      },

      /**
       * Changes element html content.
       */
      changeHTML: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeHTML.');
        }

        return function (el, below) {
          el.innerHTML = below ? changed : initial;
        };
      },

      /**
       * Changes element.style.property value.
       */
      changeStyle: function (property, initial, changed) {
        if (arguments.length < 3) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeStyle.');
        }

        return function (el, below) {
          el.style[property] = below ? changed : initial;
        };
      },

      /**
       * Changes element.textContent.
       */
      changeText: function (initial, changed) {
        if (arguments.length < 2) {
          throw new Error('You have not supplied all parameters to scrollEvents.changeText.');
        }

        return function (el, below) {
          el.textContent = below ? changed : initial;
        };
      }
    },

    // all method names
    methodNames = Object.keys(methods),
    // when* methods (instance only)
    whenMethods = {
      /**
       * Generic method
       */
      when: function(breakPoint) {
        var type;
        if(!breakPoint) {
          throw new Error('breakPoint is required for scrollEvents.when.');
        }
        type = typeof breakPoint;

        switch (type) {
          case 'function': return breakPoint;
          case 'string': return whenMethods.whenElement.apply(whenMethods, arguments);
          case 'number': return whenMethods.whenDistance.apply(whenMethods, arguments);
          default: throw new Error('Unknown breakPoint type "' + type + '" for scrollEvents.when.');
        }
      },

      /**
       * When scroll distance reaches the breakpoint
       */
      whenDistance: function(breakPoint) {
        if(typeof breakPoint !==  'number') {
          throw new Error('breakPoint should be a number for scrollEvents.whenDistance.');
        }
        return function() {
          return breakPoint;
        };
      },

      /**
       * When element selector enters the view port or hits top.
       */
      whenElement: function(selector, hitsTop) {
        if(typeof selector !== 'string') {
          throw new Error('breakPoint should be a string for scrollEvents.whenElement.');
        }

        selector = doc.querySelector(selector);

        // we want user be expicit whether she/he wants to trigger event when element hits top
        if(hitsTop === true) {
          return function() {
            return selector.offsetTop;
          };
        }
        // enters the viewport
        return function() {
          // all modern browsers support window.innerHeright
          return selector.offsetTop - win.innerHeight;
        };
      },

      /**
       * When element enters the viewport.
       */
      whenElementEnters: function(selector) {
        return whenMethods.whenElement(selector, false);
      },

      /**
       * When element hits the viewport top.
       */
      whenElementHitsTop: function(selector) {
        return whenMethods.whenElement(selector, true);
      }
    },
    // all when* method names
    whenMethodNames = Object.keys(whenMethods);

  function scrollSpy(selector) {
    var spy = {},
      // all registered callbacks changers.
      callbacks = [],
      // default breakpoint getter
      defaultBreakPoint;

    // spy requires selector.
    if(!selector) {
      throw new Error('Selector is required to apply scroll events to.');
    }

    // enhance spy object with public methods.
    methodNames.forEach(function (name) {
      // original method
      var method = methods[name];

      // make function that checks if we cross the breakPoint
      function makeBreakPointChecker(args) {
        var params = slice(args, method.length),
          breakPoint = params.length;

        breakPoint = breakPoint ? whenMethods.when.apply(whenMethods, params) : defaultBreakPoint;

        return function() {
          return win.pageYOffset > breakPoint();
        };
      }

      spy[name] = function () {
        // previous value
        var previous,
          args = arguments,
          // make isBelow checker
          isBelow = makeBreakPointChecker(args),
          // make el changer
          callback = method.apply(spy, args);

        // add to callback list
        callbacks.push(function (elements) {
          // get current value
          var current = isBelow();

          // check if it was actually changed
          if (current !== previous) {
            // save for the next call
            previous = current;

            // check if another callback has already made dom query
            if (!elements) {
              // query the DOM
              elements = qsa(selector);
            }
            // apply changer to each element
            each(elements, function (el) {
              callback(el, current);
            });
          }

          // return elements for the next callback to avoid multiple queries
          return elements;
        });

        return spy;
      };
    });

    // enhance spy object with when* methods
    whenMethodNames.forEach(function(name) {
      var method = whenMethods[name];

      method = method.apply.bind(method, methodNames);

      spy[name] = function() {
        defaultBreakPoint = method(arguments);
        return spy;
      };
    });

    // run all callbacks passing elements around
    function listener() {
      var elements;

      each(callbacks, function (cb) {
        elements = cb(elements);
      });
    }

    // does nothing but returns current spy
    function noop() {
      return spy;
    }

    // create on method that binds scroll listener for current spy
    spy.on = function on() {
      // add DOM listener
      var unbinder = addEventListener(listener);

      // avoid multiple inits
      spy.on = noop;

      // make a turn off method
      spy.off = function off() {
        // unbind
        unbinder();

        // noop to avoid multiple unbinds.
        spy.off = noop;

        // restore on method
        spy.on = on;

        return spy;
      };

      return spy;
    };

    // init
    spy.on();
    // init default breakpoint getter
    spy.when(scrollSpy.breakPoint, !scrollSpy.useViewportHeight);

    return spy;
  }

  // default breakPoint
  scrollSpy.breakPoint = 10;

  // add the option to use the viewport height,
  // so that events can trigger when objects first
  // enter the viewport, rather than when they
  // hit the top of the page, ONLY activates
  // when breakPoint is a selector
  //
  // default is true until i am convinced that false
  // is more intuitive
  scrollSpy.useViewportHeight = true;

  // enhance scrollSpy with static methods
  methodNames.forEach(function(name) {
    scrollSpy[name] = function(selector/*, rest...*/) {
      var rest = slice(arguments, 1),
        // create a spy
        spy = scrollSpy(selector);

      // and invoke corresponding method on it
      return spy[name].apply(spy, rest);
    };
  });

  // export
  win.scrollEvents = scrollSpy;
})(window, document);
<|endoftext|>