<commit_before>/*jslint node: true */
"use strict";
var _ = require('lodash');
var db = require('./db.js');
var constants = require("./constants.js");
var conf = require("./conf.js");
var storage = require("./storage.js");
var main_chain = require("./main_chain.js");

function pickParentUnits(conn, arrWitnesses, onDone){
	// don't exclude units derived from unwitnessed potentially bad units! It is not their blame and can cause a split.

	// test creating bad units
	//var cond = bDeep ? "is_on_main_chain=1" : "is_free=0 AND main_chain_index=1420";
	//var order_and_limit = bDeep ? "ORDER BY main_chain_index DESC LIMIT 1" : "ORDER BY unit LIMIT 1";

	conn.query(
		"SELECT \n\
			unit, version, alt, ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			) AS count_matching_witnesses \n\
		FROM units "+(conf.storage === 'sqlite' ? "INDEXED BY byFree" : "")+" \n\
		LEFT JOIN archived_joints USING(unit) \n\
		WHERE +sequence='good' AND is_free=1 AND archived_joints.unit IS NULL ORDER BY unit",
		// exclude potential parents that were archived and then received again
		[arrWitnesses],
		function(rows){
			if (rows.some(function(row){ return (row.version !== constants.version || row.alt !== constants.alt); }))
				throw Error('wrong network');
			var count_required_matches = constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS;
			// we need at least one compatible parent, otherwise go deep
			if (rows.filter(function(row){ return (row.count_matching_witnesses >= count_required_matches); }).length === 0)
				return pickDeepParentUnits(conn, arrWitnesses, null, onDone);
			var arrParentUnits = rows.map(function(row){ return row.unit; });
			adjustParentsToNotRetreatWitnessedLevel(conn, arrWitnesses, arrParentUnits, function(arrAdjustedParents){
				onDone(null, arrAdjustedParents.slice(0, constants.MAX_PARENTS_PER_UNIT));
			});
		//	checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, (arrParentUnits.length === 1), onDone);
		}
	);
}

function adjustParentsToNotRetreatWitnessedLevel(conn, arrWitnesses, arrParentUnits, handleAdjustedParents){
	var arrExcludedUnits = [];
	var iterations = 0;

	function replaceExcludedParent(arrCurrentParentUnits, excluded_unit){
		console.log('replaceExcludedParent '+arrCurrentParentUnits.join(', ')+" excluding "+excluded_unit);
		var arrNewExcludedUnits = [excluded_unit];
		console.log('excluded parents: '+arrNewExcludedUnits.join(', '));
		arrExcludedUnits = arrExcludedUnits.concat(arrNewExcludedUnits);
		var arrParentsToKeep = _.difference(arrCurrentParentUnits, arrNewExcludedUnits);
		conn.query("SELECT DISTINCT parent_unit FROM parenthoods WHERE child_unit IN(?)", [arrNewExcludedUnits], function(rows){
			var arrCandidateReplacements = rows.map(function(row){ return row.parent_unit; });
			console.log('candidate replacements: '+arrCandidateReplacements.join(', '));
			conn.query(
				"SELECT DISTINCT parent_unit FROM parenthoods WHERE parent_unit IN(?) AND child_unit NOT IN(?)",
				[arrCandidateReplacements, arrExcludedUnits],
				function(rows){
					var arrCandidatesWithOtherChildren = rows.map(function(row){ return row.parent_unit; });
					console.log('candidates with other children: '+arrCandidatesWithOtherChildren.join(', '));
					var arrReplacementParents = _.difference(arrCandidateReplacements, arrCandidatesWithOtherChildren);
					console.log('replacements for excluded parents: '+arrReplacementParents.join(', '));
					var arrNewParents = arrParentsToKeep.concat(arrReplacementParents);
					console.log('new parents: '+arrNewParents.join(', '));
					if (arrNewParents.length === 0)
						throw Error("no new parents for initial parents "+arrParentUnits.join(', ')+" and witnesses "+arrWitnesses.join(', '));
					checkWitnessedLevelAndReplace(arrNewParents);
				}
			);
		});
	}

	function checkWitnessedLevelAndReplace(arrCurrentParentUnits){
		console.log('checkWitnessedLevelAndReplace '+arrCurrentParentUnits.join(', '));
		if (iterations > 0 && arrExcludedUnits.length === 0)
			throw Error("infinite cycle");
		iterations++;
		determineWitnessedLevels(conn, arrWitnesses, arrCurrentParentUnits, function(child_witnessed_level, best_parent_witnessed_level, best_parent_unit){
			if (child_witnessed_level >= best_parent_witnessed_level)
				return handleAdjustedParents(arrCurrentParentUnits.sort());
			console.log('wl would retreat from '+best_parent_witnessed_level+' to '+child_witnessed_level+', parents '+arrCurrentParentUnits.join(', '));
			replaceExcludedParent(arrCurrentParentUnits, best_parent_unit);
		});
	}

	checkWitnessedLevelAndReplace(arrParentUnits);
}

function pickParentUnitsUnderWitnessedLevel(conn, arrWitnesses, max_wl, onDone){
	console.log("looking for free parents under wl "+max_wl);
	conn.query(
		"SELECT unit \n\
		FROM units "+(conf.storage === 'sqlite' ? "INDEXED BY byFree" : "")+" \n\
		WHERE +sequence='good' AND is_free=1 AND witnessed_level<? \n\
			AND ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			)>=? \n\
		ORDER BY witnessed_level DESC, level DESC LIMIT ?",
		[max_wl, arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS, constants.MAX_PARENTS_PER_UNIT],
		function(rows){
			if (rows.length === 0)
				return pickDeepParentUnits(conn, arrWitnesses, max_wl, onDone);
			var arrParentUnits = rows.map(function(row){ return row.unit; }).sort();
			checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, true, onDone);
		}
	);
}

// if we failed to find compatible parents among free units.
// (This may be the case if an attacker floods the network trying to shift the witness list)
function pickDeepParentUnits(conn, arrWitnesses, max_wl, onDone){
	// fixed: an attacker could cover all free compatible units with his own incompatible ones, then those that were not on MC will be never included
	//var cond = bDeep ? "is_on_main_chain=1" : "is_free=1";

	console.log("looking for deep parents, max_wl="+max_wl);
	var and_wl = (max_wl === null) ? '' : "AND +is_on_main_chain=1 AND witnessed_level<"+max_wl;
	conn.query(
		"SELECT unit \n\
		FROM units \n\
		WHERE +sequence='good' "+and_wl+" \n\
			AND ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			)>=? \n\
		ORDER BY main_chain_index DESC LIMIT 1",
		[arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS],
		function(rows){
			if (rows.length === 0)
				return onDone("failed to find compatible parents: no deep units");
			var arrParentUnits = rows.map(function(row){ return row.unit; });
			checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, true, onDone);
		}
	);
}

function determineWitnessedLevels(conn, arrWitnesses, arrParentUnits, handleResult){
	storage.determineWitnessedLevelAndBestParent(conn, arrParentUnits, arrWitnesses, function(witnessed_level, best_parent_unit){
		storage.readStaticUnitProps(conn, best_parent_unit, function(bestParentProps){
			handleResult(witnessed_level, bestParentProps.witnessed_level, best_parent_unit);
		});
	});
}

function checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, bRetryDeeper, onDone){
	determineWitnessedLevels(conn, arrWitnesses, arrParentUnits, function(child_witnessed_level, best_parent_witnessed_level){
		if (child_witnessed_level >= best_parent_witnessed_level)
			return onDone(null, arrParentUnits);
		console.log("witness level would retreat from "+best_parent_witnessed_level+" to "+child_witnessed_level+" if parents = "+arrParentUnits.join(', ')+", will look for older parents");
		bRetryDeeper
			? pickDeepParentUnits(conn, arrWitnesses, best_parent_witnessed_level, onDone)
			: pickParentUnitsUnderWitnessedLevel(conn, arrWitnesses, best_parent_witnessed_level, onDone);
	});
}

function findLastStableMcBall(conn, arrWitnesses, onDone){
	conn.query(
		"SELECT ball, unit, main_chain_index FROM units JOIN balls USING(unit) \n\
		WHERE is_on_main_chain=1 AND is_stable=1 AND +sequence='good' AND ( \n\
			SELECT COUNT(*) \n\
			FROM unit_witnesses \n\
			WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
		)>=? \n\
		ORDER BY main_chain_index DESC LIMIT 1",
		[arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS],
		function(rows){
			if (rows.length === 0)
				return onDone("failed to find last stable ball");
			onDone(null, rows[0].ball, rows[0].unit, rows[0].main_chain_index);
		}
	);
}

function adjustLastStableMcBallAndParents(conn, last_stable_mc_ball_unit, arrParentUnits, arrWitnesses, handleAdjustedLastStableUnit){
	main_chain.determineIfStableInLaterUnits(conn, last_stable_mc_ball_unit, arrParentUnits, function(bStable){
		if (bStable){
			conn.query("SELECT ball, main_chain_index FROM units JOIN balls USING(unit) WHERE unit=?", [last_stable_mc_ball_unit], function(rows){
				if (rows.length !== 1)
					throw Error("not 1 ball by unit "+last_stable_mc_ball_unit);
				var row = rows[0];
				handleAdjustedLastStableUnit(row.ball, last_stable_mc_ball_unit, row.main_chain_index, arrParentUnits);
			});
			return;
		}
		console.log('will adjust last stable ball because '+last_stable_mc_ball_unit+' is not stable in view of parents '+arrParentUnits.join(', '));
		if (arrParentUnits.length > 1){ // select only one parent
			pickDeepParentUnits(conn, arrWitnesses, null, function(err, arrAdjustedParentUnits){
				if (err)
					throw Error("pickDeepParentUnits in adjust failed: "+err);
				adjustLastStableMcBallAndParents(conn, last_stable_mc_ball_unit, arrAdjustedParentUnits, arrWitnesses, handleAdjustedLastStableUnit);
			});
			return;
		}
		storage.readStaticUnitProps(conn, last_stable_mc_ball_unit, function(objUnitProps){
			if (!objUnitProps.best_parent_unit)
				throw Error("no best parent of "+last_stable_mc_ball_unit);
			adjustLastStableMcBallAndParents(conn, objUnitProps.best_parent_unit, arrParentUnits, arrWitnesses, handleAdjustedLastStableUnit);
		});
	});
}

function trimParentList(conn, arrParentUnits, arrWitnesses, handleTrimmedList){
	if (arrParentUnits.length <= constants.MAX_PARENTS_PER_UNIT)
		return handleTrimmedList(arrParentUnits);
	conn.query(
		"SELECT unit, (SELECT 1 FROM unit_authors WHERE unit_authors.unit=units.unit AND address IN(?) LIMIT 1) AS is_witness \n\
		FROM units WHERE unit IN("+arrParentUnits.map(db.escape).join(', ')+") ORDER BY is_witness DESC, "+db.getRandom()+" LIMIT ?",
		[arrWitnesses, constants.MAX_PARENTS_PER_UNIT],
		function(rows){
			handleTrimmedList(rows.map(function(row){ return row.unit; }).sort());
		}
	);
}

function pickParentUnitsAndLastBall(conn, arrWitnesses, onDone){
	pickParentUnits(conn, arrWitnesses, function(err, arrParentUnits){
		if (err)
			return onDone(err);
		findLastStableMcBall(conn, arrWitnesses, function(err, last_stable_mc_ball, last_stable_mc_ball_unit, last_stable_mc_ball_mci){
			if (err)
				return onDone(err);
			adjustLastStableMcBallAndParents(
				conn, last_stable_mc_ball_unit, arrParentUnits, arrWitnesses,
				function(last_stable_ball, last_stable_unit, last_stable_mci, arrAdjustedParentUnits){
					trimParentList(conn, arrAdjustedParentUnits, arrWitnesses, function(arrTrimmedParentUnits){
						storage.findWitnessListUnit(conn, arrWitnesses, last_stable_mci, function(witness_list_unit){
							var objFakeUnit = {parent_units: arrTrimmedParentUnits};
							if (witness_list_unit)
								objFakeUnit.witness_list_unit = witness_list_unit;
							storage.determineIfHasWitnessListMutationsAlongMc(conn, objFakeUnit, last_stable_unit, arrWitnesses, function(err){
								if (err)
									return onDone(err); // if first arg is not array, it is error
								onDone(null, arrTrimmedParentUnits, last_stable_ball, last_stable_unit, last_stable_mci);
							});
						});
					});
				}
			);
		});
	});
}

exports.pickParentUnitsAndLastBall = pickParentUnitsAndLastBall;
<commit_msg>adjust last ball with all parents<commit_after>/*jslint node: true */
"use strict";
var _ = require('lodash');
var db = require('./db.js');
var constants = require("./constants.js");
var conf = require("./conf.js");
var storage = require("./storage.js");
var main_chain = require("./main_chain.js");

function pickParentUnits(conn, arrWitnesses, onDone){
	// don't exclude units derived from unwitnessed potentially bad units! It is not their blame and can cause a split.

	// test creating bad units
	//var cond = bDeep ? "is_on_main_chain=1" : "is_free=0 AND main_chain_index=1420";
	//var order_and_limit = bDeep ? "ORDER BY main_chain_index DESC LIMIT 1" : "ORDER BY unit LIMIT 1";

	conn.query(
		"SELECT \n\
			unit, version, alt, ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			) AS count_matching_witnesses \n\
		FROM units "+(conf.storage === 'sqlite' ? "INDEXED BY byFree" : "")+" \n\
		LEFT JOIN archived_joints USING(unit) \n\
		WHERE +sequence='good' AND is_free=1 AND archived_joints.unit IS NULL ORDER BY unit",
		// exclude potential parents that were archived and then received again
		[arrWitnesses],
		function(rows){
			if (rows.some(function(row){ return (row.version !== constants.version || row.alt !== constants.alt); }))
				throw Error('wrong network');
			var count_required_matches = constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS;
			// we need at least one compatible parent, otherwise go deep
			if (rows.filter(function(row){ return (row.count_matching_witnesses >= count_required_matches); }).length === 0)
				return pickDeepParentUnits(conn, arrWitnesses, null, onDone);
			var arrParentUnits = rows.map(function(row){ return row.unit; });
			adjustParentsToNotRetreatWitnessedLevel(conn, arrWitnesses, arrParentUnits, function(arrAdjustedParents){
				onDone(null, arrAdjustedParents.slice(0, constants.MAX_PARENTS_PER_UNIT));
			});
		//	checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, (arrParentUnits.length === 1), onDone);
		}
	);
}

function adjustParentsToNotRetreatWitnessedLevel(conn, arrWitnesses, arrParentUnits, handleAdjustedParents){
	var arrExcludedUnits = [];
	var iterations = 0;

	function replaceExcludedParent(arrCurrentParentUnits, excluded_unit){
		console.log('replaceExcludedParent '+arrCurrentParentUnits.join(', ')+" excluding "+excluded_unit);
		var arrNewExcludedUnits = [excluded_unit];
		console.log('excluded parents: '+arrNewExcludedUnits.join(', '));
		arrExcludedUnits = arrExcludedUnits.concat(arrNewExcludedUnits);
		var arrParentsToKeep = _.difference(arrCurrentParentUnits, arrNewExcludedUnits);
		conn.query("SELECT DISTINCT parent_unit FROM parenthoods WHERE child_unit IN(?)", [arrNewExcludedUnits], function(rows){
			var arrCandidateReplacements = rows.map(function(row){ return row.parent_unit; });
			console.log('candidate replacements: '+arrCandidateReplacements.join(', '));
			conn.query(
				"SELECT DISTINCT parent_unit FROM parenthoods WHERE parent_unit IN(?) AND child_unit NOT IN(?)",
				[arrCandidateReplacements, arrExcludedUnits],
				function(rows){
					var arrCandidatesWithOtherChildren = rows.map(function(row){ return row.parent_unit; });
					console.log('candidates with other children: '+arrCandidatesWithOtherChildren.join(', '));
					var arrReplacementParents = _.difference(arrCandidateReplacements, arrCandidatesWithOtherChildren);
					console.log('replacements for excluded parents: '+arrReplacementParents.join(', '));
					var arrNewParents = arrParentsToKeep.concat(arrReplacementParents);
					console.log('new parents: '+arrNewParents.join(', '));
					if (arrNewParents.length === 0)
						throw Error("no new parents for initial parents "+arrParentUnits.join(', ')+" and witnesses "+arrWitnesses.join(', '));
					checkWitnessedLevelAndReplace(arrNewParents);
				}
			);
		});
	}

	function checkWitnessedLevelAndReplace(arrCurrentParentUnits){
		console.log('checkWitnessedLevelAndReplace '+arrCurrentParentUnits.join(', '));
		if (iterations > 0 && arrExcludedUnits.length === 0)
			throw Error("infinite cycle");
		iterations++;
		determineWitnessedLevels(conn, arrWitnesses, arrCurrentParentUnits, function(child_witnessed_level, best_parent_witnessed_level, best_parent_unit){
			if (child_witnessed_level >= best_parent_witnessed_level)
				return handleAdjustedParents(arrCurrentParentUnits.sort());
			console.log('wl would retreat from '+best_parent_witnessed_level+' to '+child_witnessed_level+', parents '+arrCurrentParentUnits.join(', '));
			replaceExcludedParent(arrCurrentParentUnits, best_parent_unit);
		});
	}

	checkWitnessedLevelAndReplace(arrParentUnits);
}

function pickParentUnitsUnderWitnessedLevel(conn, arrWitnesses, max_wl, onDone){
	console.log("looking for free parents under wl "+max_wl);
	conn.query(
		"SELECT unit \n\
		FROM units "+(conf.storage === 'sqlite' ? "INDEXED BY byFree" : "")+" \n\
		WHERE +sequence='good' AND is_free=1 AND witnessed_level<? \n\
			AND ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			)>=? \n\
		ORDER BY witnessed_level DESC, level DESC LIMIT ?",
		[max_wl, arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS, constants.MAX_PARENTS_PER_UNIT],
		function(rows){
			if (rows.length === 0)
				return pickDeepParentUnits(conn, arrWitnesses, max_wl, onDone);
			var arrParentUnits = rows.map(function(row){ return row.unit; }).sort();
			checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, true, onDone);
		}
	);
}

// if we failed to find compatible parents among free units.
// (This may be the case if an attacker floods the network trying to shift the witness list)
function pickDeepParentUnits(conn, arrWitnesses, max_wl, onDone){
	// fixed: an attacker could cover all free compatible units with his own incompatible ones, then those that were not on MC will be never included
	//var cond = bDeep ? "is_on_main_chain=1" : "is_free=1";

	console.log("looking for deep parents, max_wl="+max_wl);
	var and_wl = (max_wl === null) ? '' : "AND +is_on_main_chain=1 AND witnessed_level<"+max_wl;
	conn.query(
		"SELECT unit \n\
		FROM units \n\
		WHERE +sequence='good' "+and_wl+" \n\
			AND ( \n\
				SELECT COUNT(*) \n\
				FROM unit_witnesses \n\
				WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
			)>=? \n\
		ORDER BY main_chain_index DESC LIMIT 1",
		[arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS],
		function(rows){
			if (rows.length === 0)
				return onDone("failed to find compatible parents: no deep units");
			var arrParentUnits = rows.map(function(row){ return row.unit; });
			checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, true, onDone);
		}
	);
}

function determineWitnessedLevels(conn, arrWitnesses, arrParentUnits, handleResult){
	storage.determineWitnessedLevelAndBestParent(conn, arrParentUnits, arrWitnesses, function(witnessed_level, best_parent_unit){
		storage.readStaticUnitProps(conn, best_parent_unit, function(bestParentProps){
			handleResult(witnessed_level, bestParentProps.witnessed_level, best_parent_unit);
		});
	});
}

function checkWitnessedLevelNotRetreatingAndLookLower(conn, arrWitnesses, arrParentUnits, bRetryDeeper, onDone){
	determineWitnessedLevels(conn, arrWitnesses, arrParentUnits, function(child_witnessed_level, best_parent_witnessed_level){
		if (child_witnessed_level >= best_parent_witnessed_level)
			return onDone(null, arrParentUnits);
		console.log("witness level would retreat from "+best_parent_witnessed_level+" to "+child_witnessed_level+" if parents = "+arrParentUnits.join(', ')+", will look for older parents");
		bRetryDeeper
			? pickDeepParentUnits(conn, arrWitnesses, best_parent_witnessed_level, onDone)
			: pickParentUnitsUnderWitnessedLevel(conn, arrWitnesses, best_parent_witnessed_level, onDone);
	});
}

function findLastStableMcBall(conn, arrWitnesses, onDone){
	conn.query(
		"SELECT ball, unit, main_chain_index FROM units JOIN balls USING(unit) \n\
		WHERE is_on_main_chain=1 AND is_stable=1 AND +sequence='good' AND ( \n\
			SELECT COUNT(*) \n\
			FROM unit_witnesses \n\
			WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN(?) \n\
		)>=? \n\
		ORDER BY main_chain_index DESC LIMIT 1",
		[arrWitnesses, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS],
		function(rows){
			if (rows.length === 0)
				return onDone("failed to find last stable ball");
			onDone(null, rows[0].ball, rows[0].unit, rows[0].main_chain_index);
		}
	);
}

function adjustLastStableMcBallAndParents(conn, last_stable_mc_ball_unit, arrParentUnits, arrWitnesses, handleAdjustedLastStableUnit){
	main_chain.determineIfStableInLaterUnits(conn, last_stable_mc_ball_unit, arrParentUnits, function(bStable){
		if (bStable){
			conn.query("SELECT ball, main_chain_index FROM units JOIN balls USING(unit) WHERE unit=?", [last_stable_mc_ball_unit], function(rows){
				if (rows.length !== 1)
					throw Error("not 1 ball by unit "+last_stable_mc_ball_unit);
				var row = rows[0];
				handleAdjustedLastStableUnit(row.ball, last_stable_mc_ball_unit, row.main_chain_index, arrParentUnits);
			});
			return;
		}
		console.log('will adjust last stable ball because '+last_stable_mc_ball_unit+' is not stable in view of parents '+arrParentUnits.join(', '));
		/*if (arrParentUnits.length > 1){ // select only one parent
			pickDeepParentUnits(conn, arrWitnesses, null, function(err, arrAdjustedParentUnits){
				if (err)
					throw Error("pickDeepParentUnits in adjust failed: "+err);
				adjustLastStableMcBallAndParents(conn, last_stable_mc_ball_unit, arrAdjustedParentUnits, arrWitnesses, handleAdjustedLastStableUnit);
			});
			return;
		}*/
		storage.readStaticUnitProps(conn, last_stable_mc_ball_unit, function(objUnitProps){
			if (!objUnitProps.best_parent_unit)
				throw Error("no best parent of "+last_stable_mc_ball_unit);
			adjustLastStableMcBallAndParents(conn, objUnitProps.best_parent_unit, arrParentUnits, arrWitnesses, handleAdjustedLastStableUnit);
		});
	});
}

function trimParentList(conn, arrParentUnits, arrWitnesses, handleTrimmedList){
	if (arrParentUnits.length <= constants.MAX_PARENTS_PER_UNIT)
		return handleTrimmedList(arrParentUnits);
	conn.query(
		"SELECT unit, (SELECT 1 FROM unit_authors WHERE unit_authors.unit=units.unit AND address IN(?) LIMIT 1) AS is_witness \n\
		FROM units WHERE unit IN("+arrParentUnits.map(db.escape).join(', ')+") ORDER BY is_witness DESC, "+db.getRandom()+" LIMIT ?",
		[arrWitnesses, constants.MAX_PARENTS_PER_UNIT],
		function(rows){
			handleTrimmedList(rows.map(function(row){ return row.unit; }).sort());
		}
	);
}

function pickParentUnitsAndLastBall(conn, arrWitnesses, onDone){
	pickParentUnits(conn, arrWitnesses, function(err, arrParentUnits){
		if (err)
			return onDone(err);
		findLastStableMcBall(conn, arrWitnesses, function(err, last_stable_mc_ball, last_stable_mc_ball_unit, last_stable_mc_ball_mci){
			if (err)
				return onDone(err);
			adjustLastStableMcBallAndParents(
				conn, last_stable_mc_ball_unit, arrParentUnits, arrWitnesses,
				function(last_stable_ball, last_stable_unit, last_stable_mci, arrAdjustedParentUnits){
					trimParentList(conn, arrAdjustedParentUnits, arrWitnesses, function(arrTrimmedParentUnits){
						storage.findWitnessListUnit(conn, arrWitnesses, last_stable_mci, function(witness_list_unit){
							var objFakeUnit = {parent_units: arrTrimmedParentUnits};
							if (witness_list_unit)
								objFakeUnit.witness_list_unit = witness_list_unit;
							storage.determineIfHasWitnessListMutationsAlongMc(conn, objFakeUnit, last_stable_unit, arrWitnesses, function(err){
								if (err)
									return onDone(err); // if first arg is not array, it is error
								onDone(null, arrTrimmedParentUnits, last_stable_ball, last_stable_unit, last_stable_mci);
							});
						});
					});
				}
			);
		});
	});
}

exports.pickParentUnitsAndLastBall = pickParentUnitsAndLastBall;
<|endoftext|>


<commit_before>/**
 * The global namespace for Comments.
 * @namespace Comments
 */
Comments = new Mongo.Collection("comments");

/**
 * Comments schema
 * @type {SimpleSchema}
 */
Comments.schema = new SimpleSchema({
  /**
    ID
  */
  _id: {
    type: String,
    optional: true
  },
  /**
    The `_id` of the parent comment, if there is one
  */
  parentCommentId: {
    type: String,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"],
    optional: true,
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The `_id` of the top-level parent comment, if there is one
  */
  topLevelCommentId: {
    type: String,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"],
    optional: true,
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The timestamp of comment creation
  */
  createdAt: {
    type: Date,
    optional: true
  },
  /**
    The timestamp of the comment being posted. For now, comments are always created and posted at the same time
  */
  postedAt: {
    type: Date,
    optional: true
  },
  /**
    The comment body (Markdown)
  */
  body: {
    type: String,
    max: 3000,
    editableBy: ["member", "admin"],
    autoform: {
      rows: 5,
      afFormGroup: {
        'formgroup-class': 'hide-label'
      }
    }
  },
  /**
    The HTML version of the comment body
  */
  htmlBody: {
    type: String,
    optional: true
  },
  /**
    The comment's base score (doesn't factor in comment age)
  */
  baseScore: {
    type: Number,
    decimal: true,
    optional: true
  },
  /**
    The comment's current score (factors in comment age)
  */
  score: {
    type: Number,
    decimal: true,
    optional: true
  },
  /**
    The number of upvotes the comment has received
  */
  upvotes: {
    type: Number,
    optional: true
  },
  /**
    An array containing the `_id`s of upvoters
  */
  upvoters: {
    type: [String],
    optional: true
  },
  /**
    The number of downvotes the comment has received
  */
  downvotes: {
    type: Number,
    optional: true
  },
  /**
    An array containing the `_id`s of downvoters
  */
  downvoters: {
    type: [String],
    optional: true
  },
  /**
    The comment author's name
  */
  author: {
    type: String,
    optional: true
  },
  /**
    Whether the comment is inactive. Inactive comments' scores gets recalculated less often
  */
  inactive: {
    type: Boolean,
    optional: true
  },
  /**
    The post's `_id`
  */
  postId: {
    type: String,
    optional: true,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"], // TODO: should users be able to set postId, but not modify it?
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The comment author's `_id`
  */
  userId: {
    type: String,
    optional: true
  },
  /**
    Whether the comment is deleted. Delete comments' content doesn't appear on the site.
  */
  isDeleted: {
    type: Boolean,
    optional: true
  }
});

Meteor.startup(function(){
  // needs to happen after every fields are added
  Events.internationalize();
});

Comments.attachSchema(Comments.schema);

Comments.allow({
  update: _.partial(Telescope.allowCheck, Comments),
  remove: _.partial(Telescope.allowCheck, Comments)
});
<commit_msg>fix typo<commit_after>/**
 * The global namespace for Comments.
 * @namespace Comments
 */
Comments = new Mongo.Collection("comments");

/**
 * Comments schema
 * @type {SimpleSchema}
 */
Comments.schema = new SimpleSchema({
  /**
    ID
  */
  _id: {
    type: String,
    optional: true
  },
  /**
    The `_id` of the parent comment, if there is one
  */
  parentCommentId: {
    type: String,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"],
    optional: true,
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The `_id` of the top-level parent comment, if there is one
  */
  topLevelCommentId: {
    type: String,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"],
    optional: true,
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The timestamp of comment creation
  */
  createdAt: {
    type: Date,
    optional: true
  },
  /**
    The timestamp of the comment being posted. For now, comments are always created and posted at the same time
  */
  postedAt: {
    type: Date,
    optional: true
  },
  /**
    The comment body (Markdown)
  */
  body: {
    type: String,
    max: 3000,
    editableBy: ["member", "admin"],
    autoform: {
      rows: 5,
      afFormGroup: {
        'formgroup-class': 'hide-label'
      }
    }
  },
  /**
    The HTML version of the comment body
  */
  htmlBody: {
    type: String,
    optional: true
  },
  /**
    The comment's base score (doesn't factor in comment age)
  */
  baseScore: {
    type: Number,
    decimal: true,
    optional: true
  },
  /**
    The comment's current score (factors in comment age)
  */
  score: {
    type: Number,
    decimal: true,
    optional: true
  },
  /**
    The number of upvotes the comment has received
  */
  upvotes: {
    type: Number,
    optional: true
  },
  /**
    An array containing the `_id`s of upvoters
  */
  upvoters: {
    type: [String],
    optional: true
  },
  /**
    The number of downvotes the comment has received
  */
  downvotes: {
    type: Number,
    optional: true
  },
  /**
    An array containing the `_id`s of downvoters
  */
  downvoters: {
    type: [String],
    optional: true
  },
  /**
    The comment author's name
  */
  author: {
    type: String,
    optional: true
  },
  /**
    Whether the comment is inactive. Inactive comments' scores gets recalculated less often
  */
  inactive: {
    type: Boolean,
    optional: true
  },
  /**
    The post's `_id`
  */
  postId: {
    type: String,
    optional: true,
    // regEx: SimpleSchema.RegEx.Id,
    max: 500,
    editableBy: ["member", "admin"], // TODO: should users be able to set postId, but not modify it?
    autoform: {
      omit: true // never show this
    }
  },
  /**
    The comment author's `_id`
  */
  userId: {
    type: String,
    optional: true
  },
  /**
    Whether the comment is deleted. Delete comments' content doesn't appear on the site.
  */
  isDeleted: {
    type: Boolean,
    optional: true
  }
});

Meteor.startup(function(){
  // needs to happen after every fields are added
  Comments.internationalize();
});

Comments.attachSchema(Comments.schema);

Comments.allow({
  update: _.partial(Telescope.allowCheck, Comments),
  remove: _.partial(Telescope.allowCheck, Comments)
});
<|endoftext|>


<commit_before>const winston = require('winston');

module.exports = ({
  filename,
  level,
  transports: transports = [{ type: 'Console', options: { level: 'debug' } }],
}) => {
  if (filename) {
    transports.push({
      type: 'File',
      options: { filename, level: 'debug' },
    });
  }
  if (level) {
    for (const transport of transports) {
      transport.options = transport.options || {};
      transport.options.level = level;
    }
  }
  return (category, options) => {
    const logger = new winston.Logger(options);
    for (const transport of transports) {
      logger.add(
        winston.transports[transport.type],
        Object.assign({}, transport.options, { label: category })
      );
    }
    return logger;
  };
};
<commit_msg>Replace default transport for logging, remove file logging<commit_after>const winston = require('winston');

module.exports = ({
  level,
  transports: transports = [
    {
      type: 'Console',
      options: {
        timestamp: true,
        colorize: process.env.NODE_ENV !== 'production',
        prettyPrint: process.env.NODE_ENV !== 'production',
        json: process.env.NODE_ENV === 'production',
        stringify: obj => JSON.stringify(obj),
        silent: process.env.NODE_ENV === 'test',
        level: 'debug',
      },
    },
  ],
}) => {
  if (level) {
    for (const transport of transports) {
      transport.options = transport.options || {};
      transport.options.level = level;
    }
  }
  return (category, options) => {
    const logger = new winston.Logger(options);
    for (const transport of transports) {
      logger.add(
        winston.transports[transport.type],
        Object.assign({}, transport.options, { label: category })
      );
    }
    return logger;
  };
};
<|endoftext|>


<commit_before>/**
 * @summary History and Location namespace
 * @namespace Thriver.history
 */
Thriver.history = {}

/**
 * @summary Registry for History, Routing, and Deep-Linking functionality
 * @type {Collection}
 */
Thriver.history.registry = new Mongo.Collection(null);

/**
 * @summary Enforce Registry Schema
 * @type {SimpleSchema}
 */
Thriver.history.schema = new SimpleSchema({
    /** Element ID to link to */
    element: {
        type: String,
        optional: false
    },
    /** For deep-link support */
    callback: {
        type: Function,
        optional: true
    },
    /** Module's current/stateful path */
    currentPath: {
        type: String,
        optional: false,
        // By default, set the currentPath to the element's ID
        autoValue: function (doc) { return doc.currentPath || doc.element }
    },
    /** Callback for special-case navigation */
    accessFunction: {
        type: Function,
        optional: true
    },
    /** Data to pass to accessFunction */
    accessData: {
        type: Object, // only way to allow anything because no 'any' type
        optional: true,
        defaultValue: {},
        blackbox: true
    }
});

// Attach Schema
Thriver.history.registry.attachSchema(Thriver.history.schema);

/**
 * @summary Update Browser's location bar
 * @method
 */
Thriver.history.updateLocation = function () {
    // Get Y coordinates for each registered page element
    // We have to regenerate this every time in case some elements
    // grow or shrink in size
    var coordinates = [],
        // Don't include special-access sections, since they're not visible on page
        elements = Thriver.history.registry.find({
            accessFunction: { $exists: false } }).fetch(),
        i, j, k, l,
        links = document.querySelectorAll('nav.mainNav a'), link;

    // For each registered section, get its Y coordinate
    for (i = 0, j = elements.length; i < j; ++i) {
        elements[i].y = document.querySelector('#' + elements[i].element).offsetTop;
        elements[i].h = document.querySelector('#' + elements[i].element).offsetHeight;
    }

    // For masthead and everything before first registered section
    elements.unshift({ element: '', y: 0, h: elements[0].y, currentPath: '' });

    // Does the current scroll position match with any element?
    for (i = 0, j = elements.length; i < j; ++i) {
        if (i !== j - 1)    // if this isn't the last element
            // If the section height minus the portion hidden off the screen is more
            // than half the height of the visible area, we're good.  Otherwise,
            // try the next section.
            if ( (elements[i].h - (window.scrollY - elements[i].y) ) < (window.innerHeight / 2) )
                    continue;

        // Update URL/location bar
        Thriver.history.updateLocationBar(elements[i].currentPath);

        // Remove active class from all main nav items
        for (k = 0, l = links.length; k < l; ++k)
            links[k].classList.remove('active');

        // Add active class to associated menu item
        link = document.querySelector('nav.mainNav a[href="/' +
            elements[i].element + '"]');

        if (link instanceof Element)
            link.classList.add('active');
            //link.blur();
            //This allows the UI to remove unwanted :focus class to last selection
            $('header.mainHeader nav.mainNav li a').blur();

        break;
    }
};

/**
 * @summary Update the URI Location Bar
 * @method
 *   @param {String} path
 */
Thriver.history.updateLocationBar = function (path) {
    check(path, String);

    window.history.pushState({ path: path }, undefined, '/' + path);
};

/**
 * @summary Update a section's path
 * @method
 *   @param {String} section - Section name to update
 *   @param {String} path    - Path to set for section
 */
Thriver.history.update = function (section, path) {
    check(section, String);
    check(path, String);

    // Update collection with new path
    Thriver.history.registry.update({ element: section }, {
        $set: { currentPath: path }
    });

    // Update URI
    Thriver.history.updateLocationBar(path);
};

/**
 * @summary Navigate to a section based on URI path
 * @method
 *   @param {String} path - Path to navigate to
 */
Thriver.history.navigate = function (path) {
    // Must have a path
    check(path, String);

    // Break into constituent parts
    path = path.split(/\//).

    // Ignore empties
    filter(function (element) { return element.length != 0; });

    // If there's no path, allow scroll to top
    if (!path.length) Thriver.history.smoothScroll('/');

    /**
     * @summary Get registered element
     * @type {Object}
     */
    var location = Thriver.history.registry.findOne({ element: path[0] });

    // Navigate to the appropriate element, if it exists
    if (location) {
        // If this has a particular function used to access this component
        if (location.accessFunction instanceof Function)
            location.accessFunction( location.accessData );
        else
            // Smooth scroll to element
            Thriver.history.smoothScroll(location.element);

        // Update element's stateful path
        Thriver.history.update(location.element, path.join('/') );

        // Now, pass the rest of the path along to the callback function
        if (location.callback instanceof Function)
            location.callback( path.slice(1) );
    }
};

/**
 * @summary Handle Page-Load Navigation
 * @function
 *   @param {String} path - Path to navigate to
 * @returns {Function}
 */
var onLoadNavigate = function (path) {
    return function () {
        Thriver.history.navigate(path);
    };
};

// Manage Updating the Browser's Location bar, as well as reacting to changes
// in the location bar, smooth scrolling to the proper location, then passing
// additional URI parameters to the appropriately registered function.
Template.canvas.onRendered(function () {
    /**
     * @summary Timeout in milliseconds to wait until scroll completion to check position
     * @type {number}
     */
    var timeout = 0,

    /**
     * @summary When the page reloads, the URI will be overridden based on the
     *   position while the page is rendering, preventing the client from actually
     *   navigating to the proper location.  Override that change.
     * @type {string}
     */
    path = document.location.pathname;

    /**
     * @summary Update Location Bar based on position on page
     * @method
     *   @param {$.Event} event - jQuery Scroll event object
     */
    window.addEventListener('scroll', function (event) {
        // Why we're using timers here:
        // The scroll event will fire for every pixel (or browser/OS-specific unit)
        // scrolled, causing major performance issues, especially when interacting
        // with the DOM.
        //
        // Instead, we use timeouts to wait for a scroll to complete before executing
        // CPU-intensive code.  For each time the event fires, the timeout is cleared,
        // essentially causing the code to wait until the event is fired for its
        // last time.

        // Clear the timeout
        clearTimeout(timeout);

        // Set a timeout to modify location bar about every quarter second
        timeout = setTimeout(Thriver.history.updateLocation, 200);
    });

    // Wait a couple seconds for the page to render before navigating to the proper link
    // TODO: Is there a better way to do this?
    setTimeout(onLoadNavigate(path), 3000);
});

/**
 * @summary Handle Forward and Backward Browser buttons
 * @method
 *   @param {Event} event - Popstate event
 */
window.addEventListener('popstate', function (event) {
    console.debug('popstate', event);
    check(event,            Event);
    check(event.state,      Object);
    check(event.state.path, Match.Maybe(String) );

    // Navigate to path
    if (event.state.path)
        Thriver.history.navigate(event.state.path);
});

/**
 * @summary Bind all anchor events that reference local resources
 *   to smooth scroll to appropriate path
 * @method
 *   @param {$.Event} event
 */
Template.body.events({
    // If href starts with a slash and is intended to remain in this tab
    'click a[href^="/"][target!="_blank"]': function (event) { console.debug('href', event.target);
        check(event, $.Event);

        // Prevent navigation away from page
        event.preventDefault();

        // Navigate to path
        Thriver.history.navigate(event.target.pathname || '/');
    }
});
<commit_msg>Fixed race condition in history. No longer depends on 3 second timer before smooth scrolling, drastically improving user experience here.<commit_after>/**
 * @summary History and Location namespace
 * @namespace Thriver.history
 */
Thriver.history = {}

/**
 * @summary Registry for History, Routing, and Deep-Linking functionality
 * @type {Collection}
 */
Thriver.history.registry = new Mongo.Collection(null);

/**
 * @summary Enforce Registry Schema
 * @type {SimpleSchema}
 */
Thriver.history.schema = new SimpleSchema({
    /** Element ID to link to */
    element: {
        type: String,
        optional: false
    },
    /** For deep-link support */
    callback: {
        type: Function,
        optional: true
    },
    /** Module's current/stateful path */
    currentPath: {
        type: String,
        optional: false,
        // By default, set the currentPath to the element's ID
        autoValue: function (doc) { return doc.currentPath || doc.element }
    },
    /** Callback for special-case navigation */
    accessFunction: {
        type: Function,
        optional: true
    },
    /** Data to pass to accessFunction */
    accessData: {
        type: Object, // only way to allow anything because no 'any' type
        optional: true,
        defaultValue: {},
        blackbox: true
    }
});

// Attach Schema
Thriver.history.registry.attachSchema(Thriver.history.schema);

/**
 * @summary Update Browser's location bar
 * @method
 */
Thriver.history.updateLocation = function () {
    // Get Y coordinates for each registered page element
    // We have to regenerate this every time in case some elements
    // grow or shrink in size
    var coordinates = [],
        // Don't include special-access sections, since they're not visible on page
        elements = Thriver.history.registry.find({
            accessFunction: { $exists: false } }).fetch(),
        i, j, k, l,
        links = document.querySelectorAll('nav.mainNav a'), link;

    // For each registered section, get its Y coordinate
    for (i = 0, j = elements.length; i < j; ++i) {
        elements[i].y = document.querySelector('#' + elements[i].element).offsetTop;
        elements[i].h = document.querySelector('#' + elements[i].element).offsetHeight;
    }

    // For masthead and everything before first registered section
    elements.unshift({ element: '', y: 0, h: elements[0].y, currentPath: '' });

    // Does the current scroll position match with any element?
    for (i = 0, j = elements.length; i < j; ++i) {
        if (i !== j - 1)    // if this isn't the last element
            // If the section height minus the portion hidden off the screen is more
            // than half the height of the visible area, we're good.  Otherwise,
            // try the next section.
            if ( (elements[i].h - (window.scrollY - elements[i].y) ) < (window.innerHeight / 2) )
                    continue;

        // Update URL/location bar
        Thriver.history.updateLocationBar(elements[i].currentPath);

        // Remove active class from all main nav items
        for (k = 0, l = links.length; k < l; ++k)
            links[k].classList.remove('active');

        // Add active class to associated menu item
        link = document.querySelector('nav.mainNav a[href="/' +
            elements[i].element + '"]');

        if (link instanceof Element)
            link.classList.add('active');
            //link.blur();
            //This allows the UI to remove unwanted :focus class to last selection
            $('header.mainHeader nav.mainNav li a').blur();

        break;
    }
};

/**
 * @summary Update the URI Location Bar
 * @method
 *   @param {String} path
 */
Thriver.history.updateLocationBar = function (path) {
    check(path, String);

    window.history.pushState({ path: path }, undefined, '/' + path);
};

/**
 * @summary Update a section's path
 * @method
 *   @param {String} section - Section name to update
 *   @param {String} path    - Path to set for section
 */
Thriver.history.update = function (section, path) {
    check(section, String);
    check(path, String);

    // Update collection with new path
    Thriver.history.registry.update({ element: section }, {
        $set: { currentPath: path }
    });

    // Update URI
    Thriver.history.updateLocationBar(path);
};

/**
 * @summary Navigate to a section based on URI path
 * @method
 *   @param {String} path - Path to navigate to
 */
Thriver.history.navigate = function (path) {
    // Must have a path
    check(path, String);

    // Break into constituent parts
    path = path.split(/\//).

    // Ignore empties
    filter(function (element) { return element.length != 0; });

    // If there's no path, allow scroll to top
    if (!path.length) Thriver.history.smoothScroll('/');

    // Navigate to the appropriate element, once it exists
    // TODO:  Could there be an infinite loop if the element doesn't exist?
    Tracker.autorun(function (c) {
        /**
         * @summary Get registered element
         * @type {Object}
         */
        var location = Thriver.history.registry.findOne({ element: path[0] });

        if (location) c.stop(); else return;

        // If this has a particular function used to access this component
        if (location.accessFunction instanceof Function)
            location.accessFunction( location.accessData );
        else
            // Smooth scroll to element
            Thriver.history.smoothScroll(location.element);

        // Update element's stateful path
        Thriver.history.update(location.element, path.join('/') );

        // Now, pass the rest of the path along to the callback function
        if (location.callback instanceof Function)
            location.callback( path.slice(1) );
    });
};

/**
 * @summary Handle Page-Load Navigation
 * @function
 *   @param {String} path - Path to navigate to
 * @returns {Function}
 */
/*var onLoadNavigate = function (path) {
    return function () {
        Thriver.history.navigate(path);
    };
};*/

// Manage Updating the Browser's Location bar, as well as reacting to changes
// in the location bar, smooth scrolling to the proper location, then passing
// additional URI parameters to the appropriately registered function.
Template.canvas.onRendered(function () {
    /**
     * @summary Timeout in milliseconds to wait until scroll completion to check position
     * @type {number}
     */
    var timeout = 0,

    /**
     * @summary When the page reloads, the URI will be overridden based on the
     *   position while the page is rendering, preventing the client from actually
     *   navigating to the proper location.  Override that change.
     * @type {string}
     */
    path = document.location.pathname;

    /**
     * @summary Update Location Bar based on position on page
     * @method
     *   @param {$.Event} event - jQuery Scroll event object
     */
    window.addEventListener('scroll', function (event) {
        // Why we're using timers here:
        // The scroll event will fire for every pixel (or browser/OS-specific unit)
        // scrolled, causing major performance issues, especially when interacting
        // with the DOM.
        //
        // Instead, we use timeouts to wait for a scroll to complete before executing
        // CPU-intensive code.  For each time the event fires, the timeout is cleared,
        // essentially causing the code to wait until the event is fired for its
        // last time.

        // Clear the timeout
        clearTimeout(timeout);

        // Set a timeout to modify location bar about every quarter second
        timeout = setTimeout(Thriver.history.updateLocation, 200);
    });

    // Wait a couple seconds for the page to render before navigating to the proper link
    // TODO: Is there a better way to do this?
    //setTimeout(onLoadNavigate(path), 3000);
    Thriver.history.navigate(path);
});

/**
 * @summary Handle Forward and Backward Browser buttons
 * @method
 *   @param {Event} event - Popstate event
 */
window.addEventListener('popstate', function (event) {
    console.debug('popstate', event);
    check(event,            Event);
    check(event.state,      Object);
    check(event.state.path, Match.Maybe(String) );

    // Navigate to path
    if (event.state.path)
        Thriver.history.navigate(event.state.path);
});

/**
 * @summary Bind all anchor events that reference local resources
 *   to smooth scroll to appropriate path
 * @method
 *   @param {$.Event} event
 */
Template.body.events({
    // If href starts with a slash and is intended to remain in this tab
    'click a[href^="/"][target!="_blank"]': function (event) { console.debug('href', event.target);
        check(event, $.Event);

        // Prevent navigation away from page
        event.preventDefault();

        // Navigate to path
        Thriver.history.navigate(event.target.pathname || '/');
    }
});
<|endoftext|>


<commit_before>// Declaring global variables
(function (window) {

  var document = window.document,
      app = {};

  // App basic config
  app.container = document.getElementById('main');
  app.defaultRoute = '/';

  // Modules storage
  app.modules = {};

  app.module = function (name, instance, route) {
    if (!instance)
      return (app.modules[name] ? app.modules[name].instance : undefined);
    else
      app.modules[name] = { route : (route ? route : '/'), instance : instance };
  };

  // Models storage
  app.models = {};

  app.model = function (name, instance) {
    if (!instance)
      return (app.models[name] ? app.models[name] : undefined);
    else
      app.models[name] = instance;
  };

  // Globalizing the ordeal
  window.app = app;

})(window);<commit_msg>Fixing previous commit<commit_after>// Declaring global variables
(function (window) {

  var document = window.document,
      app = {};

  // App basic config
  app.container = document.getElementById('main');
  app.defaultRoute = '/';

  // Modules storage
  app.modules = {};

  app.module = function (name, instance, route) {
    if (!instance)
      return (app.modules[name] ? app.modules[name].instance : undefined);
    else
      app.modules[name] = { route : (route ? route : '/'), instance : instance };
  };

  // Models storage
  app.models = {};

  app.model = function (name, instance) {
    if (!instance)
      return app.models[name];
    else
      app.models[name] = instance;
  };

  // Globalizing the ordeal
  window.app = app;

})(window);<|endoftext|>


<commit_before>const notifier = require('node-notifier')
const relative = require('path').relative

const babelClientOpts = {
  presets: [
    '@babel/preset-typescript',
    [
      '@babel/preset-env',
      {
        modules: 'commonjs',
        targets: {
          esmodules: true,
        },
        loose: true,
        exclude: ['transform-typeof-symbol'],
      },
    ],
    '@babel/preset-react',
  ],
  plugins: [
    // workaround for @taskr/esnext bug replacing `-import` with `-require(`
    // eslint-disable-next-line no-useless-concat
    '@babel/plugin-syntax-dynamic-impor' + 't',
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: 2,
        helpers: true,
        regenerator: false,
        useESModules: false,
      },
    ],
  ],
}

const babelServerOpts = {
  presets: [
    '@babel/preset-typescript',
    [
      '@babel/preset-env',
      {
        modules: 'commonjs',
        targets: {
          node: '8.3',
        },
        loose: true,
        exclude: ['transform-typeof-symbol'],
      },
    ],
  ],
  plugins: [
    '@babel/plugin-proposal-optional-chaining',
    '@babel/plugin-proposal-nullish-coalescing-operator',
    'babel-plugin-dynamic-import-node',
    ['@babel/plugin-proposal-class-properties', { loose: true }],
  ],
}

// eslint-disable-next-line camelcase
export async function ncc_arg(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('arg')))
    .ncc({ packageName: 'arg' })
    .target('dist/compiled/arg')
}

// eslint-disable-next-line camelcase
export async function ncc_resolve(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('resolve')))
    .ncc({ packageName: 'resolve' })
    .target('dist/compiled/resolve')
}

// eslint-disable-next-line camelcase
export async function ncc_nanoid(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('nanoid')))
    .ncc({ packageName: 'nanoid' })
    .target('dist/compiled/nanoid')
}

// eslint-disable-next-line camelcase
export async function ncc_unistore(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('unistore')))
    .ncc({ packageName: 'unistore' })
    .target('dist/compiled/unistore')
}

// eslint-disable-next-line camelcase
export async function ncc_text_table(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('text-table')))
    .ncc({ packageName: 'text-table' })
    .target('dist/compiled/text-table')
}

export async function precompile(task) {
  await task.parallel([
    'ncc_unistore',
    'ncc_resolve',
    'ncc_arg',
    'ncc_nanoid',
    'ncc_text_table',
  ])
}

export async function compile(task) {
  await task.parallel([
    'cli',
    'bin',
    'server',
    'nextbuild',
    'nextbuildstatic',
    'pages',
    'lib',
    'client',
    'telemetry',
    'nextserverserver',
    'nextserverlib',
  ])
}

export async function bin(task, opts) {
  await task
    .source(opts.src || 'bin/*')
    .babel(babelServerOpts, { stripExtension: true })
    .target('dist/bin', { mode: '0755' })
  notify('Compiled binaries')
}

export async function cli(task, opts) {
  await task
    .source(opts.src || 'cli/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/cli')
  notify('Compiled cli files')
}

export async function lib(task, opts) {
  await task
    .source(opts.src || 'lib/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/lib')
  notify('Compiled lib files')
}

export async function server(task, opts) {
  const babelOpts = {
    ...babelServerOpts,
    // the /server files may use React
    presets: [...babelServerOpts.presets, '@babel/preset-react'],
  }
  await task
    .source(opts.src || 'server/**/*.+(js|ts|tsx)')
    .babel(babelOpts)
    .target('dist/server')
  notify('Compiled server files')
}

export async function nextbuild(task, opts) {
  await task
    .source(opts.src || 'build/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/build')
  notify('Compiled build files')
}

export async function client(task, opts) {
  await task
    .source(opts.src || 'client/**/*.+(js|ts|tsx)')
    .babel(babelClientOpts)
    .target('dist/client')
  notify('Compiled client files')
}

// export is a reserved keyword for functions
export async function nextbuildstatic(task, opts) {
  await task
    .source(opts.src || 'export/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/export')
  notify('Compiled export files')
}

export async function pages(task, opts) {
  await task
    .source(opts.src || 'pages/**/*.+(js|ts|tsx)')
    .babel(babelClientOpts)
    .target('dist/pages')
}

export async function telemetry(task, opts) {
  await task
    .source(opts.src || 'telemetry/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/telemetry')
  notify('Compiled telemetry files')
}

export async function build(task) {
  await task.serial(['precompile', 'compile'])
}

export default async function(task) {
  await task.clear('dist')
  await task.start('build')
  await task.watch('bin/*', 'bin')
  await task.watch('pages/**/*.+(js|ts|tsx)', 'pages')
  await task.watch('server/**/*.+(js|ts|tsx)', 'server')
  await task.watch('build/**/*.+(js|ts|tsx)', 'nextbuild')
  await task.watch('export/**/*.+(js|ts|tsx)', 'nextbuildstatic')
  await task.watch('client/**/*.+(js|ts|tsx)', 'client')
  await task.watch('lib/**/*.+(js|ts|tsx)', 'lib')
  await task.watch('cli/**/*.+(js|ts|tsx)', 'cli')
  await task.watch('telemetry/**/*.+(js|ts|tsx)', 'telemetry')
  await task.watch('next-server/server/**/*.+(js|ts|tsx)', 'nextserverserver')
  await task.watch('next-server/lib/**/*.+(js|ts|tsx)', 'nextserverlib')
}

export async function nextserverlib(task, opts) {
  await task
    .source(opts.src || 'next-server/lib/**/*.+(js|ts|tsx)')
    .typescript({ module: 'commonjs' })
    .target('dist/next-server/lib')
  notify('Compiled lib files')
}

export async function nextserverserver(task, opts) {
  await task
    .source(opts.src || 'next-server/server/**/*.+(js|ts|tsx)')
    .typescript({ module: 'commonjs' })
    .target('dist/next-server/server')
  notify('Compiled server files')
}

export async function nextserverbuild(task) {
  await task.parallel(['nextserverserver', 'nextserverlib'])
}

export async function release(task) {
  await task.clear('dist').start('build')
  await task.clear('dist/next-server').start('nextserverbuild')
}

// notification helper
function notify(msg) {
  return notifier.notify({
    title: '▲ Next',
    message: msg,
    icon: false,
  })
}
<commit_msg>Don't include core-js for default _document (#10187)<commit_after>const notifier = require('node-notifier')
const relative = require('path').relative

const babelClientOpts = {
  presets: [
    '@babel/preset-typescript',
    [
      '@babel/preset-env',
      {
        modules: 'commonjs',
        targets: {
          esmodules: true,
        },
        loose: true,
        exclude: ['transform-typeof-symbol'],
      },
    ],
    '@babel/preset-react',
  ],
  plugins: [
    // workaround for @taskr/esnext bug replacing `-import` with `-require(`
    // eslint-disable-next-line no-useless-concat
    '@babel/plugin-syntax-dynamic-impor' + 't',
    ['@babel/plugin-proposal-class-properties', { loose: true }],
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: 2,
        helpers: true,
        regenerator: false,
        useESModules: false,
      },
    ],
  ],
}

const babelServerOpts = {
  presets: [
    '@babel/preset-typescript',
    [
      '@babel/preset-env',
      {
        modules: 'commonjs',
        targets: {
          node: '8.3',
        },
        loose: true,
        exclude: ['transform-typeof-symbol'],
      },
    ],
  ],
  plugins: [
    '@babel/plugin-proposal-optional-chaining',
    '@babel/plugin-proposal-nullish-coalescing-operator',
    'babel-plugin-dynamic-import-node',
    ['@babel/plugin-proposal-class-properties', { loose: true }],
  ],
}

// eslint-disable-next-line camelcase
export async function ncc_arg(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('arg')))
    .ncc({ packageName: 'arg' })
    .target('dist/compiled/arg')
}

// eslint-disable-next-line camelcase
export async function ncc_resolve(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('resolve')))
    .ncc({ packageName: 'resolve' })
    .target('dist/compiled/resolve')
}

// eslint-disable-next-line camelcase
export async function ncc_nanoid(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('nanoid')))
    .ncc({ packageName: 'nanoid' })
    .target('dist/compiled/nanoid')
}

// eslint-disable-next-line camelcase
export async function ncc_unistore(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('unistore')))
    .ncc({ packageName: 'unistore' })
    .target('dist/compiled/unistore')
}

// eslint-disable-next-line camelcase
export async function ncc_text_table(task, opts) {
  await task
    .source(opts.src || relative(__dirname, require.resolve('text-table')))
    .ncc({ packageName: 'text-table' })
    .target('dist/compiled/text-table')
}

export async function precompile(task) {
  await task.parallel([
    'ncc_unistore',
    'ncc_resolve',
    'ncc_arg',
    'ncc_nanoid',
    'ncc_text_table',
  ])
}

export async function compile(task) {
  await task.parallel([
    'cli',
    'bin',
    'server',
    'nextbuild',
    'nextbuildstatic',
    'pages',
    'lib',
    'client',
    'telemetry',
    'nextserverserver',
    'nextserverlib',
  ])
}

export async function bin(task, opts) {
  await task
    .source(opts.src || 'bin/*')
    .babel(babelServerOpts, { stripExtension: true })
    .target('dist/bin', { mode: '0755' })
  notify('Compiled binaries')
}

export async function cli(task, opts) {
  await task
    .source(opts.src || 'cli/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/cli')
  notify('Compiled cli files')
}

export async function lib(task, opts) {
  await task
    .source(opts.src || 'lib/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/lib')
  notify('Compiled lib files')
}

export async function server(task, opts) {
  const babelOpts = {
    ...babelServerOpts,
    // the /server files may use React
    presets: [...babelServerOpts.presets, '@babel/preset-react'],
  }
  await task
    .source(opts.src || 'server/**/*.+(js|ts|tsx)')
    .babel(babelOpts)
    .target('dist/server')
  notify('Compiled server files')
}

export async function nextbuild(task, opts) {
  await task
    .source(opts.src || 'build/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/build')
  notify('Compiled build files')
}

export async function client(task, opts) {
  await task
    .source(opts.src || 'client/**/*.+(js|ts|tsx)')
    .babel(babelClientOpts)
    .target('dist/client')
  notify('Compiled client files')
}

// export is a reserved keyword for functions
export async function nextbuildstatic(task, opts) {
  await task
    .source(opts.src || 'export/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/export')
  notify('Compiled export files')
}

export async function pages_app(task) {
  await task
    .source('pages/_app.tsx')
    .babel(babelClientOpts)
    .target('dist/pages')
}

export async function pages_error(task) {
  await task
    .source('pages/_error.tsx')
    .babel(babelClientOpts)
    .target('dist/pages')
}

export async function pages_document(task) {
  const babelOpts = {
    ...babelServerOpts,
    presets: [...babelServerOpts.presets, '@babel/preset-react'],
  }

  await task
    .source('pages/_document.tsx')
    .babel(babelOpts)
    .target('dist/pages')
}

export async function pages(task, opts) {
  await task.parallel(['pages_app', 'pages_error', 'pages_document'])
}

export async function telemetry(task, opts) {
  await task
    .source(opts.src || 'telemetry/**/*.+(js|ts|tsx)')
    .babel(babelServerOpts)
    .target('dist/telemetry')
  notify('Compiled telemetry files')
}

export async function build(task) {
  await task.serial(['precompile', 'compile'])
}

export default async function(task) {
  await task.clear('dist')
  await task.start('build')
  await task.watch('bin/*', 'bin')
  await task.watch('pages/**/*.+(js|ts|tsx)', 'pages')
  await task.watch('server/**/*.+(js|ts|tsx)', 'server')
  await task.watch('build/**/*.+(js|ts|tsx)', 'nextbuild')
  await task.watch('export/**/*.+(js|ts|tsx)', 'nextbuildstatic')
  await task.watch('client/**/*.+(js|ts|tsx)', 'client')
  await task.watch('lib/**/*.+(js|ts|tsx)', 'lib')
  await task.watch('cli/**/*.+(js|ts|tsx)', 'cli')
  await task.watch('telemetry/**/*.+(js|ts|tsx)', 'telemetry')
  await task.watch('next-server/server/**/*.+(js|ts|tsx)', 'nextserverserver')
  await task.watch('next-server/lib/**/*.+(js|ts|tsx)', 'nextserverlib')
}

export async function nextserverlib(task, opts) {
  await task
    .source(opts.src || 'next-server/lib/**/*.+(js|ts|tsx)')
    .typescript({ module: 'commonjs' })
    .target('dist/next-server/lib')
  notify('Compiled lib files')
}

export async function nextserverserver(task, opts) {
  await task
    .source(opts.src || 'next-server/server/**/*.+(js|ts|tsx)')
    .typescript({ module: 'commonjs' })
    .target('dist/next-server/server')
  notify('Compiled server files')
}

export async function nextserverbuild(task) {
  await task.parallel(['nextserverserver', 'nextserverlib'])
}

export async function release(task) {
  await task.clear('dist').start('build')
  await task.clear('dist/next-server').start('nextserverbuild')
}

// notification helper
function notify(msg) {
  return notifier.notify({
    title: '▲ Next',
    message: msg,
    icon: false,
  })
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Asana Links in Bitbucket
// @namespace    https://github.com/GetintheLoop/asana-highlighter/
// @version      0.2
// @description  Use Asana Task Id and render as links in Bitbucket
// @author       Lukas Siemon
// @match        https://bitbucket.org/*
// @grant        none
// @updateURL    https://github.com/GetintheLoop/asana-highlighter/blob/master/scripts/asana-in-bitbucket.js
// ==/UserScript==

(function() {
    'use strict';
    setInterval(function() {
        if (!document.hidden) {
            let desc = $(".pull-request-title h1");
            if (desc.length === 1) {
                if (desc[0].innerText.indexOf('#') !== -1) {
                    desc[0].innerHTML = desc[0].innerText.replace(
                        /#(\d{10,})/g,
                        '' +
                        '' +
                        ''
                    );
                }
            }
        }
    }, 1000);
})();
<commit_msg>Update asana-in-bitbucket.js<commit_after>// ==UserScript==
// @name         Asana Links in Bitbucket
// @namespace    https://github.com/GetintheLoop/asana-highlighter/
// @version      0.3
// @description  Use Asana Task Id and render as links in Bitbucket
// @author       Lukas Siemon
// @match        https://bitbucket.org/*
// @grant        none
// @updateURL    https://github.com/GetintheLoop/asana-highlighter/blob/master/scripts/asana-in-bitbucket.js
// ==/UserScript==

(function() {
    'use strict';
    setInterval(function() {
        if (!document.hidden) {
            let desc = $(".pull-request-title h1");
            if (desc.length === 1) {
                if (desc[0].innerText.indexOf('#') !== -1) {
                    desc[0].innerHTML = desc[0].innerText.replace(
                        /#(\d{10,})/g,
                        '' +
                        '' +
                        ''
                    );
                }
            }
        }
    }, 1000);
})();
<|endoftext|>


<commit_before>'use strict';
var EventEmitter = require('wolfy87-eventemitter');
var assign = require('lodash/object/assign');
var cloneDeep = require('lodash/lang/cloneDeep');
var Auth = require('./lib/auth');
var Form = require('./lib/form');
var FormGroup = require('./lib/form-group');
var utils = require('./lib/utils');
require('./lib/polyfills.js');

var EVENTS = {
    INVALID: 'invalid',
    LOGIN: 'login:init',
    LOGIN_ERROR: 'login:error',
    LOGIN_ACCOUNT_EXPIRED: 'login:accountExpired',
    LOGIN_ACCOUNT_WILL_EXPIRE: 'login:accountWillExpire',
    LOGIN_PASSWORD_EXPIRED: 'login:passwordExpired',
    LOGIN_PASSWORD_WILL_EXPIRE: 'login:passwordWillExpire',
    LOGIN_SUCCESS: 'login:success',
    LOGOUT: 'logout:init',
    LOGOUT_ERROR: 'logout:error',
    LOGOUT_SUCCESS: 'logout:success'
};

var day = 24 * 60 * 60 * 1000;

function CoinsLogonWidget(options) {
    EventEmitter.call(this);
    var element = this._assertElement(options.el);
    var baseUrl = this._assertString(options.baseUrl, 'baseUrl');
    var authCookieName = this._assertString(options.authCookieName, 'authCookieName');

    this.options = assign(cloneDeep(CoinsLogonWidget.DEFAULTS), options);

    /**
     * If the form has hidden labels or a horizontal layout apply the
     * `hiddenLabel` form group option. This persists the option down to the
     * `FormGroup` elements.
     *
     * @todo  Devise a better solution for passing down options
     */
    if (this.options.hiddenLabels || this.options.horizontal) {
        this.options.formGroups.forEach(function(formGroupOption) {
            formGroupOption.hiddenLabel = true;
        });
    }

    // Configure auth
    Auth.setOptions({
        authCookieName: authCookieName,
        baseUrl: baseUrl,
    });

    this.element = this._getElements(element);
    this._setState();
}

// Inherit from `EventEmitter`
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Example
CoinsLogonWidget.prototype = Object.create(EventEmitter.prototype);
CoinsLogonWidget.prototype.constructor = CoinsLogonWidget;

CoinsLogonWidget.prototype._assertElement = function(element) {
    if (!element) {
        throw new TypeError('Element required');
    } else if (!(element instanceof Node)) {
        // Make sure `element` is an actual node
        // http://stackoverflow.com/a/384380
        throw new TypeError('Expected element to be a DOM node');
    }

    return element;
};

CoinsLogonWidget.prototype._assertString = function(str, prop) {
    if (!str || typeof str !== 'string') {
        throw new TypeError([
            'expected string',
            prop ? ' (' + prop + ')' : '',
            ', received: ',
            str.toString()
        ].join(''));
    }

    return str;
};

CoinsLogonWidget.prototype._getElements = function(element) {
    var self = this;

    this.form = new Form({
        formGroups: this.options.formGroups.reverse(),
        horizontal: this.options.horizontal,
        login: function() {
            self.emit(EVENTS.LOGIN, self.form.getFormData());
        },

        logout: function() {
            self.emit(EVENTS.LOGOUT);
        }
    });

    element.innerHTML = '';
    element.className = this.options.classNames.root;
    element.appendChild(this.form.element);

    return element;
};

CoinsLogonWidget.prototype._setState = function() {
    this.on(EVENTS.LOGIN, this.login);
    this.on(EVENTS.LOGIN_ERROR, this.onError);
    this.on(EVENTS.LOGIN_ACCOUNT_EXPIRED, this.onAccountExpired);
    this.on(EVENTS.LOGIN_ACCOUNT_WILL_EXPIRE, this.onAccountWillExpire);
    this.on(EVENTS.LOGIN_PASSWORD_EXPIRED, this.onPasswordExpired);
    this.on(EVENTS.LOGIN_PASSWORD_WILL_EXPIRE, this.onPasswordWillExpire);
    this.on(EVENTS.LOGIN_SUCCESS, this.onLogin);
    this.on(EVENTS.LOGOUT, this.logout);
    this.on(EVENTS.LOGOUT_ERROR, this.onError);
    this.on(EVENTS.LOGOUT_SUCCESS, this.onLogout);
};

CoinsLogonWidget.prototype.destroy = function() {
    this.form.destroy();
    delete this.form;
    this.removeAllListeners();
    delete this.options;
    delete this.element;
};

CoinsLogonWidget.prototype.login = function(formData) {
    var self = this;
    var validations = self.form.validate();

    if (validations !== true) {
        self.emit(EVENTS.INVALID, validations);
        return;
    } else {
        self.form.clearMessage();
    }

    this.form.setLoading();

    return Auth.login(formData.username, formData.password)
        .done(function(response) {
            /**
             * Successful authentication also contains information regarding
             * a user's account status. If the user's password or account is
             * expired the widget needs to handle it here.
             */
            var accountExpiration = Date.parse(response.user.acctExpDate);
            var now = Date.now();
            var passwordExpiration = Date.parse(response.user.passwordExpDate);

            self.form.clearLoading();

            if (accountExpiration - now < day * 10) {
                self.emit(EVENTS.LOGIN_ACCOUNT_WILL_EXPIRE, response);
            } else if (passwordExpiration - now < day * 10) {
                self.emit(EVENTS.LOGIN_PASSWORD_WILL_EXPIRE, response);
            } else {
                self.emit(EVENTS.LOGIN_SUCCESS, response);
            }
        })
        .fail(function(error) {
            self.form.clearLoading();
            if (error === 'Password expired') {
                self.emit(EVENTS.LOGIN_PASSWORD_EXPIRED, error);
            } else if (error === 'Account expired') {
                self.emit(EVENTS.LOGIN_ACCOUNT_EXPIRED, error);
            } else {
                self.emit(EVENTS.LOGIN_ERROR, error);
            }
        });
};

CoinsLogonWidget.prototype.logout = function() {
    var self = this;

    this.form.setLoading();

    Auth.logout()
        .done(function(response) {
            self.form.clearLoading();
            self.emit(EVENTS.LOGOUT_SUCCESS, response);
        })
        .fail(function(error) {
            self.form.clearLoading();
            self.emit(EVENTS.LOGOUT_ERROR, error);
        });
};

CoinsLogonWidget.prototype.onAccountExpired = function(response) {
    var message = utils.callOrReturn(this.options.messages.accountExpired);
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onAccountWillExpire = function(response) {
    var accountExpiration = Date.parse(response.user.acctExpDate);
    var now = Date.now();
    var message = utils.callOrReturn(
        this.options.messages.accountWillExpire,
        [utils.formatDay((accountExpiration - now) / day)]
    );
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onPasswordExpired = function(response) {
    var message = utils.callOrReturn(this.options.messages.passwordExpired);
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onPasswordWillExpire = function(response) {
    var passwordExpiration = Date.parse(response.user.passwordExpDate);
    var now = Date.now();
    var message = utils.callOrReturn(
        this.options.messages.passwordWillExpire,
        [utils.formatDay((passwordExpiration - now) / day)]
    );
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onError = function(error) {
    var message;

    if (typeof error !== 'undefined') {
        message = error.message || error.toString();
    } else {
        message = 'Unknown error!';
    }

    this.form.setErrorMessage(message);
    console.error(error);
};

CoinsLogonWidget.prototype.onLogin = function(response) {
    var username = response.username;
    var statusText = username ?
        'Logged in as ' + username + '.' :
        'Logged in.';

    this.form.setToLogout(statusText);
};

CoinsLogonWidget.prototype.onLogout = function(response) {
    this.form.setToLogin();
};

CoinsLogonWidget.prototype.onSubmit = function(event) {
    event.preventDefault();

    var errors;

    this.formGroups.forEach(function(formGroup) {
        var isValid = formGroup.validate();

        //TODO: emit validation error events
        if (!isValid) {
            errors = true;
        }
    });

    if (!errors) {
        this.emit('submitted', event);

        //TODO: Make authentication pluggable
        this.login();
    }
};

CoinsLogonWidget.DEFAULTS = {
    classNames: {
        root: 'coins-logon-widget'
    },
    formGroups: [{
        inputName: 'username',
        labelText: 'Username:',
        placeholder: ''
    }, {
        inputName: 'password',
        labelText: 'Password:',
        placeholder: '',
        type: 'password'
    }],
    hiddenLabels: false,
    horizontal: false,
    messages: {
        accountExpired: 'Your account has expired.',
        accountWillExpire: function(offset) {
            return 'Your account will expire in ' + offset + '.';
        },

        passwordExpired: 'Your password has expired.',
        passwordWillExpire: function(offset) {
            return 'Your password will expire in ' + offset + '.';
        }
    }
};

module.exports = CoinsLogonWidget;
<commit_msg>Lowercase 'auth' module references.<commit_after>'use strict';
var EventEmitter = require('wolfy87-eventemitter');
var assign = require('lodash/object/assign');
var cloneDeep = require('lodash/lang/cloneDeep');
var auth = require('./lib/auth');
var Form = require('./lib/form');
var FormGroup = require('./lib/form-group');
var utils = require('./lib/utils');
require('./lib/polyfills.js');

var EVENTS = {
    INVALID: 'invalid',
    LOGIN: 'login:init',
    LOGIN_ERROR: 'login:error',
    LOGIN_ACCOUNT_EXPIRED: 'login:accountExpired',
    LOGIN_ACCOUNT_WILL_EXPIRE: 'login:accountWillExpire',
    LOGIN_PASSWORD_EXPIRED: 'login:passwordExpired',
    LOGIN_PASSWORD_WILL_EXPIRE: 'login:passwordWillExpire',
    LOGIN_SUCCESS: 'login:success',
    LOGOUT: 'logout:init',
    LOGOUT_ERROR: 'logout:error',
    LOGOUT_SUCCESS: 'logout:success'
};

var day = 24 * 60 * 60 * 1000;

function CoinsLogonWidget(options) {
    EventEmitter.call(this);
    var element = this._assertElement(options.el);
    var baseUrl = this._assertString(options.baseUrl, 'baseUrl');
    var authCookieName = this._assertString(options.authCookieName, 'authCookieName');

    this.options = assign(cloneDeep(CoinsLogonWidget.DEFAULTS), options);

    /**
     * If the form has hidden labels or a horizontal layout apply the
     * `hiddenLabel` form group option. This persists the option down to the
     * `FormGroup` elements.
     *
     * @todo  Devise a better solution for passing down options
     */
    if (this.options.hiddenLabels || this.options.horizontal) {
        this.options.formGroups.forEach(function(formGroupOption) {
            formGroupOption.hiddenLabel = true;
        });
    }

    // Configure auth
    auth.setOptions({
        authCookieName: authCookieName,
        baseUrl: baseUrl,
    });

    this.element = this._getElements(element);
    this._setState();
}

// Inherit from `EventEmitter`
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Example
CoinsLogonWidget.prototype = Object.create(EventEmitter.prototype);
CoinsLogonWidget.prototype.constructor = CoinsLogonWidget;

CoinsLogonWidget.prototype._assertElement = function(element) {
    if (!element) {
        throw new TypeError('Element required');
    } else if (!(element instanceof Node)) {
        // Make sure `element` is an actual node
        // http://stackoverflow.com/a/384380
        throw new TypeError('Expected element to be a DOM node');
    }

    return element;
};

CoinsLogonWidget.prototype._assertString = function(str, prop) {
    if (!str || typeof str !== 'string') {
        throw new TypeError([
            'expected string',
            prop ? ' (' + prop + ')' : '',
            ', received: ',
            str.toString()
        ].join(''));
    }

    return str;
};

CoinsLogonWidget.prototype._getElements = function(element) {
    var self = this;

    this.form = new Form({
        formGroups: this.options.formGroups.reverse(),
        horizontal: this.options.horizontal,
        login: function() {
            self.emit(EVENTS.LOGIN, self.form.getFormData());
        },

        logout: function() {
            self.emit(EVENTS.LOGOUT);
        }
    });

    element.innerHTML = '';
    element.className = this.options.classNames.root;
    element.appendChild(this.form.element);

    return element;
};

CoinsLogonWidget.prototype._setState = function() {
    this.on(EVENTS.LOGIN, this.login);
    this.on(EVENTS.LOGIN_ERROR, this.onError);
    this.on(EVENTS.LOGIN_ACCOUNT_EXPIRED, this.onAccountExpired);
    this.on(EVENTS.LOGIN_ACCOUNT_WILL_EXPIRE, this.onAccountWillExpire);
    this.on(EVENTS.LOGIN_PASSWORD_EXPIRED, this.onPasswordExpired);
    this.on(EVENTS.LOGIN_PASSWORD_WILL_EXPIRE, this.onPasswordWillExpire);
    this.on(EVENTS.LOGIN_SUCCESS, this.onLogin);
    this.on(EVENTS.LOGOUT, this.logout);
    this.on(EVENTS.LOGOUT_ERROR, this.onError);
    this.on(EVENTS.LOGOUT_SUCCESS, this.onLogout);
};

CoinsLogonWidget.prototype.destroy = function() {
    this.form.destroy();
    delete this.form;
    this.removeAllListeners();
    delete this.options;
    delete this.element;
};

CoinsLogonWidget.prototype.login = function(formData) {
    var self = this;
    var validations = self.form.validate();

    if (validations !== true) {
        self.emit(EVENTS.INVALID, validations);
        return;
    } else {
        self.form.clearMessage();
    }

    this.form.setLoading();

    return auth.login(formData.username, formData.password)
        .done(function(response) {
            /**
             * Successful authentication also contains information regarding
             * a user's account status. If the user's password or account is
             * expired the widget needs to handle it here.
             */
            var accountExpiration = Date.parse(response.user.acctExpDate);
            var now = Date.now();
            var passwordExpiration = Date.parse(response.user.passwordExpDate);

            self.form.clearLoading();

            if (accountExpiration - now < day * 10) {
                self.emit(EVENTS.LOGIN_ACCOUNT_WILL_EXPIRE, response);
            } else if (passwordExpiration - now < day * 10) {
                self.emit(EVENTS.LOGIN_PASSWORD_WILL_EXPIRE, response);
            } else {
                self.emit(EVENTS.LOGIN_SUCCESS, response);
            }
        })
        .fail(function(error) {
            self.form.clearLoading();
            if (error === 'Password expired') {
                self.emit(EVENTS.LOGIN_PASSWORD_EXPIRED, error);
            } else if (error === 'Account expired') {
                self.emit(EVENTS.LOGIN_ACCOUNT_EXPIRED, error);
            } else {
                self.emit(EVENTS.LOGIN_ERROR, error);
            }
        });
};

CoinsLogonWidget.prototype.logout = function() {
    var self = this;

    this.form.setLoading();

    auth.logout()
        .done(function(response) {
            self.form.clearLoading();
            self.emit(EVENTS.LOGOUT_SUCCESS, response);
        })
        .fail(function(error) {
            self.form.clearLoading();
            self.emit(EVENTS.LOGOUT_ERROR, error);
        });
};

CoinsLogonWidget.prototype.onAccountExpired = function(response) {
    var message = utils.callOrReturn(this.options.messages.accountExpired);
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onAccountWillExpire = function(response) {
    var accountExpiration = Date.parse(response.user.acctExpDate);
    var now = Date.now();
    var message = utils.callOrReturn(
        this.options.messages.accountWillExpire,
        [utils.formatDay((accountExpiration - now) / day)]
    );
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onPasswordExpired = function(response) {
    var message = utils.callOrReturn(this.options.messages.passwordExpired);
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onPasswordWillExpire = function(response) {
    var passwordExpiration = Date.parse(response.user.passwordExpDate);
    var now = Date.now();
    var message = utils.callOrReturn(
        this.options.messages.passwordWillExpire,
        [utils.formatDay((passwordExpiration - now) / day)]
    );
    this.form.setErrorMessage(message);
};

CoinsLogonWidget.prototype.onError = function(error) {
    var message;

    if (typeof error !== 'undefined') {
        message = error.message || error.toString();
    } else {
        message = 'Unknown error!';
    }

    this.form.setErrorMessage(message);
    console.error(error);
};

CoinsLogonWidget.prototype.onLogin = function(response) {
    var username = response.username;
    var statusText = username ?
        'Logged in as ' + username + '.' :
        'Logged in.';

    this.form.setToLogout(statusText);
};

CoinsLogonWidget.prototype.onLogout = function(response) {
    this.form.setToLogin();
};

CoinsLogonWidget.prototype.onSubmit = function(event) {
    event.preventDefault();

    var errors;

    this.formGroups.forEach(function(formGroup) {
        var isValid = formGroup.validate();

        //TODO: emit validation error events
        if (!isValid) {
            errors = true;
        }
    });

    if (!errors) {
        this.emit('submitted', event);

        //TODO: Make authentication pluggable
        this.login();
    }
};

CoinsLogonWidget.DEFAULTS = {
    classNames: {
        root: 'coins-logon-widget'
    },
    formGroups: [{
        inputName: 'username',
        labelText: 'Username:',
        placeholder: ''
    }, {
        inputName: 'password',
        labelText: 'Password:',
        placeholder: '',
        type: 'password'
    }],
    hiddenLabels: false,
    horizontal: false,
    messages: {
        accountExpired: 'Your account has expired.',
        accountWillExpire: function(offset) {
            return 'Your account will expire in ' + offset + '.';
        },

        passwordExpired: 'Your password has expired.',
        passwordWillExpire: function(offset) {
            return 'Your password will expire in ' + offset + '.';
        }
    }
};

module.exports = CoinsLogonWidget;
<|endoftext|>