<commit_before>var express = require('express'),
  jsdom = require('jsdom'),
  http  = require('http'),
  app   = express();

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

exports.listen = function (listenSpec, folder) {
  if (process.env.NODE_ENV == 'development') {
    app.use(express.static(folder));
    app.use(function (req, res, next) {
      if (typeof req.query.prerender !== 'undefined') {
        prerender(req, res);
      } else {
        next();
      }
    });
  } else {
    app.use(prerender);
  }

  app.use(function (req, res) {
    res.sendfile('index.html', {root: folder});
  });

  return app.listen(listenSpec);

  function prerender (req, res, next) {
    jsdom.env({
      file: folder + '/index.html',
      url: 'http'+(process.env.NODE_ENV=="development"?'':'s')+'://' + req.host + (process.env.NODE_ENV=="development"?":"+listenSpec:"") + req.url,
      src: ['window.callPhantom = function () { window.phantomCalled = true; }'],

      features: {
        FetchExternalResources: ["script"],
        ProcessExternalResources: true
      },
      done: function (errors, window) {
        if(window.phantomCalled) {
          respond();
        } else {
          window.callPhantom = function () {
            respond();
          }
        }

        function respond () {
          res.writeHead(200, {'Content-Type': 'text/html'});
          res.write(window.document.documentElement.innerHTML);
          res.end();
        }
      }
    });
  }
};

if (!module.parent || module.parent.exports.PhusionPassenger) {
  exports.listen(3000, __dirname + '/../public');
}
<commit_msg>properly disable experiments on the backend<commit_after>var express = require('express'),
  jsdom = require('jsdom'),
  http  = require('http'),
  app   = express(),
  fs    = require('fs');
  pShim = '<script id="phantom-shim">window.callPhantom = function () { window.phantomCalled = true; }</script><script';

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

exports.listen = function (listenSpec, folder) {
  var html = fs.readFileSync(folder + '/index.html', 'utf-8').replace('<script', pShim);

  if (process.env.NODE_ENV == 'development') {
    app.use(express.static(folder));
    app.use(function (req, res, next) {
      if (typeof req.query.prerender !== 'undefined') {
        prerender(req, res);
      } else {
        next();
      }
    });
  } else {
    app.use(prerender);
  }

  app.use(function (req, res) {
    res.sendfile('index.html', {root: folder});
  });

  return app.listen(listenSpec);

  function prerender (req, res, next) {
    jsdom.env({
      html: html,
      url: 'http'+(process.env.NODE_ENV=="development"?'':'s')+'://' + req.host + (process.env.NODE_ENV=="development"?":"+listenSpec:"") + req.url,
      features: {
        FetchExternalResources: ["script"],
        ProcessExternalResources: ["script"]
      },
      done: function (errors, window) {
        var ps = window.document.getElementById('phantom-shim');
        ps.parentNode.removeChild(ps);
        ps = window.document.getElementById('sm2-container');
        if (ps) {
          ps.parentNode.removeChild(ps);
        }

        if(window.phantomCalled) {
          respond();
        } else {
          window.callPhantom = function () {
            respond();
          }
        }

        function respond () {
          res.writeHead(200, {'Content-Type': 'text/html'});
          res.write(window.document.documentElement.outerHTML);
          res.end();
          window.close();
        }
      }
    });
  }
};

if (!module.parent || module.parent.exports.PhusionPassenger) {
  exports.listen(3000, __dirname + '/../public');
}
<|endoftext|>


<commit_before>/***
|''Name''|TiddlySpaceRevisionView|
|''Description''|Show tiddler revisions in a stack of cards view|
|''Author''|BenGillies|
|''Version''|0.1.2|
|''Status''|beta|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor|
!Usage
The viewRevisions macro can be attached to any element, which should be passed
in as a parameter.

For example:

<<viewRevisions page:10 link:"<<view modified date>>">>

would show the revisions "stack of cards" view, 10 at a time, when the modified
date is clicked.
!Code
***/
//{{{
(function($) {

var me = config.macros.viewRevisions = {
	revisionTemplate: "RevisionTemplate",
	revSuffix: " [rev. #%0]", //text to append to each tiddler title
	defaultPageSize: 5, //default number of revisions to show
	defaultLinkText: "View Revisions", //when there's nothing else to use
	offsetTop: 30, //in px
	offsetLeft: 10, //in px
	shiftDownDelay: 50, //in ms
	visibleSlideAmount: 20, //amount of revisions to show on left hand edge after sliding
	zIndex: 100, //default z-index
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		params = paramString.parseParams(null, null, true)[0];
		var tiddlerElem = story.findContainingTiddler(place);

		var revButton;
		var pageSize = parseInt(params.page[0], 10) || me.defaultPageSize;
		var linkObj = params.link ? params.link[0] || me.defaultLinkText : false;
		if(linkObj) {
			 revButton = $('')
				.appendTo(place);
			wikify(linkObj, revButton[0], null, tiddler);
		} else {
			revButton = place;
		}

		$(revButton).click(function() {
			if (!$(tiddlerElem).hasClass("revisions")) {
				me.showRevisions(tiddlerElem, tiddler, pageSize);
			} else {
				me.closeRevisions(tiddlerElem);
			}
		});
	},

	// initialisation for revision view
	showRevisions: function(tiddlerElem, tiddler, pageSize) {
		var context = {
			host: tiddler.fields["server.host"],
			workspace: tiddler.fields["server.workspace"]
		};
		$(tiddlerElem).addClass('revisions');
		$(tiddlerElem).attr("revName", tiddler.title);
		$("a", ".toolbar", tiddlerElem).each(function(index, btn) {
			var _onclick = btn.onclick;
			$(btn).click(function() {
				me.closeRevisions(tiddlerElem);
				_onclick.apply(this, arguments);
			});
		});
		var type = tiddler.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var userParams = {
			tiddlerElem: tiddlerElem,
			pageSize: pageSize,
			title: tiddler.title
		};
		me.createCloak(tiddlerElem);
		adaptor.getTiddlerRevisionList(tiddler.title, null, context, userParams,
				function (context, userParams) {
					//strip the current revision
					context.revisions.shift();
					me.expandStack(context, userParams);
				});
	},

	// fetch the actual revision and put it in the tiddler div
	showRevision: function(place, revision, callback) {
		var context = {
			host: revision.fields["server.host"],
			workspace: revision.fields["server.workspace"]
		};
		var userParams = {
			revElem: place
		};
		var type = revision.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var revNo = revision.fields["server.page.revision"];
		adaptor.getTiddlerRevision(revision.title, revNo, context, userParams,
			function(context, userParams) {
				var tiddler = context.tiddler;
				tiddler.title += me.revSuffix
					.format([$(place).attr("revision")]);
				tiddler.fields.doNotSave = true;
				if (store.getTiddler(tiddler.title)) {
					store.deleteTiddler(tiddler.title);
				}
				store.addTiddler(tiddler);

				//now, populate the existing div
				var revElem = userParams.revElem;
				$(revElem).attr('id', story.tiddlerId(tiddler.title));
				$(revElem).attr("refresh", "tiddler");
				story.refreshTiddler(tiddler.title, me.revisionTemplate, true);
				callback(tiddler);
			});
	},

	createCloak: function(promoteElem) {
		//store for later
		$(promoteElem).attr("zindex", $(promoteElem).css("z-index"));
		$(promoteElem).attr("top", $(promoteElem).css("top"));
		$(promoteElem).attr("left", $(promoteElem).css("left"));

		$('
').css("z-index", me.zIndex)
			.click(function() {
				me.closeRevisions(promoteElem);
			})
			.appendTo(document.body);

		$(promoteElem).css("z-index", me.zIndex + 1);
	},

	// clean up stuff. Remove all evidence of revision view.
	closeRevisions: function(promoteElem) {
		//revert the original tiddler back to its previous state
		$(promoteElem)
			.css("z-index", $(promoteElem).attr("zindex"))
			.css("top", $(promoteElem).attr("top"))
			.css("left", $(promoteElem).attr("left"))
			.removeAttr("zindex")
			.removeAttr("top")
			.removeAttr("left")
			.removeAttr("revName")
			.removeClass("revisions");

		//remove any revisions still in the store
		var revisions = $(".revisions");
		$(".revisions").each(function(index, revision) {
			var revAttributes = revision.attributes;
			if ((revAttributes["revname"]) &&
					(revAttributes["revision"])) {
				var revName = revAttributes["revname"].value;
				var revNo = revAttributes["revision"].value;
				var title = revName + me.revSuffix.format([revNo]);

				if (store.getTiddler(title)) {
					store.deleteTiddler(title);
				}
			}
		});

		//delete the previous revisions
		revisions.remove();

		//remove the cloak
		$(".revisionCloak").remove();
	},

	// calback from getting list of revisions.
	expandStack: function(context, userParams) {
		var pageSize = userParams.pageSize;

		var from = userParams.from || 0;
		var tiddlerElem = userParams.tiddlerElem;

		userParams.defaultHeight = $(tiddlerElem).height();
		userParams.defaultWidth = $(tiddlerElem).width();
		if (from < context.revisions.length) {
			me.displayNextRevision(tiddlerElem, userParams, context, from,
				from + pageSize - 1);
		}
	},

	//place the next div above and behind the previous one.
	displayNextRevision: function(tiddlerElem, userParams, context, from, to) {
		var revision = context.revisions[from];
		function callback() {
			var revText = revBtn.getRevisionText(tiddlerElem, revision);
			tiddlerElem = me.createRevisionObject(tiddlerElem, context,
				userParams, revText);
			$(tiddlerElem)
				.attr("revision", (context.revisions.length - from));
			if ((from < to) && ((from + 1) < context.revisions.length)){
				me.displayNextRevision(tiddlerElem, userParams, context,
					from + 1, to);
			} else if ((context.revisions.length - 1) > to) {
				me.showMoreButton(tiddlerElem, context, userParams, to + 1);
			}
		}
		me.shiftVisibleDown(userParams.title, callback);
	},

	createRevisionObject: function(tiddlerElem, context, userParams, text) {
		var newPosition = me.calculatePosition(tiddlerElem, context);
		return $('
')
			.css({
				position: "absolute",
				top: newPosition.top,
				left: newPosition.left,
				"z-index": me.zIndex + 1,
				height: userParams.defaultHeight,
				width: userParams.defaultWidth
			})
			.attr("revName", userParams.title)
			.append(text)
			.insertBefore(tiddlerElem);
	},

	// move the already present revisions down by 1 to fit the next one in
	shiftVisibleDown: function(title, callback) {
		var revisions = $("[revName=%0].revisions".format([title]));
		var revisionCount = revisions.length;

		$(revisions).animate({top: "+=" + me.offsetTop},
				me.shiftDownDelay, function() {
					revisionCount -= 1;
					if ((callback) && (!revisionCount)) {
						callback();
					}
				});
	},

	// where we put the new revision
	calculatePosition: function(elem, context) {
		var offset = $(elem).offset();
		var currentPosition = $(elem).position();
		var newPosition = {
			top: currentPosition.top - me.offsetTop
		};
		if ((context.restrictLeft) ||
				((offset.left - me.offsetLeft) <
				$("#contentWrapper").offset().left)) {
			newPosition.left = 0;
			context.restrictLeft = true;
		} else {
			newPosition.left = currentPosition.left - me.offsetLeft;
		}
		return newPosition;
	},

	// equivalent of displayNextRevision, but for the more button
	showMoreButton: function(tiddlerElem, context, userParams, moreIndex) {
		userParams.from = moreIndex + 1;
		me.shiftVisibleDown(userParams.title, function() {
			var btn = me.createRevisionObject(tiddlerElem, context, userParams,
				"");

			var more = createTiddlyButton(btn[0], "more...", "show more revisions",
				function() {
					if ($(".viewRevision").length) {
						return;
					}
					userParams.tiddlerElem = btn[0];
					$(btn).text("")
						.append(revBtn
							.getRevisionText(btn[0], context.revisions[moreIndex]))
						.attr("revision", context.revisions.length - moreIndex);
					me.expandStack(context, userParams);
				});
			$(more).css("float", "right");
		});
	},

	stripRevFromTitle: function(revisionTitle) {
		return revisionTitle.split(/ ?\[rev\. #[0-9]+\]$/)[0];
	},

	onClickRevision: function(revElem, revision, callback) {
		// don't do anything if we are still loading
		if ($(".revisions").hasClass("loading")) {
			return null;
		}

		var origTitle = me.stripRevFromTitle(revision.title);
		if ($(revElem).hasClass("viewRevision")) {
			$(".revisions").addClass("loading");
			me.slideIn(revElem, revision, origTitle, function() {
				store.deleteTiddler(revision.title);
				revision.title = origTitle;
				$(revElem).text("").append(revBtn.getRevisionText(revElem,
						revision))
					.removeAttr("tags").removeAttr("tiddler")
					.removeAttr("refresh").removeAttr("template")
					.removeAttr("id");
				$(".revisions").removeClass("loading");
				if (callback) {
					callback();
				}
			});
			$(revElem).removeAttr("prevPos").removeClass("viewRevision");
		} else {
			var viewRevision = function() {
				var prevPos = $(revElem).offset().left;
				$(revElem).addClass("viewRevision").attr("prevPos", prevPos);
				$(".revisions").addClass("loading");
				me.showRevision(revElem, revision, function(rev) {
					me.slideOut(revElem, rev, origTitle, function() {
						$(".revisions").removeClass("loading");
					});
				});
			};
			//make sure another revision isn't already out
			if ($(".viewRevision").length) {
				var newRevElem = $(".viewRevision")[0];
				var newRevision = store.getTiddler($(newRevElem)
					.attr("tiddler"));
				me.onClickRevision(newRevElem, newRevision, viewRevision);
			} else {
				viewRevision();
			}
		}
	},

	slideOut: function(revElem, revision, title, callback) {
		var leftMostPos = $("[revName=%0].revisions".format([title]))
			.offset().left;
		var width = $(revElem).width();
		var originalLeftPos = $(story.getTiddler(title))
			.offset().left;

		var slideAmount = leftMostPos + width - me.visibleSlideAmount;
		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "-=" + slideAmount}, 1000);
		$(revElem)
			.attr("baseHeight", $(revElem).css("height"))
			.css("height", "auto")
			.animate({left: originalLeftPos}, 1000, callback);
	},

	slideIn: function(revElem, revision, title, callback) {
		var slideAmount = $(revElem).offset().left -
			$(story.getTiddler(title)).offset().left;
		var origRevPos = $(revElem).attr("prevPos");

		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "+=" + slideAmount}, 1000);
		$(revElem).animate({left: origRevPos}, 1000, function() {
			$(revElem)
				.css("height", $(revElem).attr("baseHeight"))
				.removeAttr("baseHeight");
			callback();
		});
	}
};

var revBtn;
config.macros.slideRevision = revBtn = {
	btnText: "created by %0 at %1 on %2",
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var btn = revBtn.getRevisionText(place, tiddler);
		$(place).append(btn);
	},

	getRevisionText: function(place, revision) {
		var text = revBtn.btnText.format([revision.modifier,
			revision.modified.formatString("0hh:0mm"),
			revision.modified.formatString("0DD MMM YYYY")]);
		var btn = $('')
			.text(text)
			.click(function() {
				var revElem = story.findContainingTiddler(this);
				me.onClickRevision(revElem, revision);
			});
		return btn;
	}
};

})(jQuery);
//}}}
<commit_msg>change method of restricting to handle position: relative<commit_after>/***
|''Name''|TiddlySpaceRevisionView|
|''Description''|Show tiddler revisions in a stack of cards view|
|''Author''|BenGillies|
|''Version''|0.1.2|
|''Status''|beta|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor|
!Usage
The viewRevisions macro can be attached to any element, which should be passed
in as a parameter.

For example:

<<viewRevisions page:10 link:"<<view modified date>>">>

would show the revisions "stack of cards" view, 10 at a time, when the modified
date is clicked.
!Code
***/
//{{{
(function($) {

var me = config.macros.viewRevisions = {
	revisionTemplate: "RevisionTemplate",
	revSuffix: " [rev. #%0]", //text to append to each tiddler title
	defaultPageSize: 5, //default number of revisions to show
	defaultLinkText: "View Revisions", //when there's nothing else to use
	offsetTop: 30, //in px
	offsetLeft: 10, //in px
	shiftDownDelay: 50, //in ms
	visibleSlideAmount: 20, //amount of revisions to show on left hand edge after sliding
	zIndex: 100, //default z-index
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		params = paramString.parseParams(null, null, true)[0];
		var tiddlerElem = story.findContainingTiddler(place);

		var revButton;
		var pageSize = parseInt(params.page[0], 10) || me.defaultPageSize;
		var linkObj = params.link ? params.link[0] || me.defaultLinkText : false;
		if(linkObj) {
			 revButton = $('')
				.appendTo(place);
			wikify(linkObj, revButton[0], null, tiddler);
		} else {
			revButton = place;
		}

		$(revButton).click(function() {
			if (!$(tiddlerElem).hasClass("revisions")) {
				me.showRevisions(tiddlerElem, tiddler, pageSize);
			} else {
				me.closeRevisions(tiddlerElem);
			}
		});
	},

	// initialisation for revision view
	showRevisions: function(tiddlerElem, tiddler, pageSize) {
		var context = {
			host: tiddler.fields["server.host"],
			workspace: tiddler.fields["server.workspace"]
		};
		$(tiddlerElem).addClass('revisions');
		$(tiddlerElem).attr("revName", tiddler.title);
		$("a", ".toolbar", tiddlerElem).each(function(index, btn) {
			var _onclick = btn.onclick;
			$(btn).click(function() {
				me.closeRevisions(tiddlerElem);
				_onclick.apply(this, arguments);
			});
		});
		var type = tiddler.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var userParams = {
			tiddlerElem: tiddlerElem,
			pageSize: pageSize,
			title: tiddler.title
		};
		me.createCloak(tiddlerElem);
		adaptor.getTiddlerRevisionList(tiddler.title, null, context, userParams,
				function (context, userParams) {
					//strip the current revision
					context.revisions.shift();
					me.expandStack(context, userParams);
				});
	},

	// fetch the actual revision and put it in the tiddler div
	showRevision: function(place, revision, callback) {
		var context = {
			host: revision.fields["server.host"],
			workspace: revision.fields["server.workspace"]
		};
		var userParams = {
			revElem: place
		};
		var type = revision.fields["server.type"];
		var adaptor = new config.adaptors[type]();
		var revNo = revision.fields["server.page.revision"];
		adaptor.getTiddlerRevision(revision.title, revNo, context, userParams,
			function(context, userParams) {
				var tiddler = context.tiddler;
				tiddler.title += me.revSuffix
					.format([$(place).attr("revision")]);
				tiddler.fields.doNotSave = true;
				if (store.getTiddler(tiddler.title)) {
					store.deleteTiddler(tiddler.title);
				}
				store.addTiddler(tiddler);

				//now, populate the existing div
				var revElem = userParams.revElem;
				$(revElem).attr('id', story.tiddlerId(tiddler.title));
				$(revElem).attr("refresh", "tiddler");
				story.refreshTiddler(tiddler.title, me.revisionTemplate, true);
				callback(tiddler);
			});
	},

	createCloak: function(promoteElem) {
		//store for later
		$(promoteElem).attr("zindex", $(promoteElem).css("z-index"));
		$(promoteElem).attr("top", $(promoteElem).css("top"));
		$(promoteElem).attr("left", $(promoteElem).css("left"));

		$('
').css("z-index", me.zIndex)
			.click(function() {
				me.closeRevisions(promoteElem);
			})
			.appendTo(document.body);

		$(promoteElem).css("z-index", me.zIndex + 1);
	},

	// clean up stuff. Remove all evidence of revision view.
	closeRevisions: function(promoteElem) {
		//revert the original tiddler back to its previous state
		$(promoteElem)
			.css("z-index", $(promoteElem).attr("zindex"))
			.css("top", $(promoteElem).attr("top"))
			.css("left", $(promoteElem).attr("left"))
			.removeAttr("zindex")
			.removeAttr("top")
			.removeAttr("left")
			.removeAttr("revName")
			.removeClass("revisions");

		//remove any revisions still in the store
		var revisions = $(".revisions");
		$(".revisions").each(function(index, revision) {
			var revAttributes = revision.attributes;
			if ((revAttributes["revname"]) &&
					(revAttributes["revision"])) {
				var revName = revAttributes["revname"].value;
				var revNo = revAttributes["revision"].value;
				var title = revName + me.revSuffix.format([revNo]);

				if (store.getTiddler(title)) {
					store.deleteTiddler(title);
				}
			}
		});

		//delete the previous revisions
		revisions.remove();

		//remove the cloak
		$(".revisionCloak").remove();
	},

	// calback from getting list of revisions.
	expandStack: function(context, userParams) {
		var pageSize = userParams.pageSize;

		var from = userParams.from || 0;
		var tiddlerElem = userParams.tiddlerElem;

		userParams.defaultHeight = $(tiddlerElem).height();
		userParams.defaultWidth = $(tiddlerElem).width();
		if (from < context.revisions.length) {
			me.displayNextRevision(tiddlerElem, userParams, context, from,
				from + pageSize - 1);
		}
	},

	//place the next div above and behind the previous one.
	displayNextRevision: function(tiddlerElem, userParams, context, from, to) {
		var revision = context.revisions[from];
		function callback() {
			var revText = revBtn.getRevisionText(tiddlerElem, revision);
			tiddlerElem = me.createRevisionObject(tiddlerElem, context,
				userParams, revText);
			$(tiddlerElem)
				.attr("revision", (context.revisions.length - from));
			if ((from < to) && ((from + 1) < context.revisions.length)){
				me.displayNextRevision(tiddlerElem, userParams, context,
					from + 1, to);
			} else if ((context.revisions.length - 1) > to) {
				me.showMoreButton(tiddlerElem, context, userParams, to + 1);
			}
		}
		me.shiftVisibleDown(userParams.title, callback);
	},

	createRevisionObject: function(tiddlerElem, context, userParams, text) {
		var newPosition = me.calculatePosition(tiddlerElem, context);
		return $('
')
			.css({
				position: "absolute",
				top: newPosition.top,
				left: newPosition.left,
				"z-index": me.zIndex + 1,
				height: userParams.defaultHeight,
				width: userParams.defaultWidth
			})
			.attr("revName", userParams.title)
			.append(text)
			.insertBefore(tiddlerElem);
	},

	// move the already present revisions down by 1 to fit the next one in
	shiftVisibleDown: function(title, callback) {
		var revisions = $("[revName=%0].revisions".format([title]));
		var revisionCount = revisions.length;

		$(revisions).animate({top: "+=" + me.offsetTop},
				me.shiftDownDelay, function() {
					revisionCount -= 1;
					if ((callback) && (!revisionCount)) {
						callback();
					}
				});
	},

	// where we put the new revision
	calculatePosition: function(elem, context) {
		var offset = $(elem).offset();
		var currentPosition = $(elem).position();
		var newPosition = {
			top: currentPosition.top - me.offsetTop
		};
		if ((context.restrictLeft) ||
				((offset.left - me.offsetLeft) <
				$("#contentWrapper").offset().left)) {
			newPosition.left = $(elem).position().left;
			context.restrictLeft = true;
		} else {
			newPosition.left = currentPosition.left - me.offsetLeft;
		}
		return newPosition;
	},

	// equivalent of displayNextRevision, but for the more button
	showMoreButton: function(tiddlerElem, context, userParams, moreIndex) {
		userParams.from = moreIndex + 1;
		me.shiftVisibleDown(userParams.title, function() {
			var btn = me.createRevisionObject(tiddlerElem, context, userParams,
				"");

			var more = createTiddlyButton(btn[0], "more...", "show more revisions",
				function() {
					if ($(".viewRevision").length) {
						return;
					}
					userParams.tiddlerElem = btn[0];
					$(btn).text("")
						.append(revBtn
							.getRevisionText(btn[0], context.revisions[moreIndex]))
						.attr("revision", context.revisions.length - moreIndex);
					me.expandStack(context, userParams);
				});
			$(more).css("float", "right");
		});
	},

	stripRevFromTitle: function(revisionTitle) {
		return revisionTitle.split(/ ?\[rev\. #[0-9]+\]$/)[0];
	},

	onClickRevision: function(revElem, revision, callback) {
		// don't do anything if we are still loading
		if ($(".revisions").hasClass("loading")) {
			return null;
		}

		var origTitle = me.stripRevFromTitle(revision.title);
		if ($(revElem).hasClass("viewRevision")) {
			$(".revisions").addClass("loading");
			me.slideIn(revElem, revision, origTitle, function() {
				store.deleteTiddler(revision.title);
				revision.title = origTitle;
				$(revElem).text("").append(revBtn.getRevisionText(revElem,
						revision))
					.removeAttr("tags").removeAttr("tiddler")
					.removeAttr("refresh").removeAttr("template")
					.removeAttr("id");
				$(".revisions").removeClass("loading");
				if (callback) {
					callback();
				}
			});
			$(revElem).removeAttr("prevPos").removeClass("viewRevision");
		} else {
			var viewRevision = function() {
				var prevPos = $(revElem).offset().left;
				$(revElem).addClass("viewRevision").attr("prevPos", prevPos);
				$(".revisions").addClass("loading");
				me.showRevision(revElem, revision, function(rev) {
					me.slideOut(revElem, rev, origTitle, function() {
						$(".revisions").removeClass("loading");
					});
				});
			};
			//make sure another revision isn't already out
			if ($(".viewRevision").length) {
				var newRevElem = $(".viewRevision")[0];
				var newRevision = store.getTiddler($(newRevElem)
					.attr("tiddler"));
				me.onClickRevision(newRevElem, newRevision, viewRevision);
			} else {
				viewRevision();
			}
		}
	},

	slideOut: function(revElem, revision, title, callback) {
		var leftMostPos = $("[revName=%0].revisions".format([title]))
			.offset().left;
		var width = $(revElem).width();
		var originalLeftPos = $(story.getTiddler(title))
			.offset().left;

		var slideAmount = leftMostPos + width - me.visibleSlideAmount;
		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "-=" + slideAmount}, 1000);
		$(revElem)
			.attr("baseHeight", $(revElem).css("height"))
			.css("height", "auto")
			.animate({left: originalLeftPos}, 1000, callback);
	},

	slideIn: function(revElem, revision, title, callback) {
		var slideAmount = $(revElem).offset().left -
			$(story.getTiddler(title)).offset().left;
		var origRevPos = $(revElem).attr("prevPos");

		$("[revName=%0].revisions:not(.viewRevision)".format([title]))
			.animate({left: "+=" + slideAmount}, 1000);
		$(revElem).animate({left: origRevPos}, 1000, function() {
			$(revElem)
				.css("height", $(revElem).attr("baseHeight"))
				.removeAttr("baseHeight");
			callback();
		});
	}
};

var revBtn;
config.macros.slideRevision = revBtn = {
	btnText: "created by %0 at %1 on %2",
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var btn = revBtn.getRevisionText(place, tiddler);
		$(place).append(btn);
	},

	getRevisionText: function(place, revision) {
		var text = revBtn.btnText.format([revision.modifier,
			revision.modified.formatString("0hh:0mm"),
			revision.modified.formatString("0DD MMM YYYY")]);
		var btn = $('')
			.text(text)
			.click(function() {
				var revElem = story.findContainingTiddler(this);
				me.onClickRevision(revElem, revision);
			});
		return btn;
	}
};

})(jQuery);
//}}}
<|endoftext|>


<commit_before>var _      = require('underscore')
, assert   = require('assert')
, Customer = require('okdoki/models/Customer').Customer
, Post     = require('okdoki/models/Post').Post
, Follow   = require('okdoki/models/Follow').Follow
, River    = require('da_river').River
, Topogo   = require('topogo').Topogo
;

describe( 'Post', function () {

  var customer, customer_id, screen_name_id, pwp;
  var follows = Topogo.new('follows');
  var posts   = Topogo.new('posts');

  before(function (done) {

    // Reset sample data.
    pwp             = 'This is my pass_phrase';
    var screen_name = 'mem1';
    var vals = ({screen_name: screen_name, pass_phrase: pwp, confirm_pass_phrase: pwp, ip: '000.00.000'});

    River.new(null)
    .job('clear customers', [Customer, 'delete_all'])
    .job('clear follows', [follows, 'delete_all'])
    .job('clear posts', [posts, 'delete_all'])
    .job('create:', vals.screen_name, [Customer, 'create', vals])
    .job('record id', function (j, last) {
      customer_id = last.sanitized_data.id;
      j.finish(customer_id);
    })
    .job('read', vals.screen_name, function (j) {
      Customer.read_by_id(customer_id, j);
    })
    .job('save values', function (j, last) {
      customer = last;
      screen_name_id = _.first(_.values(customer.data.screen_name_rows)).id;
      j.finish();
    })
    .run(function () {
      done();
    });
  });

  describe( 'feed', function () {

    it( 'grabs feed of items meant for the world', function (done) {
      River.new(null)
      .job([Follow, 'create',  screen_name_id, 'mag1'])
      .job([Post, 'create_by', screen_name_id, {pub_id: 'mag1', section: 'random', body: 'post 1' }])
      .job([Post, 'create_by', screen_name_id, {pub_id: 'mag1', section: 'emergency', body: 'post 5'}])
      .job([Post, 'create_by', screen_name_id, {pub_id: 'mag1', section: 'status', body: 'post 2'}])
      .job([Post, 'read_feed'])
      .run(function (river, raw_rows) {
        var rows = _.map(raw_rows, function (r, i) { return r.id;});
        assert.deepEqual(rows, ['p2']);
        done();
      });
    });

    it( 'grabs feed of items meant for any of the screen names.', function (done) {
      var db = new pg.query();
      db.q('INSERT INTO posts (id, pub_id, section_id, allow, body) VALUES ( $1, $2, $3, $4, $5 );', ['p3', 'mag1', '3', '{"' + screen_name_id + '"}', 'post 3' ]);
      db.q('INSERT INTO posts (id, pub_id, section_id, allow, body) VALUES ( $1, $2, $3, $4, $5 );', ['p4', 'mag1', '4', '{"' + screen_name_id + '"}', 'post 4' ]);
      db.run_and_then(function (meta) {
        customer.read_feed(function (raw_rows) {
          var rows = _.map(raw_rows, function (r, i) { return r.id;});
          assert.deepEqual(rows, ['p2', 'p3', 'p4'].reverse());
          done();
        });
      });
    });

    it( 'does not grab items for any screen name in disallow.', function (done) {
      var db = new pg.query();
      db.q('INSERT INTO posts (id, pub_id, section_id, allow, disallow, body) VALUES ( $1, $2, $3, $4, $5, $6 );', ['p6', 'mag1', '4', '{"@","' + screen_name_id + '"}', '{"' + screen_name_id + '"}', 'post 6' ]);
      db.run_and_then(function (meta) {
        customer.read_feed(function (raw_rows) {
          var rows = _.map(raw_rows, function (r, i) { return r.id;});
          assert.deepEqual(rows, ['p2', 'p3', 'p4'].reverse());
          done();
        });
      });
    });

    it( 'grabs feed items for customer if in allowed contact label', function (done) {
      var db = new pg.query();
      db.q('INSERT INTO labelings (id, pub_id, label_id) VALUES ( $1, $2, $3 );',       ['lg1', screen_name_id, 'l1'] );
      db.q('INSERT INTO posts (id, pub_id, section_id, allow, body) VALUES ( $1, $2, $3, $4, $5 );', ['p7', 'mag1', '4', '{"@","l1"}', 'post 7' ]);
      db.run_and_then(function (meta) {
        customer.read_feed(function (raw_rows) {
          var rows = _.map(raw_rows, function (r, i) { return r.id;});
          assert.deepEqual(rows, ['p2', 'p3', 'p4', 'p7'].reverse());
          done();
        });
      });
    });

    it( 'does not grab items for any screen name in disallowed labels.', function (done) {
      var db = new pg.query();
      db.q('INSERT INTO posts (id, pub_id, section_id, allow, disallow, body) VALUES ( $1, $2, $3, $4, $5, $6 );', ['p8', 'mag1', '4', '{"@"}', '{"l1"}', 'post 8' ]);
      db.run_and_then(function (meta) {
        customer.read_feed(function (raw_rows) {
          var rows = _.map(raw_rows, function (r, i) { return r.id;});
          assert.deepEqual(rows, ['p2', 'p3', 'p4', 'p7'].reverse());
          done();
        });
      });
    });

  }); // === describe

}); // === end desc

<commit_msg>Removed: Obsolete Post<commit_after><|endoftext|>


<commit_before>//jshint esversion: 6
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import CallLog from './call_log';

const streams = {};
const Services = {
    initializeCallSession(_id, meteorUser) {
        CallLog.update({
            $or: [{
                status: {
                    $ne: "FINISHED"
                },
                caller: meteorUser._id
            }, {
                status: {
                    $ne: "FINISHED"
                },
                target: meteorUser._id
            }]

        }, {
            $set: {
                status: "FINISHED"
            }
        });
        const logId = CallLog.insert({
            status: "NEW",
            target: _id,
            caller: meteorUser._id,
            callerConnectionId: this.connection.id
        });
        streams[logId] = new Meteor.Streamer(logId);
        streams[logId].allowRead('all');
        streams[logId].allowWrite('all');
        return logId;
    },
    /**
     * Call allows you to call a remote user using their userId
     * @param _id {string}
     */
    call(_id) {
        check(_id, String);
        const meteorUser = Meteor.user();
        if (!meteorUser) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN", {
                caller: meteorUser._id,
                target: _id
            });
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        if (Services.checkConnect(meteorUser._id, _id)) {
            const inCall = CallLog.findOne({
                status: "CONNECTED",
                target: _id
            });
            if (inCall) {
                const err = new Meteor.Error(500, "TARGET_IN_CALL", inCall);
                Meteor.VideoCallServices.onError(err, inCall, Meteor.userId());
                throw err;
            }
            else {
                return Services.initializeCallSession(_id, meteorUser);
            }
        }
        else {
            Services.connectionNotAllowed(_id, meteorUser);
        }

    },
    connectionNotAllowed(_id, meteorUser) {
        const err = new Meteor.Error(403, "CONNECTION_NOT_ALLOWED", {
            target: meteorUser._id,
            caller: _id
        });
        Meteor.VideoCallServices.onError(err, _id, meteorUser);
        throw err;
    },
    /**
     * Check if call connection should be permitted
     * @param _id {caller}
     * @param _id {target}
     * @returns boolean
     */
    checkConnect(caller, target) {
        return true;
    },
    /**
     * Answer current phone call
     */
    answer() {
        const user = Meteor.user();
        if (!user) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN");
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        const session = CallLog.findOne({
            target: user._id,
            status: 'NEW'
        });
        if (!session) {
            const err = new Meteor.Error(500, 'SESSION_NOT_FOUND', {
                target: user._id
            });
            Meteor.VideoCallServices.onError(err, undefined, user);
            throw err;
        }

        else {
            CallLog.update({
                _id: session._id
            }, {
                $set: {
                    targetConnectionId: this.connection.id,
                    status: 'ACCEPTED'
                }
            });
        }
    },
    /**
     * End current phone call
     */
    end() {
        const _id = Meteor.userId();
        CallLog.find({
            $or: [{
                status: {
                    $ne: 'FINISHED'
                },
                target: _id
            }, {
                status: {
                    $ne: 'FINISHED'
                },
                caller: _id
            }]
        }).forEach(call =>
            CallLog.update({
                _id: call._id
            }, {
                $set: {
                    status: 'FINISHED'
                }
            }));
    },
    ackReject(id) {
        CallLog.update({
            _id: id,
            caller: Meteor.userId()
        }, {
            $set: {
                status: "FINISHED"
            }
        });
    },
    reject() {
        const user = Meteor.user();
        if (user) {
            CallLog.update({
                target: user._id,
                status: 'NEW'
            }, {
                $set: {
                    status: "REJECTED"
                }
            });
        }
        else {
            throw new Meteor.Error(403, "Could not find user");
        }
    }
};

export {
    Services
};
<commit_msg>improve call method for codeclimate<commit_after>//jshint esversion: 6
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import CallLog from './call_log';

const streams = {};
const Services = {
    destroyOldCalls(meteorUser) {
        CallLog.update({
            $or: [{
                status: {
                    $ne: "FINISHED"
                },
                caller: meteorUser._id
            }, {
                status: {
                    $ne: "FINISHED"
                },
                target: meteorUser._id
            }]

        }, {
            $set: {
                status: "FINISHED"
            }
        });
    },
    initializeCallSession(_id, meteorUser) {
        this.destroyOldCalls(meteorUser);
        const logId = CallLog.insert({
            status: "NEW",
            target: _id,
            caller: meteorUser._id,
            callerConnectionId: this.connection.id
        });
        streams[logId] = new Meteor.Streamer(logId);
        streams[logId].allowRead('all');
        streams[logId].allowWrite('all');
        return logId;
    },
    getUser(_id){
        const meteorUser = Meteor.user();
        if (!meteorUser) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN", {
                caller: meteorUser._id,
                target: _id
            });
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        return meteorUser;
    },
    /**
     * Call allows you to call a remote user using their userId
     * @param _id {string}
     */
    call(_id) {
        check(_id, String);
        const meteorUser = Services.getUser(_id);
        if (Services.checkConnect(meteorUser._id, _id)) {
            const inCall = CallLog.findOne({
                status: "CONNECTED",
                target: _id
            });
            if (inCall) {
                const err = new Meteor.Error(500, "TARGET_IN_CALL", inCall);
                Meteor.VideoCallServices.onError(err, inCall, Meteor.userId());
                throw err;
            }
            else {
                return Services.initializeCallSession(_id, meteorUser);
            }
        }
        else {
            Services.connectionNotAllowed(_id, meteorUser);
        }

    },
    connectionNotAllowed(_id, meteorUser) {
        const err = new Meteor.Error(403, "CONNECTION_NOT_ALLOWED", {
            target: meteorUser._id,
            caller: _id
        });
        Meteor.VideoCallServices.onError(err, _id, meteorUser);
        throw err;
    },
    /**
     * Check if call connection should be permitted
     * @param _id {caller}
     * @param _id {target}
     * @returns boolean
     */
    checkConnect(caller, target) {
        return true;
    },
    /**
     * Answer current phone call
     */
    answer() {
        const user = Meteor.user();
        if (!user) {
            const err = new Meteor.Error(403, "USER_NOT_LOGGED_IN");
            Meteor.VideoCallServices.onError(err);
            throw err;
        }
        const session = CallLog.findOne({
            target: user._id,
            status: 'NEW'
        });
        if (!session) {
            const err = new Meteor.Error(500, 'SESSION_NOT_FOUND', {
                target: user._id
            });
            Meteor.VideoCallServices.onError(err, undefined, user);
            throw err;
        }

        else {
            CallLog.update({
                _id: session._id
            }, {
                $set: {
                    targetConnectionId: this.connection.id,
                    status: 'ACCEPTED'
                }
            });
        }
    },
    /**
     * End current phone call
     */
    end() {
        const _id = Meteor.userId();
        CallLog.find({
            $or: [{
                status: {
                    $ne: 'FINISHED'
                },
                target: _id
            }, {
                status: {
                    $ne: 'FINISHED'
                },
                caller: _id
            }]
        }).forEach(call =>
            CallLog.update({
                _id: call._id
            }, {
                $set: {
                    status: 'FINISHED'
                }
            }));
    },
    ackReject(id) {
        CallLog.update({
            _id: id,
            caller: Meteor.userId()
        }, {
            $set: {
                status: "FINISHED"
            }
        });
    },
    reject() {
        const user = Meteor.user();
        if (user) {
            CallLog.update({
                target: user._id,
                status: 'NEW'
            }, {
                $set: {
                    status: "REJECTED"
                }
            });
        }
        else {
            throw new Meteor.Error(403, "Could not find user");
        }
    }
};

export {
    Services
};
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var DIR_ROOT            = __dirname + '/..',

        path                = require('path'),

        join                = require('join-io'),
        mollify             = require('mollify'),
        restafary           = require('restafary'),
        socketFile          = require('socket-file'),
        readjson            = require('readjson'),

        HOME                = require('os-homedir')(),
        Edit                = require('../json/edit.json'),
        jshint              = readjson.sync.try(path.join(HOME, '.jshintrc')),

        Root,

        minifyFunc          = mollify({
            dir     : DIR_ROOT
        });

    if (!jshint)
        jshint = readjson.sync(path.join(DIR_ROOT, '.jshintrc'));

    module.exports          = function(options) {
        return serve.bind(null, options);
    };

    module.exports.listen   = function(socket, options) {
        var ret;

        if (!options)
            options = {};

        if (!options.prefix)
            options.prefix = '/edward';

        if (!options.root)
            options.root = '/';

        Root = options.root;

        ret = socketFile(socket, options);

        return ret;
    };

    function checkOption(isOption) {
        var is,
            type    = typeof isOption;

        switch(type) {
        case 'function':
            is = isOption();
            break;

        case 'undefined':
            is = true;
            break;

        default:
            is = isOption;
        }

        return is;
    }

    function copy(from) {
        return Object
            .keys(from)
            .reduce(function(value, name) {
                value[name] = from[name];
            }, {});
    }

    function readEdit(callback) {
        var homePath = HOME + '/.edward.json';

        readjson(homePath, function(error, edit) {
            var data;

            if (!error)
                data = copy(edit);
            else if (error.code !== 'ENOENT')
                error = Error('edward --config ' + homePath + ': ' + error.message);
            else
                error = null;

            if (!data && !error)
                data = copy(Edit);

            callback(error, data);
        });
    }

    function serve(options, req, res, next) {
        var joinFunc, restafaryFunc, isJoin, isConfig, isEdit,
            isJsHint,
            o           = options || {},
            isMin       = checkOption(o.minify),
            isOnline    = checkOption(o.online),
            isDiff      = checkOption(o.diff),
            isZip       = checkOption(o.zip),

            url         = req.url,
            prefix      = o.prefix || '/edward',

            isEdward    = !url.indexOf(prefix),
            EDIT        = '/edit.json',
            JSHINT      = '/jshint.json',
            URL         = '/edward.js',
            CONFIG      = '/options.json',
            MODULES     = '/modules.json',
            PATH        = '/lib/client.js',

            isRestafary,

            sendFile    = function() {
                url = path.normalize(DIR_ROOT + url);
                res.sendFile(url);
            };

        if (!isEdward) {
            next();
        } else {
            url         = url.replace(prefix, '');

            isJoin      = !url.indexOf('/join');
            isJsHint    = url === JSHINT;
            isConfig    = url === CONFIG;
            isEdit      = url === EDIT;
            isRestafary = [
                '/api/v1/fs',
                '/restafary.js'
            ].some(function(item) {
                return !url.indexOf(item);
            });

            switch(url) {
            case URL:
                url = PATH;
                break;

            case MODULES:
                url = '/json' + url;
                break;
            }

            req.url = url;

             if (isEdit) {
                readEdit(function(error, data) {
                    if (error)
                        res.status(404)
                            .send(error.message);
                    else
                        res .type('json')
                            .send(data);
                });
            } else if (isConfig) {
                res .type('json')
                    .send({
                        diff: isDiff,
                        zip: isZip,
                        online: isOnline
                    });
            } else if (isJsHint) {
                res .type('json')
                    .send(jshint);
            } else if (isJoin) {
                joinFunc = join({
                    dir     : DIR_ROOT,
                    minify  : isMin
                });

                joinFunc(req, res, next);
            } else if (isRestafary) {
                restafaryFunc = restafary({
                    prefix: '/api/v1/fs',
                    root: Root
                });

                restafaryFunc(req, res, next);
            } else if (isMin) {
                minifyFunc(req, res, sendFile);
            } else {
                sendFile();
            }
        }
    }
})();
<commit_msg>fix(server) readEdit<commit_after>(function() {
    'use strict';

    var DIR_ROOT            = __dirname + '/..',

        path                = require('path'),

        join                = require('join-io'),
        mollify             = require('mollify'),
        restafary           = require('restafary'),
        socketFile          = require('socket-file'),
        readjson            = require('readjson'),

        HOME                = require('os-homedir')(),
        Edit                = require('../json/edit.json'),
        jshint              = readjson.sync.try(path.join(HOME, '.jshintrc')),

        Root,

        minifyFunc          = mollify({
            dir     : DIR_ROOT
        });

    if (!jshint)
        jshint = readjson.sync(path.join(DIR_ROOT, '.jshintrc'));

    module.exports          = function(options) {
        return serve.bind(null, options);
    };

    module.exports.listen   = function(socket, options) {
        var ret;

        if (!options)
            options = {};

        if (!options.prefix)
            options.prefix = '/edward';

        if (!options.root)
            options.root = '/';

        Root = options.root;

        ret = socketFile(socket, options);

        return ret;
    };

    function checkOption(isOption) {
        var is,
            type    = typeof isOption;

        switch(type) {
        case 'function':
            is = isOption();
            break;

        case 'undefined':
            is = true;
            break;

        default:
            is = isOption;
        }

        return is;
    }

    function copy(from) {
        return Object
            .keys(from)
            .reduce(function(value, name) {
                value[name] = from[name];
                return value;
            }, {});
    }

    function replace(from, to) {
        Object.keys(from).forEach(function(name) {
            to[name] = from[name];
        });
    }

    function readEdit(callback) {
        var homePath = HOME + '/.edward.json';

        readjson(homePath, function(error, edit) {
            var data = copy(Edit);

            if (!error)
                replace(edit, data);
            else if (error.code !== 'ENOENT')
                error = Error('edward --config ' + homePath + ': ' + error.message);
            else
                error = null;

            callback(error, data);
        });
    }

    function serve(options, req, res, next) {
        var joinFunc, restafaryFunc, isJoin, isConfig, isEdit,
            isJsHint,
            o           = options || {},
            isMin       = checkOption(o.minify),
            isOnline    = checkOption(o.online),
            isDiff      = checkOption(o.diff),
            isZip       = checkOption(o.zip),

            url         = req.url,
            prefix      = o.prefix || '/edward',

            isEdward    = !url.indexOf(prefix),
            EDIT        = '/edit.json',
            JSHINT      = '/jshint.json',
            URL         = '/edward.js',
            CONFIG      = '/options.json',
            MODULES     = '/modules.json',
            PATH        = '/lib/client.js',

            isRestafary,

            sendFile    = function() {
                url = path.normalize(DIR_ROOT + url);
                res.sendFile(url);
            };

        if (!isEdward) {
            next();
        } else {
            url         = url.replace(prefix, '');

            isJoin      = !url.indexOf('/join');
            isJsHint    = url === JSHINT;
            isConfig    = url === CONFIG;
            isEdit      = url === EDIT;
            isRestafary = [
                '/api/v1/fs',
                '/restafary.js'
            ].some(function(item) {
                return !url.indexOf(item);
            });

            switch(url) {
            case URL:
                url = PATH;
                break;

            case MODULES:
                url = '/json' + url;
                break;
            }

            req.url = url;

             if (isEdit) {
                readEdit(function(error, data) {
                    if (error)
                        res.status(404)
                            .send(error.message);
                    else
                        res .type('json')
                            .send(data);
                });
            } else if (isConfig) {
                res .type('json')
                    .send({
                        diff: isDiff,
                        zip: isZip,
                        online: isOnline
                    });
            } else if (isJsHint) {
                res .type('json')
                    .send(jshint);
            } else if (isJoin) {
                joinFunc = join({
                    dir     : DIR_ROOT,
                    minify  : isMin
                });

                joinFunc(req, res, next);
            } else if (isRestafary) {
                restafaryFunc = restafary({
                    prefix: '/api/v1/fs',
                    root: Root
                });

                restafaryFunc(req, res, next);
            } else if (isMin) {
                minifyFunc(req, res, sendFile);
            } else {
                sendFile();
            }
        }
    }
})();
<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

var express = require('express'),
fs = require('fs-extra'),
log = require('log'),
moment = require('moment'),
os = require('os'),
path = require('path'),
program = require('commander'),
querystring = require('querystring'),
S = require('string'),
twitter = require('twitter-api'),
_ = require('underscore');

var defaultCacheDir = path.join(os.tmpdir(),'twitter-api-proxy');

// setup options and logging
program
  .version('0.0.1')
  .option('-c, --cache-directory [' + defaultCacheDir + ']', 'Webserver local twitter data file cache [' + defaultCacheDir + ']', defaultCacheDir)
  .option('-d, --cache-duration [7d]', 'Cache file duration [7d]', '7d')
  .option('-h, --hostname [*all]', 'Webserver local listening address [*all]')
  .option('-l, --log-level [level]', 'Log at [level] level [error]', 'error')
  .option('-p, --port [number]', 'Webserver port [8080]', parseInt, 8080)
  .option('-t, --twitter-auth [file]', 'File with twitter authentication information [./twitter.json]', './.twitter.json')
  .parse(process.argv);

var logger = new log(program.logLevel);
logger.info('twitter auth file [%s]', program.twitterAuth);
logger.debug('cacheDir [%s]', program.cacheDirectory);

// I don't love this but its a quick/dirty create of cache duration using momenr's nice date add format
// parse cache duration as [integer][unit] via http://momentjs.com/docs/#/manipulating/add/ shorthand
var durations = /^([\d]+)([\w]+)/.exec(program.cacheDuration);
var invalidateSeconds = moment(0).add(durations[2],durations[1]).unix();
logger.debug('cache invalidates in [%d] seconds', invalidateSeconds);

// read in twitter credentials, create client and  attempt basic authorization
// no error callback available on two methods in this block :(
try{
  var twitterAuth = fs.readJsonSync(program.twitterAuth); // no err
  logger.debug('twitter auth info:',twitterAuth);

  var client = twitter.createClient();

  client.setAuth( //no err
    twitterAuth.consumerKey,
    twitterAuth.consumerSecret,
    twitterAuth.accessKey,
    twitterAuth.accessSecret
  );
}catch(e){
  logger.emergency('unable to set twitter authentication information : ', e);
  process.exit(1);
}

// instantiate server only after this succeeds?
client.get( 'account/verify_credentials', { skip_status: true }, function( user, error, status ){
  if(error){
    logger.emergency('unable to authenticate to twitter : ', error);
    process.exit(1);
  }else logger.info('authenticated at Twitter as @%s',user.screen_name);
});

logger.debug('Express starting on [%j:%j]', program.hostname, program.port);

var server = express();

// right now all we implement are the suggestions and user timeline

server.get('/users/suggestions/:slug', function(req, res){
  logger.debug('users suggestions request w/ %s', req.params.slug);
  cachePass(req, res, 'users/suggestions/' + req.params.slug,{
    include_entities : true
  });
});

server.get('/statuses/user_timeline', function(req, res){
  logger.debug('user timeline request');
  cachePass(req, res, 'statuses/user_timeline',req.query);
});

server.use(express.bodyParser());

server.listen(program.port, program.hostname);

function cachePass(req, res, url, args){
  // if cache file & cache file within cache time, return it
  // else pass request to twitter and cache in cacheFile

  var argsString = (args ? querystring.stringify(args) : '');
  var cacheFilename = path.join(program.cacheDirectory,S(url + argsString).slugify().ensureRight('.json').s);
  logger.debug('cachePass on url [%s] via file [%s]', url, cacheFilename);

  //reach cache

  fs.readJson(cacheFilename, function(err, saved){

    var now = moment().unix();
    var valid = (saved && saved._cached && ( (now - saved._cached) < invalidateSeconds));
    // do something abuot pruning existing files that are invalid
    if(!saved) logger.debug('no datafile for ', cacheFilename);

    if(err) logger.debug('error reading cache file [%s] : %j', cacheFilename, err);
    if(!valid) logger.debug('cache file invalid [%s]', cacheFilename);
      // regenerate file
      // pass url to twitter api and cache results if valid
    if(err || (!valid)){
      client.get(url, args, function(data, err, status){
	logger.debug('twitter request [%s] status[%d]', url, status);
	if(err){
	  logger.debug('error requesting [%s] : %j', url, err);
	  return res.send(err, status);
	}else{
	  // wrap it in a object storing the cache meta-data
	  var saved = {
	    _cached : moment().unix(),
	    data : data};
	  fs.outputJson(cacheFilename, saved, function(err){
	    if(err) logger.error('Unable to write cache file [%s]', cacheFilename);
	  });
	  return res.json(data);
	}
      });
    }else{
      logger.debug('returning cached data @ [%s]', cacheFilename);
      return res.json(saved.data);
    }
  });
}

<commit_msg>fixed typo in comment<commit_after>#!/usr/bin/env node
"use strict";

var express = require('express'),
fs = require('fs-extra'),
log = require('log'),
moment = require('moment'),
os = require('os'),
path = require('path'),
program = require('commander'),
querystring = require('querystring'),
S = require('string'),
twitter = require('twitter-api'),
_ = require('underscore');

var defaultCacheDir = path.join(os.tmpdir(),'twitter-api-proxy');

// setup options and logging
program
  .version('0.0.1')
  .option('-c, --cache-directory [' + defaultCacheDir + ']', 'Webserver local twitter data file cache [' + defaultCacheDir + ']', defaultCacheDir)
  .option('-d, --cache-duration [7d]', 'Cache file duration [7d]', '7d')
  .option('-h, --hostname [*all]', 'Webserver local listening address [*all]')
  .option('-l, --log-level [level]', 'Log at [level] level [error]', 'error')
  .option('-p, --port [number]', 'Webserver port [8080]', parseInt, 8080)
  .option('-t, --twitter-auth [file]', 'File with twitter authentication information [./twitter.json]', './.twitter.json')
  .parse(process.argv);

var logger = new log(program.logLevel);
logger.info('twitter auth file [%s]', program.twitterAuth);
logger.debug('cacheDir [%s]', program.cacheDirectory);

// I don't love this but its a quick/dirty create of cache duration using moment's nice date add format
// parse cache duration as [integer][unit] via http://momentjs.com/docs/#/manipulating/add/ shorthand
var durations = /^([\d]+)([\w]+)/.exec(program.cacheDuration);
var invalidateSeconds = moment(0).add(durations[2],durations[1]).unix();
logger.debug('cache invalidates in [%d] seconds', invalidateSeconds);

// read in twitter credentials, create client and  attempt basic authorization
// no error callback available on two methods in this block :(
try{
  var twitterAuth = fs.readJsonSync(program.twitterAuth); // no err
  logger.debug('twitter auth info:',twitterAuth);

  var client = twitter.createClient();

  client.setAuth( //no err
    twitterAuth.consumerKey,
    twitterAuth.consumerSecret,
    twitterAuth.accessKey,
    twitterAuth.accessSecret
  );
}catch(e){
  logger.emergency('unable to set twitter authentication information : ', e);
  process.exit(1);
}

// instantiate server only after this succeeds?
client.get( 'account/verify_credentials', { skip_status: true }, function( user, error, status ){
  if(error){
    logger.emergency('unable to authenticate to twitter : ', error);
    process.exit(1);
  }else logger.info('authenticated at Twitter as @%s',user.screen_name);
});

logger.debug('Express starting on [%j:%j]', program.hostname, program.port);

var server = express();

// right now all we implement are the suggestions and user timeline

server.get('/users/suggestions/:slug', function(req, res){
  logger.debug('users suggestions request w/ %s', req.params.slug);
  cachePass(req, res, 'users/suggestions/' + req.params.slug,{
    include_entities : true
  });
});

server.get('/statuses/user_timeline', function(req, res){
  logger.debug('user timeline request');
  cachePass(req, res, 'statuses/user_timeline',req.query);
});

server.use(express.bodyParser());

server.listen(program.port, program.hostname);

function cachePass(req, res, url, args){
  // if cache file & cache file within cache time, return it
  // else pass request to twitter and cache in cacheFile

  var argsString = (args ? querystring.stringify(args) : '');
  var cacheFilename = path.join(program.cacheDirectory,S(url + argsString).slugify().ensureRight('.json').s);
  logger.debug('cachePass on url [%s] via file [%s]', url, cacheFilename);

  //reach cache

  fs.readJson(cacheFilename, function(err, saved){

    var now = moment().unix();
    var valid = (saved && saved._cached && ( (now - saved._cached) < invalidateSeconds));
    // do something abuot pruning existing files that are invalid
    if(!saved) logger.debug('no datafile for ', cacheFilename);

    if(err) logger.debug('error reading cache file [%s] : %j', cacheFilename, err);
    if(!valid) logger.debug('cache file invalid [%s]', cacheFilename);
      // regenerate file
      // pass url to twitter api and cache results if valid
    if(err || (!valid)){
      client.get(url, args, function(data, err, status){
	logger.debug('twitter request [%s] status[%d]', url, status);
	if(err){
	  logger.debug('error requesting [%s] : %j', url, err);
	  return res.send(err, status);
	}else{
	  // wrap it in a object storing the cache meta-data
	  var saved = {
	    _cached : moment().unix(),
	    data : data};
	  fs.outputJson(cacheFilename, saved, function(err){
	    if(err) logger.error('Unable to write cache file [%s]', cacheFilename);
	  });
	  return res.json(data);
	}
      });
    }else{
      logger.debug('returning cached data @ [%s]', cacheFilename);
      return res.json(saved.data);
    }
  });
}

<|endoftext|>


<commit_before>"use strict";

var config = require("../config.js");
var hapi   = require("hapi");
var server = new hapi.Server();

server.connection({port: 8010});

server.auth.scheme("token", require("./tokenauthscheme.js"));

server.auth.strategy("verify", "token", {
	validateFunc: require("./verifytoken.js")
});

server.register({
	register: require("humming"),
	options: {
		modelsPath: "/../../../models",
		adapter: require("russian-doll"),
		adapterOpts: {
			index: config.database.index,
			host:  config.database.host,
			port:  config.database.port
		}
	}
}, function (err) {

	if(err){
		throw err;
	}
	server.route(require("./routes.js"));
});

module.exports = server;<commit_msg>search port by process.env.PORT<commit_after>"use strict";

var config = require("../config.js");
var hapi   = require("hapi");
var server = new hapi.Server();

server.connection({port: process.env.PORT || 8010});

server.auth.scheme("token", require("./tokenauthscheme.js"));

server.auth.strategy("verify", "token", {
	validateFunc: require("./verifytoken.js")
});

server.register({
	register: require("humming"),
	options: {
		modelsPath: "/../../../models",
		adapter: require("russian-doll"),
		adapterOpts: {
			index: config.database.index,
			host:  config.database.host,
			port:  config.database.port
		}
	}
}, function (err) {

	if(err){
		throw err;
	}
	server.route(require("./routes.js"));
});

module.exports = server;
<|endoftext|>


<commit_before>/*
 * Main server controller.
 */

/*
 * Module dependencies.
 */
var util = require('util');
var fs = require('fs');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var express = require('express');
var async = require('async');
var Application = require('./application');
var utils = require('prana').utils;
var _ = require('lodash');

/**
 * Main server controller class.
 *
 * @class Server
 */
var Server = module.exports = function(applicationsDir) {
  EventEmitter.call(this);

  this.baseDir = path.resolve(__dirname + '/..');

  this.applicationsDir = path.resolve(applicationsDir);

  // Create main express application.
  this.mainApplication = express();

  this.webServer = null;

  // Initialize applications container.
  this.applications = {};
};

util.inherits(Server, EventEmitter);

/**
 * Initialize the server at a given port.
 *
 * @param {Number} port Port number.
 * @param {Function} [callback] Optional callback to run when the server is initialized.
 */
Server.prototype.start = function(port, callback) {
  this.port = port;

  // Set default process environment.
  process.env.NODE_ENV = process.env.NODE_ENV || 'production';

  var self = this;

  // Load all applications.
  this.loadApplications(function() {

    // Start main application.
    self.webServer = self.mainApplication.listen(port, function() {
      self.emit('serverStarted', self, port);

      util.log('Choko server started on port ' + this.address().port + '.');

      if (callback) {
        callback(self);
      }
    });
  });
};

/**
 * Restart the server.
 *
 * @param {Function} callback Function to run when the server is restarted.
 */
Server.prototype.restart = function(callback) {
  var oldApplications = this.applications;
  var oldWebServer = this.webServer;

  // Create a new main application;
  this.mainApplication = express();

  // Let open connections end.
  oldWebServer.close();

  // Try to start the new server on the same port.
  this.start(this.port, function(server) {
    async.each(Object.keys(oldApplications), function(hostname, next) {
      // Unset routes for old apps, to minimise side effects until other
      // all old open connections closes.
      oldApplications[hostname].application.routes = {};
      next();
    }, function() {
      callback(server);
    });
  });
};

/**
 * Get and specific application by hostname.
 *
 * @param {String} hostname Application hostname.
 */
Server.prototype.getApplication = function(hostname) {
  return this.applications[hostname];
};

/**
 * Add an application.
 *
 * @param {String} hostname Application hostname.
 * @param {String} dir Application base directory.
 * @param {Function} callback Function to run when the application is started.
 */
Server.prototype.addApplication = function(hostname, dir, callback) {
  var environments = [null, 'local', process.env.NODE_ENV];
  var settingObjects = [];
  var applications = {
    default: path.join(this.baseDir, '/applications/default'),
    main: path.join(this.applicationsDir, dir)
  };

  // Default settings.
  var settings = {
    baseDir: this.baseDir,
    applicationDir: applications.main
  };

  // Loop through applications.
  Object.keys(applications).forEach(function (name) {
    // Loop through environments to find and load specific settings and add
    // each of the found items to the extension list.
    environments.forEach(function (enviroment) {
      var settings;
      var filePath = path.relative(__dirname, applications[name])
                   + '/settings.'
                   + (enviroment ? enviroment + '.' : '')
                   + 'json';

      try {
        settings = require(filePath);
      } catch (e) {
        // No problem if requre fails.
      } finally {
        settingObjects.push(settings || {});
      }
    });
  });

  // Load and merge all the settings.
  _.merge.apply(null, [settings].concat(settingObjects));

  var application = this.applications[hostname] = new Application(this.mainApplication, hostname, settings);

  // Add a reference to the server to be used by the installer.
  // @todo: this may be not safe in shared server with multiple apps environment.
  application.server = this;

  application.start(function() {
    application.server.emit('applicationAdded', hostname, application);

    util.log('Application "' + settings.application.name + '" started at "' + hostname + '".');
    callback();
  });
};

/**
 * Remove an application.
 *
 * @param {String} hostname Application hostname.
 */
Server.prototype.removeApplication = function(hostname) {
  var application = this.applications[hostname];
  delete this.applications[hostname];

  this.emit('applicationRemoved', hostname, application);

  // Removing middlewares can be possible in the future.
  // @see https://github.com/senchalabs/connect/pull/696
  //this.mainApplication.unuse(express.vhost(applicationName, application));
};

/**
 * Load all applications in the applications folders that are enabled on the
 * applications.js file.
 */
Server.prototype.loadApplications = function(callback) {
  var self = this;
  var applications = JSON.parse(fs.readFileSync(path.join(this.applicationsDir, '/applications.json'), 'utf-8'));
  async.each(Object.keys(applications), function(hostname, next) {
    self.addApplication(hostname, applications[hostname], next);
  }, callback);
};
<commit_msg>Made sure that even if we try to add applications before starting the serve, NODE_ENV falls back to 'production'.<commit_after>/*
 * Main server controller.
 */

/*
 * Module dependencies.
 */
var util = require('util');
var fs = require('fs');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var express = require('express');
var async = require('async');
var Application = require('./application');
var utils = require('prana').utils;
var _ = require('lodash');

/**
 * Main server controller class.
 *
 * @class Server
 */
var Server = module.exports = function(applicationsDir) {
  EventEmitter.call(this);

  this.baseDir = path.resolve(__dirname + '/..');

  this.applicationsDir = path.resolve(applicationsDir);

  // Create main express application.
  this.mainApplication = express();

  this.webServer = null;

  // Initialize applications container.
  this.applications = {};
};

util.inherits(Server, EventEmitter);

/**
 * Initialize the server at a given port.
 *
 * @param {Number} port Port number.
 * @param {Function} [callback] Optional callback to run when the server is initialized.
 */
Server.prototype.start = function(port, callback) {
  this.port = port;

  // Set default process environment.
  process.env.NODE_ENV = process.env.NODE_ENV || 'production';

  var self = this;

  // Load all applications.
  this.loadApplications(function() {

    // Start main application.
    self.webServer = self.mainApplication.listen(port, function() {
      self.emit('serverStarted', self, port);

      util.log('Choko server started on port ' + this.address().port + '.');

      if (callback) {
        callback(self);
      }
    });
  });
};

/**
 * Restart the server.
 *
 * @param {Function} callback Function to run when the server is restarted.
 */
Server.prototype.restart = function(callback) {
  var oldApplications = this.applications;
  var oldWebServer = this.webServer;

  // Create a new main application;
  this.mainApplication = express();

  // Let open connections end.
  oldWebServer.close();

  // Try to start the new server on the same port.
  this.start(this.port, function(server) {
    async.each(Object.keys(oldApplications), function(hostname, next) {
      // Unset routes for old apps, to minimise side effects until other
      // all old open connections closes.
      oldApplications[hostname].application.routes = {};
      next();
    }, function() {
      callback(server);
    });
  });
};

/**
 * Get and specific application by hostname.
 *
 * @param {String} hostname Application hostname.
 */
Server.prototype.getApplication = function(hostname) {
  return this.applications[hostname];
};

/**
 * Add an application.
 *
 * @param {String} hostname Application hostname.
 * @param {String} dir Application base directory.
 * @param {Function} callback Function to run when the application is started.
 */
Server.prototype.addApplication = function(hostname, dir, callback) {
  var environments = [null, 'local', process.env.NODE_ENV || 'production'];
  var settingObjects = [];
  var applications = {
    default: path.join(this.baseDir, '/applications/default'),
    main: path.join(this.applicationsDir, dir)
  };

  // Default settings.
  var settings = {
    baseDir: this.baseDir,
    applicationDir: applications.main
  };

  // Loop through applications.
  Object.keys(applications).forEach(function (name) {
    // Loop through environments to find and load specific settings and add
    // each of the found items to the extension list.
    environments.forEach(function (enviroment) {
      var settings;
      var filePath = path.relative(__dirname, applications[name])
                   + '/settings.'
                   + (enviroment ? enviroment + '.' : '')
                   + 'json';

      try {
        settings = require(filePath);
      } catch (e) {
        // No problem if requre fails.
      } finally {
        settingObjects.push(settings || {});
      }
    });
  });

  // Load and merge all the settings.
  _.merge.apply(null, [settings].concat(settingObjects));

  var application = this.applications[hostname] = new Application(this.mainApplication, hostname, settings);

  // Add a reference to the server to be used by the installer.
  // @todo: this may be not safe in shared server with multiple apps environment.
  application.server = this;

  application.start(function() {
    application.server.emit('applicationAdded', hostname, application);

    util.log('Application "' + settings.application.name + '" started at "' + hostname + '".');
    callback();
  });
};

/**
 * Remove an application.
 *
 * @param {String} hostname Application hostname.
 */
Server.prototype.removeApplication = function(hostname) {
  var application = this.applications[hostname];
  delete this.applications[hostname];

  this.emit('applicationRemoved', hostname, application);

  // Removing middlewares can be possible in the future.
  // @see https://github.com/senchalabs/connect/pull/696
  //this.mainApplication.unuse(express.vhost(applicationName, application));
};

/**
 * Load all applications in the applications folders that are enabled on the
 * applications.js file.
 */
Server.prototype.loadApplications = function(callback) {
  var self = this;
  var applications = JSON.parse(fs.readFileSync(path.join(this.applicationsDir, '/applications.json'), 'utf-8'));
  async.each(Object.keys(applications), function(hostname, next) {
    self.addApplication(hostname, applications[hostname], next);
  }, callback);
};
<|endoftext|>


<commit_before>var util = require("./util");
var bodyParser = require("body-parser");
var WebSocketServer = require("ws").Server;
var url = require("url");
var cors = require("cors");

var app = (exports = module.exports = {});

/** Initialize WebSocket server. */
app._initializeWSS = function(server) {
  var self = this;

  if (this.mountpath instanceof Array) {
    throw new Error("This app can only be mounted on a single path");
  }

  var path = this.mountpath;
  var path = path + (path[path.length - 1] != "/" ? "/" : "") + "peerjs";

  // Create WebSocket server as well.
  this._wss = new WebSocketServer({ path: path, server: server });

  this._wss.on("connection", function(socket, req) {
    var query = url.parse(req.url, true).query;
    var id = query.id;
    var token = query.token;
    var key = query.key;
    var ip = req.socket.remoteAddress;

    if (!id || !token || !key) {
      socket.send(
        JSON.stringify({
          type: "ERROR",
          payload: { msg: "No id, token, or key supplied to websocket server" }
        })
      );
      socket.close();
      return;
    }

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err) {
          if (!self._clients[key][id]) {
            self._clients[key][id] = { token: token, ip: ip };
            self._ips[ip]++;
            socket.send(JSON.stringify({ type: "OPEN" }));
          }
          self._configureWS(socket, key, id, token);
        } else {
          socket.send(JSON.stringify({ type: "ERROR", payload: { msg: err } }));
        }
      });
    } else {
      self._configureWS(socket, key, id, token);
    }
  });
};

app._configureWS = function(socket, key, id, token) {
  var self = this;
  var client = this._clients[key][id];

  if (token === client.token) {
    // res 'close' event will delete client.res for us
    client.socket = socket;
    // Client already exists
    if (client.res) {
      client.res.end();
    }
  } else {
    // ID-taken, invalid token
    socket.send(
      JSON.stringify({ type: "ID-TAKEN", payload: { msg: "ID is taken" } })
    );
    socket.close();
    return;
  }

  this._processOutstanding(key, id);

  // Cleanup after a socket closes.
  socket.on("close", function() {
    self._log("Socket closed:", id);
    if (client.socket == socket) {
      self._removePeer(key, id);
    }
  });

  // Handle messages from peers.
  socket.on("message", function(data) {
    try {
      var message = JSON.parse(data);

      if (
        ["LEAVE", "CANDIDATE", "OFFER", "ANSWER"].indexOf(message.type) !== -1
      ) {
        self._handleTransmission(key, {
          type: message.type,
          src: id,
          dst: message.dst,
          payload: message.payload
        });
      } else {
        util.prettyError("Message unrecognized");
      }
    } catch (e) {
      self._log("Invalid message", data);
      throw e;
    }
  });

  // We're going to emit here, because for XHR we don't *know* when someone
  // disconnects.
  this.emit("connection", id);
};

app._checkAllowsDiscovery = function(key, cb) {
  cb(this._options.allow_discovery);
};

app._checkKey = function(key, ip, cb) {
  if (key == this._options.key) {
    if (!this._clients[key]) {
      this._clients[key] = {};
    }
    if (!this._outstanding[key]) {
      this._outstanding[key] = {};
    }
    if (!this._ips[ip]) {
      this._ips[ip] = 0;
    }
    // Check concurrent limit
    if (
      Object.keys(this._clients[key]).length >= this._options.concurrent_limit
    ) {
      cb("Server has reached its concurrent user limit");
      return;
    }
    if (this._ips[ip] >= this._options.ip_limit) {
      cb(ip + " has reached its concurrent user limit");
      return;
    }
    cb(null);
  } else {
    cb("Invalid key provided");
  }
};

/** Initialize HTTP server routes. */
app._initializeHTTP = function() {
  var self = this;

  this.use(cors());

  this.get("/", function(req, res, next) {
    res.send(require("../app.json"));
  });

  // Retrieve guaranteed random ID.
  this.get("/:key/id", function(req, res, next) {
    res.contentType = "text/html";
    res.send(self._generateClientId(req.params.key));
  });

  // Server sets up HTTP streaming when you get post an ID.
  this.post("/:key/:id/:token/id", function(req, res, next) {
    var id = req.params.id;
    var token = req.params.token;
    var key = req.params.key;
    var ip = req.connection.remoteAddress;

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err && !self._clients[key][id]) {
          self._clients[key][id] = { token: token, ip: ip };
          self._ips[ip]++;
          self._startStreaming(res, key, id, token, true);
        } else {
          res.send(JSON.stringify({ type: "HTTP-ERROR" }));
        }
      });
    } else {
      self._startStreaming(res, key, id, token);
    }
  });

  // Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
  this.get("/:key/peers", function(req, res, next) {
    var key = req.params.key;
    if (self._clients[key]) {
      self._checkAllowsDiscovery(key, function(isAllowed) {
        if (isAllowed) {
          res.send(Object.keys(self._clients[key]));
        } else {
          res.sendStatus(401);
        }
      });
    } else {
      res.sendStatus(404);
    }
  });

  var handle = function(req, res, next) {
    var key = req.params.key;
    var id = req.params.id;

    var client;
    if (!self._clients[key] || !(client = self._clients[key][id])) {
      if (req.params.retry) {
        res.sendStatus(401);
        return;
      } else {
        // Retry this request
        req.params.retry = true;
        setTimeout(handle, 25, req, res);
        return;
      }
    }

    // Auth the req
    if (client.token && req.params.token !== client.token) {
      res.sendStatus(401);
      return;
    } else {
      self._handleTransmission(key, {
        type: req.body.type,
        src: id,
        dst: req.body.dst,
        payload: req.body.payload
      });
      res.sendStatus(200);
    }
  };

  var jsonParser = bodyParser.json();

  this.post("/:key/:id/:token/offer", jsonParser, handle);

  this.post("/:key/:id/:token/candidate", jsonParser, handle);

  this.post("/:key/:id/:token/answer", jsonParser, handle);

  this.post("/:key/:id/:token/leave", jsonParser, handle);
};

/** Saves a streaming response and takes care of timeouts and headers. */
app._startStreaming = function(res, key, id, token, open) {
  var self = this;

  res.writeHead(200, { "Content-Type": "application/octet-stream" });

  var pad = "00";
  for (var i = 0; i < 10; i++) {
    pad += pad;
  }
  res.write(pad + "\n");

  if (open) {
    res.write(JSON.stringify({ type: "OPEN" }) + "\n");
  }

  var client = this._clients[key][id];

  if (token === client.token) {
    // Client already exists
    res.on("close", function() {
      if (client.res === res) {
        if (!client.socket) {
          // No new request yet, peer dead
          self._removePeer(key, id);
          return;
        }
        delete client.res;
      }
    });
    client.res = res;
    this._processOutstanding(key, id);
  } else {
    // ID-taken, invalid token
    res.end(JSON.stringify({ type: "HTTP-ERROR" }));
  }
};

app._pruneOutstanding = function() {
  var keys = Object.keys(this._outstanding);
  for (var k = 0, kk = keys.length; k < kk; k += 1) {
    var key = keys[k];
    var dsts = Object.keys(this._outstanding[key]);
    for (var i = 0, ii = dsts.length; i < ii; i += 1) {
      var offers = this._outstanding[key][dsts[i]];
      var seen = {};
      for (var j = 0, jj = offers.length; j < jj; j += 1) {
        var message = offers[j];
        if (!seen[message.src]) {
          this._handleTransmission(key, {
            type: "EXPIRE",
            src: message.dst,
            dst: message.src
          });
          seen[message.src] = true;
        }
      }
    }
    this._outstanding[key] = {};
  }
};

/** Cleanup */
app._setCleanupIntervals = function() {
  var self = this;

  // Clean up ips every 10 minutes
  setInterval(function() {
    var keys = Object.keys(self._ips);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      if (self._ips[key] === 0) {
        delete self._ips[key];
      }
    }
  }, 600000);

  // Clean up outstanding messages every 5 seconds
  setInterval(function() {
    self._pruneOutstanding();
  }, 5000);
};

/** Process outstanding peer offers. */
app._processOutstanding = function(key, id) {
  var offers = this._outstanding[key][id];
  if (!offers) {
    return;
  }
  for (var j = 0, jj = offers.length; j < jj; j += 1) {
    this._handleTransmission(key, offers[j]);
  }
  delete this._outstanding[key][id];
};

app._removePeer = function(key, id) {
  if (this._clients[key] && this._clients[key][id]) {
    this._ips[this._clients[key][id].ip]--;
    delete this._clients[key][id];
    this.emit("disconnect", id);
  }
};

/** Handles passing on a message. */
app._handleTransmission = function(key, message) {
  var type = message.type;
  var src = message.src;
  var dst = message.dst;
  var data = JSON.stringify(message);

  var destination = this._clients[key][dst];

  // User is connected!
  if (destination) {
    try {
      this._log(type, "from", src, "to", dst);
      if (destination.socket) {
        destination.socket.send(data);
      } else if (destination.res) {
        data += "\n";
        destination.res.write(data);
      } else {
        // Neither socket no res available. Peer dead?
        throw "Peer dead";
      }
    } catch (e) {
      // This happens when a peer disconnects without closing connections and
      // the associated WebSocket has not closed.
      // Tell other side to stop trying.
      this._removePeer(key, dst);
      this._handleTransmission(key, {
        type: "LEAVE",
        src: dst,
        dst: src
      });
    }
  } else {
    // Wait for this client to connect/reconnect (XHR) for important
    // messages.
    if (type !== "LEAVE" && type !== "EXPIRE" && dst) {
      var self = this;
      if (!this._outstanding[key][dst]) {
        this._outstanding[key][dst] = [];
      }
      this._outstanding[key][dst].push(message);
    } else if (type === "LEAVE" && !dst) {
      this._removePeer(key, src);
    } else {
      // Unavailable destination specified with message LEAVE or EXPIRE
      // Ignore
    }
  }
};

app._generateClientId = function(key) {
  var clientId = util.randomId();
  if (!this._clients[key]) {
    return clientId;
  }
  while (!!this._clients[key][clientId]) {
    clientId = util.randomId();
  }
  return clientId;
};

app._log = function() {
  if (this._options.debug) {
    console.log.apply(console, arguments);
  }
};
<commit_msg>Ignore heartbeat messages<commit_after>var util = require("./util");
var bodyParser = require("body-parser");
var WebSocketServer = require("ws").Server;
var url = require("url");
var cors = require("cors");

var app = (exports = module.exports = {});

/** Initialize WebSocket server. */
app._initializeWSS = function(server) {
  var self = this;

  if (this.mountpath instanceof Array) {
    throw new Error("This app can only be mounted on a single path");
  }

  var path = this.mountpath;
  var path = path + (path[path.length - 1] != "/" ? "/" : "") + "peerjs";

  // Create WebSocket server as well.
  this._wss = new WebSocketServer({ path: path, server: server });

  this._wss.on("connection", function(socket, req) {
    var query = url.parse(req.url, true).query;
    var id = query.id;
    var token = query.token;
    var key = query.key;
    var ip = req.socket.remoteAddress;

    if (!id || !token || !key) {
      socket.send(
        JSON.stringify({
          type: "ERROR",
          payload: { msg: "No id, token, or key supplied to websocket server" }
        })
      );
      socket.close();
      return;
    }

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err) {
          if (!self._clients[key][id]) {
            self._clients[key][id] = { token: token, ip: ip };
            self._ips[ip]++;
            socket.send(JSON.stringify({ type: "OPEN" }));
          }
          self._configureWS(socket, key, id, token);
        } else {
          socket.send(JSON.stringify({ type: "ERROR", payload: { msg: err } }));
        }
      });
    } else {
      self._configureWS(socket, key, id, token);
    }
  });
};

app._configureWS = function(socket, key, id, token) {
  var self = this;
  var client = this._clients[key][id];

  if (token === client.token) {
    // res 'close' event will delete client.res for us
    client.socket = socket;
    // Client already exists
    if (client.res) {
      client.res.end();
    }
  } else {
    // ID-taken, invalid token
    socket.send(
      JSON.stringify({ type: "ID-TAKEN", payload: { msg: "ID is taken" } })
    );
    socket.close();
    return;
  }

  this._processOutstanding(key, id);

  // Cleanup after a socket closes.
  socket.on("close", function() {
    self._log("Socket closed:", id);
    if (client.socket == socket) {
      self._removePeer(key, id);
    }
  });

  // Handle messages from peers.
  socket.on("message", function(data) {
    try {
      var message = JSON.parse(data);

      if (
        ["LEAVE", "CANDIDATE", "OFFER", "ANSWER"].indexOf(message.type) !== -1
      ) {
        self._handleTransmission(key, {
          type: message.type,
          src: id,
          dst: message.dst,
          payload: message.payload
        });
      } else if (message.type === 'HEARTBEAT') {
        // Ignore - nothing needs doing here.
      } else {
        util.prettyError("Message unrecognized");
      }
    } catch (e) {
      self._log("Invalid message", data);
      throw e;
    }
  });

  // We're going to emit here, because for XHR we don't *know* when someone
  // disconnects.
  this.emit("connection", id);
};

app._checkAllowsDiscovery = function(key, cb) {
  cb(this._options.allow_discovery);
};

app._checkKey = function(key, ip, cb) {
  if (key == this._options.key) {
    if (!this._clients[key]) {
      this._clients[key] = {};
    }
    if (!this._outstanding[key]) {
      this._outstanding[key] = {};
    }
    if (!this._ips[ip]) {
      this._ips[ip] = 0;
    }
    // Check concurrent limit
    if (
      Object.keys(this._clients[key]).length >= this._options.concurrent_limit
    ) {
      cb("Server has reached its concurrent user limit");
      return;
    }
    if (this._ips[ip] >= this._options.ip_limit) {
      cb(ip + " has reached its concurrent user limit");
      return;
    }
    cb(null);
  } else {
    cb("Invalid key provided");
  }
};

/** Initialize HTTP server routes. */
app._initializeHTTP = function() {
  var self = this;

  this.use(cors());

  this.get("/", function(req, res, next) {
    res.send(require("../app.json"));
  });

  // Retrieve guaranteed random ID.
  this.get("/:key/id", function(req, res, next) {
    res.contentType = "text/html";
    res.send(self._generateClientId(req.params.key));
  });

  // Server sets up HTTP streaming when you get post an ID.
  this.post("/:key/:id/:token/id", function(req, res, next) {
    var id = req.params.id;
    var token = req.params.token;
    var key = req.params.key;
    var ip = req.connection.remoteAddress;

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err && !self._clients[key][id]) {
          self._clients[key][id] = { token: token, ip: ip };
          self._ips[ip]++;
          self._startStreaming(res, key, id, token, true);
        } else {
          res.send(JSON.stringify({ type: "HTTP-ERROR" }));
        }
      });
    } else {
      self._startStreaming(res, key, id, token);
    }
  });

  // Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
  this.get("/:key/peers", function(req, res, next) {
    var key = req.params.key;
    if (self._clients[key]) {
      self._checkAllowsDiscovery(key, function(isAllowed) {
        if (isAllowed) {
          res.send(Object.keys(self._clients[key]));
        } else {
          res.sendStatus(401);
        }
      });
    } else {
      res.sendStatus(404);
    }
  });

  var handle = function(req, res, next) {
    var key = req.params.key;
    var id = req.params.id;

    var client;
    if (!self._clients[key] || !(client = self._clients[key][id])) {
      if (req.params.retry) {
        res.sendStatus(401);
        return;
      } else {
        // Retry this request
        req.params.retry = true;
        setTimeout(handle, 25, req, res);
        return;
      }
    }

    // Auth the req
    if (client.token && req.params.token !== client.token) {
      res.sendStatus(401);
      return;
    } else {
      self._handleTransmission(key, {
        type: req.body.type,
        src: id,
        dst: req.body.dst,
        payload: req.body.payload
      });
      res.sendStatus(200);
    }
  };

  var jsonParser = bodyParser.json();

  this.post("/:key/:id/:token/offer", jsonParser, handle);

  this.post("/:key/:id/:token/candidate", jsonParser, handle);

  this.post("/:key/:id/:token/answer", jsonParser, handle);

  this.post("/:key/:id/:token/leave", jsonParser, handle);
};

/** Saves a streaming response and takes care of timeouts and headers. */
app._startStreaming = function(res, key, id, token, open) {
  var self = this;

  res.writeHead(200, { "Content-Type": "application/octet-stream" });

  var pad = "00";
  for (var i = 0; i < 10; i++) {
    pad += pad;
  }
  res.write(pad + "\n");

  if (open) {
    res.write(JSON.stringify({ type: "OPEN" }) + "\n");
  }

  var client = this._clients[key][id];

  if (token === client.token) {
    // Client already exists
    res.on("close", function() {
      if (client.res === res) {
        if (!client.socket) {
          // No new request yet, peer dead
          self._removePeer(key, id);
          return;
        }
        delete client.res;
      }
    });
    client.res = res;
    this._processOutstanding(key, id);
  } else {
    // ID-taken, invalid token
    res.end(JSON.stringify({ type: "HTTP-ERROR" }));
  }
};

app._pruneOutstanding = function() {
  var keys = Object.keys(this._outstanding);
  for (var k = 0, kk = keys.length; k < kk; k += 1) {
    var key = keys[k];
    var dsts = Object.keys(this._outstanding[key]);
    for (var i = 0, ii = dsts.length; i < ii; i += 1) {
      var offers = this._outstanding[key][dsts[i]];
      var seen = {};
      for (var j = 0, jj = offers.length; j < jj; j += 1) {
        var message = offers[j];
        if (!seen[message.src]) {
          this._handleTransmission(key, {
            type: "EXPIRE",
            src: message.dst,
            dst: message.src
          });
          seen[message.src] = true;
        }
      }
    }
    this._outstanding[key] = {};
  }
};

/** Cleanup */
app._setCleanupIntervals = function() {
  var self = this;

  // Clean up ips every 10 minutes
  setInterval(function() {
    var keys = Object.keys(self._ips);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      if (self._ips[key] === 0) {
        delete self._ips[key];
      }
    }
  }, 600000);

  // Clean up outstanding messages every 5 seconds
  setInterval(function() {
    self._pruneOutstanding();
  }, 5000);
};

/** Process outstanding peer offers. */
app._processOutstanding = function(key, id) {
  var offers = this._outstanding[key][id];
  if (!offers) {
    return;
  }
  for (var j = 0, jj = offers.length; j < jj; j += 1) {
    this._handleTransmission(key, offers[j]);
  }
  delete this._outstanding[key][id];
};

app._removePeer = function(key, id) {
  if (this._clients[key] && this._clients[key][id]) {
    this._ips[this._clients[key][id].ip]--;
    delete this._clients[key][id];
    this.emit("disconnect", id);
  }
};

/** Handles passing on a message. */
app._handleTransmission = function(key, message) {
  var type = message.type;
  var src = message.src;
  var dst = message.dst;
  var data = JSON.stringify(message);

  var destination = this._clients[key][dst];

  // User is connected!
  if (destination) {
    try {
      this._log(type, "from", src, "to", dst);
      if (destination.socket) {
        destination.socket.send(data);
      } else if (destination.res) {
        data += "\n";
        destination.res.write(data);
      } else {
        // Neither socket no res available. Peer dead?
        throw "Peer dead";
      }
    } catch (e) {
      // This happens when a peer disconnects without closing connections and
      // the associated WebSocket has not closed.
      // Tell other side to stop trying.
      this._removePeer(key, dst);
      this._handleTransmission(key, {
        type: "LEAVE",
        src: dst,
        dst: src
      });
    }
  } else {
    // Wait for this client to connect/reconnect (XHR) for important
    // messages.
    if (type !== "LEAVE" && type !== "EXPIRE" && dst) {
      var self = this;
      if (!this._outstanding[key][dst]) {
        this._outstanding[key][dst] = [];
      }
      this._outstanding[key][dst].push(message);
    } else if (type === "LEAVE" && !dst) {
      this._removePeer(key, src);
    } else {
      // Unavailable destination specified with message LEAVE or EXPIRE
      // Ignore
    }
  }
};

app._generateClientId = function(key) {
  var clientId = util.randomId();
  if (!this._clients[key]) {
    return clientId;
  }
  while (!!this._clients[key][clientId]) {
    clientId = util.randomId();
  }
  return clientId;
};

app._log = function() {
  if (this._options.debug) {
    console.log.apply(console, arguments);
  }
};
<|endoftext|>