<commit_before>
/*
 * fsroute
 * https://github.com/randymized/fsroute
 *
 * Copyright (c) 2013 Randy McLaughlin
 * Licensed under the MIT license.
 */

'use strict';

var _= require('underscore')
var Path= require('path')
var fs= require('fs')
var ComposableMiddleware= require('composable-middleware')
var require_directory= require('require-directory')
var RequestHandler= require('./request-handler')

var FSRoute= function(tree)
{
  this.tree= tree || {};

  this.RequestHandler= RequestHandler.bind(this,this)
}

FSRoute.prototype.set_code_root= function(root) {
  // preload all the modules in code_root (synchronously!)
  var code_root= root;
  this.modules= {}
  try {
    if (fs.statSync(code_root).isDirectory()) {
      this.modules= require_directory(module,code_root)
    }
  }
  catch (err) {
    if (err.code !== 'ENOENT') throw err;
  }
  return this;
}

FSRoute.prototype.set_resource_root= function(root) {
  this.resource_root= root;
  return this;
}

FSRoute.prototype.set_code_and_resource_roots= function(root) {
  return this.set_code_root(Path.join(root,'code'))
             .set_resource_root(Path.join(root,'resources'))
}

FSRoute.prototype.createRequestHandler= function(context,next,err) {
  return new this.RequestHandler(context,next,err)
}

FSRoute.prototype.composable_middleware= function (){
  var RequestHandler= this.RequestHandler;
  return function (next) {
    new RequestHandler(this,next).descend();
  }
}

FSRoute.prototype.composable_error_handler= function (){
  var RequestHandler= this.RequestHandler;
  return function (err, next) {
    new RequestHandler(this,next,err).descend();
  }
}

FSRoute.prototype.connect_middleware= function (){
  var mw= ComposableMiddleware(this.composable_middleware())
  return function (req,res,next) {
    mw(req,res,next)
  }
}

FSRoute.prototype.connect_error_handler= function (){
  var mw= ComposableMiddleware(this.composable_error_handler())
  return function (req,res,next) {
    mw(req,res,err,next)
  }
}

module.exports = FSRoute;
<commit_msg>Start with a tree and extend it to include modules.<commit_after>
/*
 * fsroute
 * https://github.com/randymized/fsroute
 *
 * Copyright (c) 2013 Randy McLaughlin
 * Licensed under the MIT license.
 */

'use strict';

var _= require('underscore')
var Path= require('path')
var fs= require('fs')
var ComposableMiddleware= require('composable-middleware')
var require_directory= require('require-directory')
var requestHandler= require('./request-handler')

var InvalidObjKey= /^\*|\/$|\.$/

function validate_tree(tree) {
  for (var key in tree) {
    var val = tree[key];
    if (_.isFunction(val)) continue;
    if (InvalidObjKey.test(key)) throw new Error('Keys starting with "*" or ending with "." or "/" are reserved for functions: '+key+' is not valid.')
    if (_.isObject(val)) validate_tree(val);
    else throw new Error('The tree may contain only objects and functions: '+key+' is neither.')
  }
}

var FSRoute= function(tree)
{
  this.tree= tree || {};

  this._module_extensions= ['js','coffee']

  // validate the tree.  It may only include functions and objects
  validate_tree(tree)
}

FSRoute.prototype.set_module_extentions= function(extensions) {
  if (_.isArray(extensions)) {
    this._module_extensions= extensions
  }
  else if (_.isString(extensions)) {
    this._module_extensions= [extensions]
  }
  else {
    throw new Error('set_module_extentions argument an array of strings or a string')
  }
  return this;
}

FSRoute.prototype.rename_module= function(name)
{
  if (/.*\.LEAF\.[^.]$/) return '$'
}

// Preload all the modules in root and its subdirectories and add them to the tree.
// The loading is done synchronously!
FSRoute.prototype.add_modules= function(root)
{
  var fsroute= this;
  function descend(dest,path) {
    var add_function= function(into,name,module) {
      var ins_name= fsroute.rename_module(name)
      if (into[name]) {
        into[name]= ComposableMiddleware(into[name],module)
      }
      else {
        into[name]= module;
      }
    }
    var names= fs.readdirSync(path)
    for (var i= names.length; --i >= 0;) {
      var name= names[i];
      var fullname= Path.join(path,name);
      var stats= fs.statSync(fullname);
      if (stats.isDirectory()) {
        var newdest= {}
        if (dest[name]) {
          if (_.isFunction(dest[name])) {
            dest[name]= {$:dest[name]}
          }
        }
        else {
          dest[name]= {}
        }
        descend(dest[name],fullname);
        if (_.isEmpty(dest[name])) delete dest[name]
      }
      if (stats.isFile()) {
        var ext= Path.extname(name)
        if (fsroute._module_extensions.indexOf(ext.slice(1)) >= 0) {
          var module= require(fullname)
          var key= name.slice(0,-ext.length)
          if (dest[key]) {
            if (_isFunction(dest[key])) {
              add_function(dest,key,module)
            }
            else {
              add_function(dest[key],'_',module)
            }
          }
          else {
            add_function(dest,key,module)
          }
        }
      }
    }
    return dest
  }
  descend(this.tree,root)

  return this;
}

FSRoute.prototype.requestHandler= function(context,next) {
  return requestHandler(this,context,next)
}

FSRoute.prototype.composable_middleware= function (){
  var fsRoute= this;
  return function (next) {
    fsRoute.requestHandler(fsRoute,this,next);
  }
}

FSRoute.prototype.connect_middleware= function (){
  var mw= ComposableMiddleware(this.composable_middleware())
  return function (req,res,next) {
    mw(req,res,next)
  }
}

module.exports = FSRoute;
<|endoftext|>


<commit_before>angular.module('app', ['ui.bootstrap'])

angular.module('app').directive('identicon', function () {
    return {
      restrict: 'E', // element
      scope: {
        hash: '=',
        iconSize: '='
      },
      link: function(scope, element, attrs) {
        var iconSize = scope.iconSize || 32;

        // Create the identicon
        function createFromHex(dataHex) {
          var data = new Identicon(dataHex, iconSize).toString();
          element.html('');
        }
        // Watch for hash changes
        scope.$watch('hash', function() {
          if (scope.hash) {
            var tohash = scope.hash.substring(32, 64)
            createFromHex(tohash);
          }
        })
      }
    }
  });

angular.module('app').controller('Market', ['$scope', function($scope) {

  $scope.newuser = true
  $scope.page = false
  $scope.dashboard = true
  $scope.myInfoPanel = true
  $scope.shouts = [];
  $scope.newShout = ""
  $scope.searching = ""
  $scope.currentReviews = []
  $scope.myOrders = []
  $scope.myReviews = []
  $scope.sidebar = true

  $scope.createShout = function() {
     // launch a shout
     console.log($scope)
     var newShout = {'type': 'shout', 'text': $scope.newShout, 'pubkey': $scope.myself.pubkey}
     socket.send('shout', newShout)
     $scope.shouts.push(newShout)
     $scope.newShout = '';
  }

  $scope.peers = [];
  $scope.reviews = {};

  $scope.toggleSidebar = function() {
    $scope.sidebar = ($scope.sidebar) ? false : true;
  }

  $scope.hideSidebar = function() {
    $scope.sidebar = false;
  }

  $scope.showSidebar = function() {
    $scope.sidebar = true;
  }

  $scope.awaitingShop = null;
  $scope.queryShop = function(peer) {
      console.log('Shop',peer);
     $scope.dashboard = false;
     $scope.showStorePanel('storeProducts');
     $scope.awaitingShop = peer.pubkey;
     var query = {'type': 'query_page', 'pubkey': peer.pubkey}
     socket.send('query_page', query)
  }

 // Open the websocket connection and handle messages
  var socket = new Connection(function(msg) {
   switch(msg.type) {
      case 'peer':
         $scope.add_peer(msg)
         break;
      case 'peer_remove':
         $scope.remove_peer(msg)
         break;
      case 'page':
         $scope.parse_page(msg)
         break;
      case 'myself':
         $scope.parse_myself(msg)
         break;
      case 'shout':
         $scope.parse_shout(msg)
         break;
      case 'order':
         $scope.parse_order(msg)
         break;
      case 'myorders':
      	 $scope.parse_myorders(msg)
      	 break;
      case 'reputation':
      	 console.log(msg);
         $scope.parse_reputation(msg)
         break;
      case 'proto_response_pubkey':
         $scope.parse_response_pubkey(msg)
         break;
      default:
         console.log("Unhandled message!",msg)
         break;
    }
  })

  var add_review_to_page = function(pubkey, review) {
    var found = false;

    console.log("Add review");

    if (!$scope.reviews.hasOwnProperty(pubkey)) {
        $scope.reviews[pubkey] = []
    }
    $scope.reviews[pubkey].forEach(function(_review) {
        if (_review.sig == review.sig && _review.subject == review.subject && _review.pubkey == review.pubkey) {
           console.log("Found a review for this market")
           found = true
        }
    });
    if (!found) {
        // check if the review is about me
        if ($scope.myself.pubkey == review.subject) {
        	console.log("Found review for myself")
            $scope.myReviews.push(review)
        }
        $scope.reviews[pubkey].push(review)
    }
    $scope.$apply();
    console.log($scope.reviews);
  }

  $scope.parse_order = function(msg) {

  	  console.log("Order update");

      if ($scope.myOrders.hasOwnProperty(msg.id)) {
          console.log("Updating order!")
          $scope.myOrders[msg.id].state = msg.state
          $scope.myOrders[msg.id].tx = msg.tx
          $scope.myOrders[msg.id].escrows = msg.escrows
          $scope.myOrders[msg.id].address = msg.address
          return;
      } else {
          console.log(msg);
          $scope.myOrders.push(msg);
      }
      if (!$scope.$$phase) {
      	 console.log($scope.myOrders);
         $scope.$apply();
      }
  }

  $scope.parse_welcome = function(msg) {

    console.log(msg)

  }

  $scope.parse_myorders = function(msg) {

  	  console.log('Retrieved my orders: ',msg);
  	  $scope.orders = msg['orders'];
      console.log($scope.orders);

      if (!$scope.$$phase) {
	       $scope.$apply();
	    }

  }

  $scope.parse_response_pubkey = function(msg) {
      var pubkey = msg.pubkey;
      var nickname = msg.nickname;
      $scope.peers.forEach(function(peer) {
          if (peer.pubkey == pubkey) {
             // this peer!!
             peer.nickname = msg.nickname;
             if ($scope.searching == msg.nickname) {
                 $scope.queryShop(peer)
             }
          }
      });
      if (!$scope.$$phase) {

         $scope.$apply();
      }
  }

  // Peer information has arrived
  $scope.parse_reputation = function(msg) {

    console.log('Parsing reputation', msg.reviews)
    msg.reviews.forEach(function(review) {
        add_review_to_page(review.subject, review);
    });
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.parse_page = function(msg) {

    if (msg.pubkey != $scope.awaitingShop)
       return
    if (!$scope.reviews.hasOwnProperty(msg.pubkey)) {
        $scope.reviews[msg.pubkey] = []
    }
    $scope.currentReviews = $scope.reviews[msg.pubkey]
    $scope.page = msg

    // Write in store content into the HTML
    var contentDiv = document.getElementById('page-content')
    contentDiv.innerHTML = msg.text;

    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.add_peer = function(msg) {

    console.log('Add peer: ',msg);

    /* get index if peer is already known */
    var index = [-1].concat($scope.peers).reduce(
        function(previousValue, currentValue, index, array){
            return currentValue.uri == msg.uri ? index : previousValue ;
    });

    if(index == -1) {
        /* it is a new peer */
        $scope.peers.push(msg);
    } else {
        $scope.peers[index] = msg;
    }
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.remove_peer = function(msg) {

    console.log('Remove peer: ',msg);

    $scope.peers = $scope.peers.filter(function(element) {
        return element.uri != msg.uri;
    });

    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.review= {rating:5, text:""}
  $scope.addReview = function() {

     var query = {'type': 'review', 'pubkey': $scope.page.pubkey, 'text': $scope.review.text, 'rating': parseInt($scope.review.rating)}
     socket.send('review', query)

     // store in appropriate format (its different than push format :P)
     add_review_to_page($scope.page.pubkey, {type: 'review', 'pubkey': $scope.myself.pubkey, 'subject': $scope.page.pubkey, 'rating': query.rating, text: query.text})

     $scope.review.rating = 5;
     $scope.review.text = '';
     $scope.showReviewForm = false;
  }

  // My information has arrived
  $scope.parse_myself = function(msg) {

    $scope.myself = msg;

    if (!$scope.$$phase) {
       $scope.$apply();
    }

    // Settings

    $scope.settings = msg.settings

    msg.reputation.forEach(function(review) {
       add_review_to_page($scope.myself.pubkey, review)
    });

    msg.peers.forEach(function(peer) {
       $scope.add_peer(peer)
    });

  }

  // A shout has arrived
  $scope.parse_shout = function(msg) {
    $scope.shouts.push(msg)
    console.log('Shout',msg)
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }
  $scope.search = ""
  $scope.searchNickname = function() {
     var query = {'type': 'search', 'text': $scope.search };
     $scope.searching = $scope.search;
     socket.send('search', query)
     $scope.search = ""
  }

  $scope.settings = { email:'', PGPPubKey:'', bitmessage:'', pubkey:'', secret:'', nickname:'', welcome:'', escrowAddresses:''}
  $scope.saveSettings = function() {
      var query = {'type': 'update_settings', settings: $scope.settings }
      socket.send('update_settings', query)
  }

  // Create a new order and send to the network
  $scope.newOrder = {text:'', tx: ''}
  $scope.createOrder = function() {
      $scope.creatingOrder = false;
      var newOrder = {
          'text': $scope.newOrder.text,
          'state': 'new',
          'buyer': $scope.myself.pubkey,
          'seller': $scope.page.pubkey
      }
      $scope.newOrder.text = '';
      //$scope.orders.push(newOrder);     // This doesn't really do much since it gets wiped away
      socket.send('order', newOrder);
      $scope.sentOrder = true;

      $scope.showDashboardPanel('orders');

      $('#pill-orders').addClass('active').siblings().removeClass('active').blur();
      $("#orderSuccessAlert").alert();
      window.setTimeout(function() { $("#orderSuccessAlert").alert('close') } , 5000);

  }
  $scope.payOrder = function(order) {
      order.state = 'payed'
      order.tx = $scope.newOrder.tx;
      $scope.newOrder.tx = '';
      socket.send('order', order);
  }
  $scope.receiveOrder = function(order) {
      order.state = 'received'
      socket.send('order', order);
  }
  $scope.sendOrder = function(order) {
      order.state = 'sent'
      socket.send('order', order);
  }

  $scope.cancelOrder = function(order) {
  	order.state = 'cancelled'
  	socket.send('order', order)
  }

  $scope.addEscrow = function() {
    escrowAddress = $('#inputEscrowAddress').val();
    $('#inputEscrowAddress').val('');

    // TODO: Check for valid escrow address

    if(!$scope.settings.escrowAddresses) {
      $scope.settings.escrowAddresses = [];
    }
    $scope.settings.escrowAddresses.push(escrowAddress);

    // Dedupe escrow addresses
    var uniqueEscrows = [];
    $.each($scope.settings.escrowAddresses, function(i, el){
        if($.inArray(el, uniqueEscrows) === -1) uniqueEscrows.push(el);
    });

    $scope.settings.escrowAddresses = uniqueEscrows;

    $scope.saveSettings();
  }

  function resetPanels() {
  	$scope.messagesPanel = false;
  	$scope.reviewsPanel = false;
  	$scope.productCatalogPanel = false;
  	$scope.settingsPanel = false;
  	$scope.ordersPanel = false;
  	$scope.myInfoPanel = false;
  }

  $scope.showDashboardPanel = function(panelName) {

    resetPanels();

    if(panelName != 'myInfo') {
      $scope.hideSidebar();
      $('#dashboard-container').removeClass('col-sm-8').addClass('col-sm-12')
    } else {
      $scope.showSidebar();
      $('#dashboard-container').removeClass('col-sm-12').addClass('col-sm-8')
    }

    $scope.dashboard = true;
    $scope.page = false;

  	switch(panelName) {
  		case 'messages':
  			$scope.messagesPanel = true;
  			break;
  		case 'reviews':
  			$scope.reviewsPanel = true;
  			break;
  		case 'orders':
  			$scope.ordersPanel = true;
  			$scope.queryMyOrder();
  			break;
  		case 'productCatalog':
  			$scope.productCatalogPanel = true;
  			break;
  		case 'settings':
  			$scope.settingsPanel = true;
  			break;
  		case 'myInfo':
  			$scope.myInfoPanel = true;
  			break;

  	}
  }

  function resetStorePanels() {
  	$scope.storeProductsPanel = false;
  	$scope.storeReviewsPanel = false;
  	$scope.storeOrdersPanel = false;
  	$scope.storeInfoPanel = false;
  	$scope.storeOrderHistoryPanel = false;
  }

  $scope.showStorePanel = function(panelName) {

    resetStorePanels();
  	$scope.dashboard = false;

  	switch(panelName) {
  		case 'storeProducts':
  			$scope.storeProductsPanel = true;
  			break;
  		case 'storeOrders':
  			//$scope.storeOrdersPanel = true;
  			break;
  		case 'storeReviews':
  			$scope.storeReviewsPanel = true;
  			break;
  		case 'storeInfo':
  			$scope.storeInfoPanel = true;
  			break;

  	}
  }

  $scope.queryMyOrder = function() {
    	// Query for orders
    	var query = {'type': 'query_orders', 'pubkey': ''}
    	console.log('querying orders')
    	socket.send('query_orders', query)

  }

  $('ul.nav.nav-pills li a').click(function() {
	    $(this).parent().addClass('active').siblings().removeClass('active').blur();
	});

// Modal Code
$scope.WelcomeModalCtrl = function ($scope, $modal, $log) {

  $scope.open = function (size, backdrop) {

      backdrop = backdrop ? backdrop : true;

      var modalInstance = $modal.open({
        templateUrl: 'myModalContent.html',
        controller: ModalInstanceCtrl,
        size: size,
        backdrop: backdrop,
        resolve: {
          settings: function () {
            return $scope.settings;
          }
        }
      });

      modalInstance.result.then(function (selectedItem) {
        $scope.selected = selectedItem;
      }, function () {
        $log.info('Modal dismissed at: ' + new Date());
      });

    }

  // Please note that $modalInstance represents a modal window (instance) dependency.
  // It is not the same as the $modal service used above.

  var ModalInstanceCtrl = function ($scope, $modalInstance, settings) {

    $scope.settings = settings;
    // $scope.selected = {
    //   item: $scope.items[0]
    // };
    //

    $scope.welcome = settings.welcome;

    $scope.ok = function () {
      $modalInstance.dismiss('cancel');
    };

    $scope.cancel = function () {
      $modalInstance.dismiss('cancel');
    };
  };

};

}])
<commit_msg>Only load store page if not in dashboard view<commit_after>angular.module('app', ['ui.bootstrap'])

angular.module('app').directive('identicon', function () {
    return {
      restrict: 'E', // element
      scope: {
        hash: '=',
        iconSize: '='
      },
      link: function(scope, element, attrs) {
        var iconSize = scope.iconSize || 32;

        // Create the identicon
        function createFromHex(dataHex) {
          var data = new Identicon(dataHex, iconSize).toString();
          element.html('');
        }
        // Watch for hash changes
        scope.$watch('hash', function() {
          if (scope.hash) {
            var tohash = scope.hash.substring(32, 64)
            createFromHex(tohash);
          }
        })
      }
    }
  });

angular.module('app').controller('Market', ['$scope', function($scope) {

  $scope.newuser = true
  $scope.page = false
  $scope.dashboard = true
  $scope.myInfoPanel = true
  $scope.shouts = [];
  $scope.newShout = ""
  $scope.searching = ""
  $scope.currentReviews = []
  $scope.myOrders = []
  $scope.myReviews = []
  $scope.sidebar = true

  $scope.createShout = function() {
     // launch a shout
     console.log($scope)
     var newShout = {'type': 'shout', 'text': $scope.newShout, 'pubkey': $scope.myself.pubkey}
     socket.send('shout', newShout)
     $scope.shouts.push(newShout)
     $scope.newShout = '';
  }

  $scope.peers = [];
  $scope.reviews = {};

  $scope.toggleSidebar = function() {
    $scope.sidebar = ($scope.sidebar) ? false : true;
  }

  $scope.hideSidebar = function() {
    $scope.sidebar = false;
  }

  $scope.showSidebar = function() {
    $scope.sidebar = true;
  }

  $scope.awaitingShop = null;
  $scope.queryShop = function(peer) {
      console.log('Shop',peer);
     $scope.dashboard = false;
     $scope.showStorePanel('storeProducts');
     $scope.awaitingShop = peer.pubkey;
     var query = {'type': 'query_page', 'pubkey': peer.pubkey}
     socket.send('query_page', query)
  }

 // Open the websocket connection and handle messages
  var socket = new Connection(function(msg) {
   switch(msg.type) {
      case 'peer':
         $scope.add_peer(msg)
         break;
      case 'peer_remove':
         $scope.remove_peer(msg)
         break;
      case 'page':
         $scope.parse_page(msg)
         break;
      case 'myself':
         $scope.parse_myself(msg)
         break;
      case 'shout':
         $scope.parse_shout(msg)
         break;
      case 'order':
         $scope.parse_order(msg)
         break;
      case 'myorders':
      	 $scope.parse_myorders(msg)
      	 break;
      case 'reputation':
      	 console.log(msg);
         $scope.parse_reputation(msg)
         break;
      case 'proto_response_pubkey':
         $scope.parse_response_pubkey(msg)
         break;
      default:
         console.log("Unhandled message!",msg)
         break;
    }
  })

  var add_review_to_page = function(pubkey, review) {
    var found = false;

    console.log("Add review");

    if (!$scope.reviews.hasOwnProperty(pubkey)) {
        $scope.reviews[pubkey] = []
    }
    $scope.reviews[pubkey].forEach(function(_review) {
        if (_review.sig == review.sig && _review.subject == review.subject && _review.pubkey == review.pubkey) {
           console.log("Found a review for this market")
           found = true
        }
    });
    if (!found) {
        // check if the review is about me
        if ($scope.myself.pubkey == review.subject) {
        	console.log("Found review for myself")
            $scope.myReviews.push(review)
        }
        $scope.reviews[pubkey].push(review)
    }
    $scope.$apply();
    console.log($scope.reviews);
  }

  $scope.parse_order = function(msg) {

  	  console.log("Order update");

      if ($scope.myOrders.hasOwnProperty(msg.id)) {
          console.log("Updating order!")
          $scope.myOrders[msg.id].state = msg.state
          $scope.myOrders[msg.id].tx = msg.tx
          $scope.myOrders[msg.id].escrows = msg.escrows
          $scope.myOrders[msg.id].address = msg.address
          return;
      } else {
          console.log(msg);
          $scope.myOrders.push(msg);
      }
      if (!$scope.$$phase) {
      	 console.log($scope.myOrders);
         $scope.$apply();
      }
  }

  $scope.parse_welcome = function(msg) {

    console.log(msg)

  }

  $scope.parse_myorders = function(msg) {

  	  console.log('Retrieved my orders: ',msg);
  	  $scope.orders = msg['orders'];
      console.log($scope.orders);

      if (!$scope.$$phase) {
	       $scope.$apply();
	    }

  }

  $scope.parse_response_pubkey = function(msg) {
      var pubkey = msg.pubkey;
      var nickname = msg.nickname;
      $scope.peers.forEach(function(peer) {
          if (peer.pubkey == pubkey) {
             // this peer!!
             peer.nickname = msg.nickname;
             if ($scope.searching == msg.nickname) {
                 $scope.queryShop(peer)
             }
          }
      });
      if (!$scope.$$phase) {

         $scope.$apply();
      }
  }

  // Peer information has arrived
  $scope.parse_reputation = function(msg) {

    console.log('Parsing reputation', msg.reviews)
    msg.reviews.forEach(function(review) {
        add_review_to_page(review.subject, review);
    });
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.parse_page = function(msg) {

    if (msg.pubkey != $scope.awaitingShop)
       return
    if (!$scope.reviews.hasOwnProperty(msg.pubkey)) {
        $scope.reviews[msg.pubkey] = []
    }

    if (!$scope.dashboard) {
      $scope.currentReviews = $scope.reviews[msg.pubkey]
      $scope.page = msg

      // Write in store content into the HTML
      var contentDiv = document.getElementById('page-content')
      contentDiv.innerHTML = msg.text;

      if (!$scope.$$phase) {
         $scope.$apply();
      }
    }
  }

  $scope.add_peer = function(msg) {

    console.log('Add peer: ',msg);

    /* get index if peer is already known */
    var index = [-1].concat($scope.peers).reduce(
        function(previousValue, currentValue, index, array){
            return currentValue.uri == msg.uri ? index : previousValue ;
    });

    if(index == -1) {
        /* it is a new peer */
        $scope.peers.push(msg);
    } else {
        $scope.peers[index] = msg;
    }
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.remove_peer = function(msg) {

    console.log('Remove peer: ',msg);

    $scope.peers = $scope.peers.filter(function(element) {
        return element.uri != msg.uri;
    });

    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }

  $scope.review= {rating:5, text:""}
  $scope.addReview = function() {

     var query = {'type': 'review', 'pubkey': $scope.page.pubkey, 'text': $scope.review.text, 'rating': parseInt($scope.review.rating)}
     socket.send('review', query)

     // store in appropriate format (its different than push format :P)
     add_review_to_page($scope.page.pubkey, {type: 'review', 'pubkey': $scope.myself.pubkey, 'subject': $scope.page.pubkey, 'rating': query.rating, text: query.text})

     $scope.review.rating = 5;
     $scope.review.text = '';
     $scope.showReviewForm = false;
  }

  // My information has arrived
  $scope.parse_myself = function(msg) {

    $scope.myself = msg;

    if (!$scope.$$phase) {
       $scope.$apply();
    }

    // Settings

    $scope.settings = msg.settings

    msg.reputation.forEach(function(review) {
       add_review_to_page($scope.myself.pubkey, review)
    });

    msg.peers.forEach(function(peer) {
       $scope.add_peer(peer)
    });

  }

  // A shout has arrived
  $scope.parse_shout = function(msg) {
    $scope.shouts.push(msg)
    console.log('Shout',msg)
    if (!$scope.$$phase) {
       $scope.$apply();
    }
  }
  $scope.search = ""
  $scope.searchNickname = function() {
     var query = {'type': 'search', 'text': $scope.search };
     $scope.searching = $scope.search;
     socket.send('search', query)
     $scope.search = ""
  }

  $scope.settings = { email:'', PGPPubKey:'', bitmessage:'', pubkey:'', secret:'', nickname:'', welcome:'', escrowAddresses:''}
  $scope.saveSettings = function() {
      var query = {'type': 'update_settings', settings: $scope.settings }
      socket.send('update_settings', query)
  }

  // Create a new order and send to the network
  $scope.newOrder = {text:'', tx: ''}
  $scope.createOrder = function() {
      $scope.creatingOrder = false;
      var newOrder = {
          'text': $scope.newOrder.text,
          'state': 'new',
          'buyer': $scope.myself.pubkey,
          'seller': $scope.page.pubkey
      }
      $scope.newOrder.text = '';
      //$scope.orders.push(newOrder);     // This doesn't really do much since it gets wiped away
      socket.send('order', newOrder);
      $scope.sentOrder = true;

      $scope.showDashboardPanel('orders');

      $('#pill-orders').addClass('active').siblings().removeClass('active').blur();
      $("#orderSuccessAlert").alert();
      window.setTimeout(function() { $("#orderSuccessAlert").alert('close') } , 5000);

  }
  $scope.payOrder = function(order) {
      order.state = 'payed'
      order.tx = $scope.newOrder.tx;
      $scope.newOrder.tx = '';
      socket.send('order', order);
  }
  $scope.receiveOrder = function(order) {
      order.state = 'received'
      socket.send('order', order);
  }
  $scope.sendOrder = function(order) {
      order.state = 'sent'
      socket.send('order', order);
  }

  $scope.cancelOrder = function(order) {
  	order.state = 'cancelled'
  	socket.send('order', order)
  }

  $scope.addEscrow = function() {
    escrowAddress = $('#inputEscrowAddress').val();
    $('#inputEscrowAddress').val('');

    // TODO: Check for valid escrow address

    if(!$scope.settings.escrowAddresses) {
      $scope.settings.escrowAddresses = [];
    }
    $scope.settings.escrowAddresses.push(escrowAddress);

    // Dedupe escrow addresses
    var uniqueEscrows = [];
    $.each($scope.settings.escrowAddresses, function(i, el){
        if($.inArray(el, uniqueEscrows) === -1) uniqueEscrows.push(el);
    });

    $scope.settings.escrowAddresses = uniqueEscrows;

    $scope.saveSettings();
  }

  function resetPanels() {
  	$scope.messagesPanel = false;
  	$scope.reviewsPanel = false;
  	$scope.productCatalogPanel = false;
  	$scope.settingsPanel = false;
  	$scope.ordersPanel = false;
  	$scope.myInfoPanel = false;
  }

  $scope.showDashboardPanel = function(panelName) {

    resetPanels();

    if(panelName != 'myInfo') {
      $scope.hideSidebar();
      $('#dashboard-container').removeClass('col-sm-8').addClass('col-sm-12')
    } else {
      $scope.showSidebar();
      $('#dashboard-container').removeClass('col-sm-12').addClass('col-sm-8')
    }

    $scope.dashboard = true;
    $scope.page = false;

  	switch(panelName) {
  		case 'messages':
  			$scope.messagesPanel = true;
  			break;
  		case 'reviews':
  			$scope.reviewsPanel = true;
  			break;
  		case 'orders':
  			$scope.ordersPanel = true;
  			$scope.queryMyOrder();
  			break;
  		case 'productCatalog':
  			$scope.productCatalogPanel = true;
  			break;
  		case 'settings':
  			$scope.settingsPanel = true;
  			break;
  		case 'myInfo':
  			$scope.myInfoPanel = true;
  			break;

  	}
  }

  function resetStorePanels() {
  	$scope.storeProductsPanel = false;
  	$scope.storeReviewsPanel = false;
  	$scope.storeOrdersPanel = false;
  	$scope.storeInfoPanel = false;
  	$scope.storeOrderHistoryPanel = false;
  }

  $scope.showStorePanel = function(panelName) {

    resetStorePanels();
  	$scope.dashboard = false;

  	switch(panelName) {
  		case 'storeProducts':
  			$scope.storeProductsPanel = true;
  			break;
  		case 'storeOrders':
  			//$scope.storeOrdersPanel = true;
  			break;
  		case 'storeReviews':
  			$scope.storeReviewsPanel = true;
  			break;
  		case 'storeInfo':
  			$scope.storeInfoPanel = true;
  			break;

  	}
  }

  $scope.queryMyOrder = function() {
    	// Query for orders
    	var query = {'type': 'query_orders', 'pubkey': ''}
    	console.log('querying orders')
    	socket.send('query_orders', query)

  }

  $('ul.nav.nav-pills li a').click(function() {
	    $(this).parent().addClass('active').siblings().removeClass('active').blur();
	});

// Modal Code
$scope.WelcomeModalCtrl = function ($scope, $modal, $log) {

  $scope.open = function (size, backdrop) {

      backdrop = backdrop ? backdrop : true;

      var modalInstance = $modal.open({
        templateUrl: 'myModalContent.html',
        controller: ModalInstanceCtrl,
        size: size,
        backdrop: backdrop,
        resolve: {
          settings: function () {
            return $scope.settings;
          }
        }
      });

      modalInstance.result.then(function (selectedItem) {
        $scope.selected = selectedItem;
      }, function () {
        $log.info('Modal dismissed at: ' + new Date());
      });

    }

  // Please note that $modalInstance represents a modal window (instance) dependency.
  // It is not the same as the $modal service used above.

  var ModalInstanceCtrl = function ($scope, $modalInstance, settings) {

    $scope.settings = settings;
    // $scope.selected = {
    //   item: $scope.items[0]
    // };
    //

    $scope.welcome = settings.welcome;

    $scope.ok = function () {
      $modalInstance.dismiss('cancel');
    };

    $scope.cancel = function () {
      $modalInstance.dismiss('cancel');
    };
  };

};

}])
<|endoftext|>


<commit_before>/*
 * grabber
 * https://github.com/ezoapp/grabber.git
 *
 * Copyright (c) 2014
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('underscore'),
  request = require('request'),
  fs = require('fs'),
  cheerio = require('cheerio'),
  urllib = require('url'),
  pathlib = require('path'),
  glob = require('glob'),
  wrench = require('wrench'),
  hashids = new(require('hashids'))(new Date().getTime() + 'T');

function Grabber(data, options, logger, originUrl, socket) {
  this.$ = null;
  this.data = data;
  this.baseUrl = '';
  this.tasklist = [];
  this.options = _.extend({
    dstDir: 'www',
    baseDir: '',
    tmplDir: '',
    indexFile: 'index.html'
  }, options || {});
  this.logger = (logger) ? logger : console;
  this.originUrl = new RegExp(originUrl, 'i');
  this.streamlist = [];
  this.socket = socket;
}

Grabber.prototype = (function () {

  var RES_TAG = {
    'href': ['link'], //,'a'],
    'src': ['img', 'script'], //, 'iframe'],
    'data-background-image': ['a']
  },
    USE_LOCAL_RES = {
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile-1.4.2.min.js',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.external-png-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.external-png-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.icons-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.icons-1.4.2.min.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.inline-png-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.inline-png-1.4.2.min.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.inline-svg-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.inline-svg-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.structure-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.structure-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.theme-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.theme-1.4.2.min.css'
      //          ,
      //          'http://jqmdesigner.appspot.com/1.4.2/gk/lib/jquery.mobile/1.4.2/flatui/jquery.mobile.flatui.css': 'gk/lib/jquery.mobile/1.4.2/flatui/jquery.mobile.flatui.css',
      //          'http://jqmdesigner.appspot.com/1.4.2/components/jquery.gk/jquery.gk.min.js': 'components/jquery.gk/jquery.gk.min.js',
      //          'http://jqmdesigner.appspot.com/1.4.2/components/gk-loader/gk-loader.js': 'components/gk-loader/gk-loader.js'
    },
    EXCLUDE_GKCOM = ['jqgrid', 'editor', 'niceditor', 'tab-panel'],
    EXCLUDE_GKLIB = ['ace', 'ace-min', 'backbone', 'bootstrap', 'codemirror', 'gk', 'htmlparser', 'jquery', 'jquery.cookie', 'jquery.mobile', 'jquery.nicescroll', 'jquery.ui.tabs', 'jqgrid', 'miniscroll-js', 'nicedit', 'require', 'require-css', 'require-text', 'split-pane'],
    EXCLUDE_COMS = ['ace', 'ace-builds', 'gk-editor', 'jquery', 'jquery.gk', 'jqgrid', 'widget-ext', 'widget-jquerymobile'];

  function run(cb) {
    var self = this,
      url = self.data;
    if (isValidUrl(url)) {
      self.baseUrl = pathlib.dirname(url);
      request(url, function (error, response, body) {
        if (!error && response.statusCode === 200) {
          self.$ = cheerio.load(body);
          self._(parse)(function (path) {
            cb(null, path);
          });
        } else {
          cb({
            error: error,
            response: response
          });
        }
      });
    } else {
      self.$ = cheerio.load(url);
      self._(parse)(function (path) {
        cb(null, path)
      });
    }
  }

  function close() {
    var self = this;
    _.each(self.streamlist, function (ele) {
      ele.end();
    });
  }

  function parse(callback) {
    var self = this,
      $ = self.$,
      dstDir = self.options.dstDir,
      comDir = '',
      gkTags = [],
      downloadProc = function () {
        self.socket.emit('progress', 60);
        self._(fetchFile)(function () {
          fs.writeFileSync(dstDir + '/' + self.options.indexFile, self._(replaceHtml)());
          self._(packageAll)(callback);
        });
      };
    _.each(RES_TAG, function (tags, attr) {
      _.each(tags, function (tag) {
        _.each($(tag), function (ele, idx) {
          var url = ele.attribs[attr];
          if (url) {
            if (isValidUrl(url)) {
              var realUrl = self._(replaceWithLocal)(url);
              ele.attribs[attr] = realUrl;
            }
            self._(queueDownload)(ele, attr);
            if (ele.attribs['components']) {
              // keep in dstDir
              //                comDir = pathlib.normalize(dstDir + '/' + urllib.parse(url).pathname + '/../../');
              gkTags = ele.attribs['components'].split(/[\s,]+/);
            }
          }
        });
      });
    });
    if (fs.existsSync(dstDir)) {
      try {
        wrench.rmdirSyncRecursive(dstDir, true);
      } catch (e) {

      }
    }

    self.socket.emit('progress', 20);

    self._(copyTmpl)(function () {
      self.socket.emit('progress', 40);
      if (gkTags.length) {
        self._(copyCom)(comDir, function () {
          downloadProc();
        });
      } else {
        downloadProc();
      }
    });

  }

  function queueDownload(ele, attr) {
    var self = this;
    if (_.isString(ele)) {
      self.logger.log('debug', '[queued] ' + ele);
      self.tasklist.push({
        url: ele
      });
    } else {
      self.logger.log('debug', '[queued] ' + ele.attribs[attr]);
      self.tasklist.push({
        ele: ele,
        attr: attr
      });
    }
  }

  function copyTmpl(callback) {
    var self = this,
      srcDir = self.options.tmplDir,
      dstDir = self.options.dstDir;
    wrench.mkdirSyncRecursive(dstDir);
    self.logger.log('debug', '[copyTmpl] ' + srcDir + ' -> ' + dstDir);
    wrench.copyDirSyncRecursive(srcDir, dstDir, {
      forceDelete: true
    });
    callback();
  }

  function copyCom(comDir, callback) {
    var self = this,
      srcDir = self.options.baseDir,
      dstDir = comDir || self.options.dstDir,
      //          comJs = {
      //            pattern: 'com/*\.js',
      //            exclude: _.map(EXCLUDE_GKCOM, function (com) {
      //              return 'com/' + com + '.js';
      //            })
      //          },
      //          coms = {
      //            pattern: 'com/*[^\.js]',
      //            exclude: _.map(EXCLUDE_GKCOM, function (com) {
      //              return 'com/' + com;
      //            })
      //          },
      //          libs = {
      //            pattern: 'lib/*',
      //            exclude: _.map(EXCLUDE_GKLIB, function (lib) {
      //              return 'lib/' + lib;
      //            })
      //          },
      coms = {
        pattern: './*',
        exclude: _.map(EXCLUDE_COMS, function (com) {
          return './' + com;
        })
      };
    wrench.mkdirSyncRecursive(dstDir + '/components');
    _.each([coms], function (col) {
      scanDir(srcDir + '/components', col.pattern, col.exclude, function (err, matches) {
        _.each(matches, function (f) {
          var src = srcDir + '/components/' + f,
            dst = dstDir + '/components/' + f,
            isFile = fs.lstatSync(src).isFile();
          self.logger.log('debug', '[copyCom] ' + src + ' -> ' + dst);

          wrench.mkdirSyncRecursive(pathlib.dirname(dst));
          if (isFile) {
            fs.writeFileSync(dst, fs.readFileSync(src));
          } else {
            wrench.copyDirSyncRecursive(src, dst, {
              forceDelete: true
            });
          }
        });
      });
    });
    callback();
  }

  function fetchFile(callback) {
    var self = this,
      tasklist = self.tasklist,
      srcDir = self.options.baseDir,
      dstDir = self.options.dstDir,
      logger = self.logger,
      count = 0,
      checkFinish = function () {
        count++;
        if (count >= tasklist.length) {
          logger.log('debug', '[fetchFile] finish...');
          callback();
        }
      };
    _.each(tasklist, function (t, idx) {
      var url = t.ele.attribs[t.attr];
      if (isValidUrl(url)) {
        self._(download)(idx, url, dstDir, function (err, path) {
          err ? logger.log('error', '[download] failed: ' + url + ', err: ' + err) : (t.path = path);
          checkFinish();
        });
      } else if (url.indexOf('data') !== 0) {
        self._(copyLocal)(idx, srcDir + '/' + url, dstDir + '/' + url, dstDir, function (err, path) {
          err ? logger.log('error', '[copy] failed: ' + url + ', err: ' + err) : (t.path = pathlib.relative(dstDir, path));
          checkFinish();
        })
      } else {
        checkFinish();
      }
    });
  }

  function replaceHtml() {
    var self = this,
      tasklist = self.tasklist;
    _.each(tasklist, function (t) {
      if (t.ele && t.attr && t.path) {
        t.ele.attribs[t.attr] = t.path;
      }
    });
    return self.$.html();
  }

  function packageAll(callback) {
    var self = this;
    callback(self.options.dstDir);
  }

  return {
    constructor: Grabber,
    run: run,
    close: close,
    _: function (privateMethod) {
      var self = this;
      return function () {
        return privateMethod.apply(self, arguments);
      };
    }
  };

  function replaceWithLocal(url) {
    if (this.originUrl.test(url)) {
      return url.replace(this.originUrl, '');
    }
    return (USE_LOCAL_RES[url]) ? (USE_LOCAL_RES[url]) : url;
  }

  function copyLocal(sn, srcPath, dstPath, dstDir, callback) {
    try {
      wrench.mkdirSyncRecursive(pathlib.dirname(dstPath));
    } catch (e) {
      dstPath = dstDir + '/' + id + pathlib.extname(dstPath);
    }
    try {
      if (fs.existsSync(dstPath)) {
        this.logger.log('debug', '[copyLocal] skip ' + dstPath);
      } else {
        fs.writeFileSync(dstPath, fs.readFileSync(srcPath));
        this.logger.log('debug', '[copyLocal] ' + srcPath + ' -> ' + dstPath);
      }
      callback(null, dstPath);
    } catch (e) {
      callback(e);
    }
  }

  function download(sn, url, dstDir, callback) {
    var path = urllib.parse(url).pathname,
      id = hashids.encrypt(sn),
      logger = this.logger,
      streamlist = this.streamlist,
      filepath;
    request.head(url, function (error, response, body) {
      if (!error) {
        if (fs.existsSync(dstDir + '/' + path)) {
          filepath = dstDir + '/' + pathlib.dirname(path) + '/' + pathlib.basename(path, pathlib.extname(path)) + id + pathlib.extname(path);
        } else {
          filepath = dstDir + '/' + path;
        }
        filepath = pathlib.normalize(filepath);
        try {
          wrench.mkdirSyncRecursive(pathlib.dirname(filepath));
        } catch (e) {
          filepath = dstDir + '/' + id + pathlib.extname(path);
        }
        var ws = fs.createWriteStream(filepath);
        ws.on('finish', function () {
          streamlist.splice(streamlist.indexOf(ws), 1);
        });
        streamlist.push(ws);
        request(url).pipe(ws);
        logger.log('debug', '[download] ' + url + ' -> ' + filepath);
        callback(error, pathlib.relative(dstDir, filepath));
      } else {
        callback(error);
      }
    });
  }

  function scanDir(baseDir, pattern, excludes, callback) {
    glob(pattern, {
      cwd: baseDir
    }, function (err, matches) {
      callback(err, _.difference(matches, excludes));
    });
  }

  function isValidUrl(str) {
    var url = urllib.parse(str);
    return url.protocol && url.protocol.indexOf('http') === 0;
  }

}());

exports.Grabber = Grabber;
<commit_msg>skip polymer elements<commit_after>/*
 * grabber
 * https://github.com/ezoapp/grabber.git
 *
 * Copyright (c) 2014
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('underscore'),
  request = require('request'),
  fs = require('fs'),
  cheerio = require('cheerio'),
  urllib = require('url'),
  pathlib = require('path'),
  glob = require('glob'),
  wrench = require('wrench'),
  hashids = new(require('hashids'))(new Date().getTime() + 'T');

function Grabber(data, options, logger, originUrl, socket) {
  this.$ = null;
  this.data = data;
  this.baseUrl = '';
  this.tasklist = [];
  this.options = _.extend({
    dstDir: 'www',
    baseDir: '',
    tmplDir: '',
    indexFile: 'index.html'
  }, options || {});
  this.logger = (logger) ? logger : console;
  this.originUrl = new RegExp(originUrl, 'i');
  this.streamlist = [];
  this.socket = socket;
}

Grabber.prototype = (function () {

  var RES_TAG = {
    'href': ['link:not([rel="import"])'], //,'a'],
    'src': ['img', 'script'], //, 'iframe'],
    'data-background-image': ['a']
  },
    USE_LOCAL_RES = {
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile-1.4.2.min.js',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.external-png-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.external-png-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.icons-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.icons-1.4.2.min.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.inline-png-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.inline-png-1.4.2.min.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.inline-svg-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.inline-svg-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.structure-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.structure-1.4.2.min.css',
      'http://code.jquery.com/mobile/1.4.2/jquery.mobile.theme-1.4.2.min.css': 'gk/lib/jquery.mobile/1.4.2/jquery.mobile.theme-1.4.2.min.css'
      //          ,
      //          'http://jqmdesigner.appspot.com/1.4.2/gk/lib/jquery.mobile/1.4.2/flatui/jquery.mobile.flatui.css': 'gk/lib/jquery.mobile/1.4.2/flatui/jquery.mobile.flatui.css',
      //          'http://jqmdesigner.appspot.com/1.4.2/components/jquery.gk/jquery.gk.min.js': 'components/jquery.gk/jquery.gk.min.js',
      //          'http://jqmdesigner.appspot.com/1.4.2/components/gk-loader/gk-loader.js': 'components/gk-loader/gk-loader.js'
    },
      EXCLUDE_LINK = ['http://jqmdesigner.appspot.com/components/platform/platform.js'],
      EXCLUDE_GKCOM = ['jqgrid', 'editor', 'niceditor', 'tab-panel'],
      EXCLUDE_GKLIB = ['ace', 'ace-min', 'backbone', 'bootstrap', 'codemirror', 'gk', 'htmlparser', 'jquery', 'jquery.cookie', 'jquery.mobile', 'jquery.nicescroll', 'jquery.ui.tabs', 'jqgrid', 'miniscroll-js', 'nicedit', 'require', 'require-css', 'require-text', 'split-pane'],
      EXCLUDE_COMS = ['ace', 'ace-builds', 'gk-editor', 'jquery', 'jquery.gk', 'jqgrid', 'widget-ext', 'widget-jquerymobile',
        /* polymer componentes */
        "core-collapse", "core-component-page", "core-drawer-panel", "core-field", "core-header-panel", "core-icon", "core-icon-button", "core-icons", "core-iconset", "core-iconset-svg", "core-input", "core-item", "core-media-query", "core-menu", "core-menu-button", "core-meta", "core-overlay", "core-scaffold", "core-selection", "core-selector", "core-toolbar", "core-transition", "platform", "polymer", "voice-elements", "widget-polymer", "x-gif", "x-meme"];

  function run(cb) {
    var self = this,
      url = self.data;
    if (isValidUrl(url)) {
      self.baseUrl = pathlib.dirname(url);
      request(url, function (error, response, body) {
        if (!error && response.statusCode === 200) {
          self.$ = cheerio.load(body);
          self._(parse)(function (path) {
            cb(null, path);
          });
        } else {
          cb({
            error: error,
            response: response
          });
        }
      });
    } else {
      self.$ = cheerio.load(url);
      self._(parse)(function (path) {
        cb(null, path)
      });
    }
  }

  function close() {
    var self = this;
    _.each(self.streamlist, function (ele) {
      ele.end();
    });
  }

  function parse(callback) {
    var self = this,
      $ = self.$,
      dstDir = self.options.dstDir,
      comDir = '',
      gkTags = [],
      downloadProc = function () {
        self.socket.emit('progress', 60);
        self._(fetchFile)(function () {
          fs.writeFileSync(dstDir + '/' + self.options.indexFile, self._(replaceHtml)());
          self._(packageAll)(callback);
        });
      };
    _.each(RES_TAG, function (tags, attr) {
      _.each(tags, function (tag) {
        _.each($(tag), function (ele, idx) {
          var url = ele.attribs[attr];
          if (url && EXCLUDE_LINK.indexOf(url) < 0) {
            if (isValidUrl(url)) {
              var realUrl = self._(replaceWithLocal)(url);
              ele.attribs[attr] = realUrl;
            }
            self._(queueDownload)(ele, attr);
            if (ele.attribs['components']) {
              // keep in dstDir
              //                comDir = pathlib.normalize(dstDir + '/' + urllib.parse(url).pathname + '/../../');
              gkTags = ele.attribs['components'].split(/[\s,]+/);
            }
          }
        });
      });
    });
    if (fs.existsSync(dstDir)) {
      try {
        wrench.rmdirSyncRecursive(dstDir, true);
      } catch (e) {

      }
    }

    self.socket.emit('progress', 20);

    self._(copyTmpl)(function () {
      self.socket.emit('progress', 40);
      if (gkTags.length) {
        self._(copyCom)(comDir, function () {
          downloadProc();
        });
      } else {
        downloadProc();
      }
    });

  }

  function queueDownload(ele, attr) {
    var self = this;
    if (_.isString(ele)) {
      self.logger.log('debug', '[queued] ' + ele);
      self.tasklist.push({
        url: ele
      });
    } else {
      self.logger.log('debug', '[queued] ' + ele.attribs[attr]);
      self.tasklist.push({
        ele: ele,
        attr: attr
      });
    }
  }

  function copyTmpl(callback) {
    var self = this,
      srcDir = self.options.tmplDir,
      dstDir = self.options.dstDir;
    wrench.mkdirSyncRecursive(dstDir);
    self.logger.log('debug', '[copyTmpl] ' + srcDir + ' -> ' + dstDir);
    wrench.copyDirSyncRecursive(srcDir, dstDir, {
      forceDelete: true
    });
    callback();
  }

  function copyCom(comDir, callback) {
    var self = this,
      srcDir = self.options.baseDir,
      dstDir = comDir || self.options.dstDir,
      //          comJs = {
      //            pattern: 'com/*\.js',
      //            exclude: _.map(EXCLUDE_GKCOM, function (com) {
      //              return 'com/' + com + '.js';
      //            })
      //          },
      //          coms = {
      //            pattern: 'com/*[^\.js]',
      //            exclude: _.map(EXCLUDE_GKCOM, function (com) {
      //              return 'com/' + com;
      //            })
      //          },
      //          libs = {
      //            pattern: 'lib/*',
      //            exclude: _.map(EXCLUDE_GKLIB, function (lib) {
      //              return 'lib/' + lib;
      //            })
      //          },
      coms = {
        pattern: './*',
        exclude: _.map(EXCLUDE_COMS, function (com) {
          return './' + com;
        })
      };
    wrench.mkdirSyncRecursive(dstDir + '/components');
    _.each([coms], function (col) {
      scanDir(srcDir + '/components', col.pattern, col.exclude, function (err, matches) {
        _.each(matches, function (f) {
          var src = srcDir + '/components/' + f,
            dst = dstDir + '/components/' + f,
            isFile = fs.lstatSync(src).isFile();
          self.logger.log('debug', '[copyCom] ' + src + ' -> ' + dst);

          wrench.mkdirSyncRecursive(pathlib.dirname(dst));
          if (isFile) {
            fs.writeFileSync(dst, fs.readFileSync(src));
          } else {
            wrench.copyDirSyncRecursive(src, dst, {
              forceDelete: true
            });
          }
        });
      });
    });
    callback();
  }

  function fetchFile(callback) {
    var self = this,
      tasklist = self.tasklist,
      srcDir = self.options.baseDir,
      dstDir = self.options.dstDir,
      logger = self.logger,
      count = 0,
      checkFinish = function () {
        count++;
        if (count >= tasklist.length) {
          logger.log('debug', '[fetchFile] finish...');
          callback();
        }
      };
    _.each(tasklist, function (t, idx) {
      var url = t.ele.attribs[t.attr];
      if (isValidUrl(url)) {
        self._(download)(idx, url, dstDir, function (err, path) {
          err ? logger.log('error', '[download] failed: ' + url + ', err: ' + err) : (t.path = path);
          checkFinish();
        });
      } else if (url.indexOf('data') !== 0) {
        self._(copyLocal)(idx, srcDir + '/' + url, dstDir + '/' + url, dstDir, function (err, path) {
          err ? logger.log('error', '[copy] failed: ' + url + ', err: ' + err) : (t.path = pathlib.relative(dstDir, path));
          checkFinish();
        })
      } else {
        checkFinish();
      }
    });
  }

  function replaceHtml() {
    var self = this,
      tasklist = self.tasklist;
    _.each(tasklist, function (t) {
      if (t.ele && t.attr && t.path) {
        t.ele.attribs[t.attr] = t.path;
      }
    });
    return self.$.html();
  }

  function packageAll(callback) {
    var self = this;
    callback(self.options.dstDir);
  }

  return {
    constructor: Grabber,
    run: run,
    close: close,
    _: function (privateMethod) {
      var self = this;
      return function () {
        return privateMethod.apply(self, arguments);
      };
    }
  };

  function replaceWithLocal(url) {
    if (EXCLUDE_LINK.indexOf(url) > -1) {
      return url;
    }
    if (this.originUrl.test(url)) {
      return url.replace(this.originUrl, '');
    }
    return (USE_LOCAL_RES[url]) ? (USE_LOCAL_RES[url]) : url;
  }

  function copyLocal(sn, srcPath, dstPath, dstDir, callback) {
    try {
      wrench.mkdirSyncRecursive(pathlib.dirname(dstPath));
    } catch (e) {
      dstPath = dstDir + '/' + id + pathlib.extname(dstPath);
    }
    try {
      if (fs.existsSync(dstPath)) {
        this.logger.log('debug', '[copyLocal] skip ' + dstPath);
      } else {
        fs.writeFileSync(dstPath, fs.readFileSync(srcPath));
        this.logger.log('debug', '[copyLocal] ' + srcPath + ' -> ' + dstPath);
      }
      callback(null, dstPath);
    } catch (e) {
      callback(e);
    }
  }

  function download(sn, url, dstDir, callback) {
    var path = urllib.parse(url).pathname,
      id = hashids.encrypt(sn),
      logger = this.logger,
      streamlist = this.streamlist,
      filepath;
    request.head(url, function (error, response, body) {
      if (!error) {
        if (fs.existsSync(dstDir + '/' + path)) {
          filepath = dstDir + '/' + pathlib.dirname(path) + '/' + pathlib.basename(path, pathlib.extname(path)) + id + pathlib.extname(path);
        } else {
          filepath = dstDir + '/' + path;
        }
        filepath = pathlib.normalize(filepath);
        try {
          wrench.mkdirSyncRecursive(pathlib.dirname(filepath));
        } catch (e) {
          filepath = dstDir + '/' + id + pathlib.extname(path);
        }
        var ws = fs.createWriteStream(filepath);
        ws.on('finish', function () {
          streamlist.splice(streamlist.indexOf(ws), 1);
        });
        streamlist.push(ws);
        request(url).pipe(ws);
        logger.log('debug', '[download] ' + url + ' -> ' + filepath);
        callback(error, pathlib.relative(dstDir, filepath));
      } else {
        callback(error);
      }
    });
  }

  function scanDir(baseDir, pattern, excludes, callback) {
    glob(pattern, {
      cwd: baseDir
    }, function (err, matches) {
      callback(err, _.difference(matches, excludes));
    });
  }

  function isValidUrl(str) {
    var url = urllib.parse(str);
    return url.protocol && url.protocol.indexOf('http') === 0;
  }

}());

exports.Grabber = Grabber;
<|endoftext|>


<commit_before>'use strict';

var IsEqual = require('./matchers/IsEqual')
	, SubstringMatcher = require('./matchers/SubstringMatcher')
	;

var hamjest = {
	assertThat: require('./assertThat'),
	isMatcher: require('./matchers/Matcher').isMatcher,
	anything: require('./matchers/IsAnything').anything,
	strictlyEqualTo: require('./matchers/IsSame').strictlyEqualTo,
	is: require('./matchers/Is').is,
	not: require('./matchers/IsNot').not,
	equalTo: IsEqual.equalTo,
	asMatcher: IsEqual.asMatcher,
	acceptingMatcher: IsEqual.acceptingMatcher,
	instanceOf: require('./matchers/IsInstanceOf').instanceOf,
	containsString: SubstringMatcher.containsString,
	startsWith: SubstringMatcher.startsWith,
	endsWith: SubstringMatcher.endsWith,
	allOf: require('./matchers/AllOf').allOf,
	anyOf: require('./matchers/AnyOf').anyOf,
	promiseThat: require('./promiseThat'),
	promise: require('./matchers/IsPromise').promise,
	fulfilled: require('./matchers/IsFulfilled').fulfilled,
	rejected: require('./matchers/IsRejected').rejected
};

module.exports = hamjest;
<commit_msg>Add type matchers to hamjest<commit_after>'use strict';

var IsEqual = require('./matchers/IsEqual')
	, SubstringMatcher = require('./matchers/SubstringMatcher')
	;

var hamjest = {
	assertThat: require('./assertThat'),
	isMatcher: require('./matchers/Matcher').isMatcher,
	anything: require('./matchers/IsAnything').anything,
	strictlyEqualTo: require('./matchers/IsSame').strictlyEqualTo,
	is: require('./matchers/Is').is,
	not: require('./matchers/IsNot').not,
	equalTo: IsEqual.equalTo,
	asMatcher: IsEqual.asMatcher,
	acceptingMatcher: IsEqual.acceptingMatcher,
	instanceOf: require('./matchers/IsInstanceOf').instanceOf,
	array: require('./matchers/IsArray').array,
	bool: require('./matchers/IsBoolean').bool,
	date: require('./matchers/IsDate').date,
	func: require('./matchers/IsFunction').func,
	number: require('./matchers/IsNumber').number,
	object: require('./matchers/IsObject').object,
	regExp: require('./matchers/IsRegExp').regExp,
	string: require('./matchers/IsString').string,
	containsString: SubstringMatcher.containsString,
	startsWith: SubstringMatcher.startsWith,
	endsWith: SubstringMatcher.endsWith,
	allOf: require('./matchers/AllOf').allOf,
	anyOf: require('./matchers/AnyOf').anyOf,
	promiseThat: require('./promiseThat'),
	promise: require('./matchers/IsPromise').promise,
	fulfilled: require('./matchers/IsFulfilled').fulfilled,
	rejected: require('./matchers/IsRejected').rejected
};

module.exports = hamjest;
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var parser = require('./parser');
var mime = require('mime');

var webstack = [];

exports = module.exports = handler = {};

handler.webstack = webstack;

handler.handle = function(req, res, getbody) {
var layercount = -1;
var pathurl = parser.parsepath(req.url).path;
var method = req.method;
var fileStream;
var body = '';
req.query = parser.parsepath(req.url).query;

if (getbody) {
	req.setEncoding = 'utf8';
	req.on('data', function(chunk) {
		body += chunk;
	})
	req.on('end', function() {
		req.body = parser.parse(body, req.headers['content-type']);
	});
}

return function cb(err) {++layercount;
	if (err) {
		throw err
	}
	if (layercount < webstack.length) {
		switch(webstack[layercount].type) {
		case 'middleware':
			webstack[layercount].callback(req, res, cb);
			break;
		case 'static':
			var filename = path.join(webstack[layercount].dir, req.url);
			fs.exists(filename, function(exists) {
				if (exists) {
					if (fs.statSync(filename).isFile()) {
						if (method == 'GET') {
							fileStream = fs.createReadStream(filename);
							fileStream.pipe(res);
							fileStream.on('error', cb);
						} else if (method == 'HEAD') {
							res.end(200, {
								'content-type' : mime.lookup(filename)
							});
						} else {
							cb();
						}
					} else {
						cb();
					}
				} else {
					cb();
				}
			});
			break;
		case 'api':
			if (webstack[layercount].path === pathurl && webstack[layercount].method === method) {
				webstack[layercount].callback(req, res);
			} else if (webstack[layercount].method === method) {
				if (parser.parseParams(req, webstack[layercount].path, pathurl)) {
					webstack[layercount].callback(req, res);
				} else {
					cb();
				}
			} else {
				cb();
			}
			break;
		case 'default':
			cb()
		}
	} else {
		res.end("can't " + method + " the route " + req.url);
	}
    }
};
<commit_msg>code formatting<commit_after>var fs = require('fs');
var path = require('path');
var parser = require('./parser');
var mime = require('mime');
var webstack = [];

exports = module.exports = handler = {};

handler.webstack = webstack;

handler.handle = function(req, res, getbody) {
var layercount = -1;
var pathurl = parser.parsepath(req.url).path;
var method = req.method;
var fileStream;
var body = '';
req.query = parser.parsepath(req.url).query;

if (getbody) {
	req.setEncoding = 'utf8';
	req.on('data', function(chunk) {
		body += chunk;
	})
	req.on('end', function() {
		req.body = parser.parse(body, req.headers['content-type']);
	});
}

return function cb(err) {++layercount;
	if (err) {
		throw err
	}
	if (layercount < webstack.length) {
		switch(webstack[layercount].type) {
		case 'middleware':
			webstack[layercount].callback(req, res, cb);
			break;
		case 'static':
			var filename = path.join(webstack[layercount].dir, req.url);
			fs.exists(filename, function(exists) {
				if (exists) {
					if (fs.statSync(filename).isFile()) {
						if (method == 'GET') {
							fileStream = fs.createReadStream(filename);
							fileStream.pipe(res);
							fileStream.on('error', cb);
						} else if (method == 'HEAD') {
							res.end(200, {
								'content-type' : mime.lookup(filename)
							});
						} else {
							cb();
						}
					} else {
						cb();
					}
				} else {
					cb();
				}
			});
			break;
		case 'api':
			if (webstack[layercount].path === pathurl && webstack[layercount].method === method) {
				webstack[layercount].callback(req, res);
			} else if (webstack[layercount].method === method) {
				if (parser.parseParams(req, webstack[layercount].path, pathurl)) {
					webstack[layercount].callback(req, res);
				} else {
					cb();
				}
			} else {
				cb();
			}
			break;
		case 'default':
			cb()
		}
	} else {
		res.end("can't " + method + " the route " + req.url);
	}
    }
};
<|endoftext|>


<commit_before>const { BrowserPool, PlaywrightPlugin } = require('browser-pool');
const { chromium } = require('playwright-core');
const debug = require('debug')('express-dom');
const clone = require('clone');

const plugins = require('./plugins');
const helpers = require('./helpers');
const tracker = require('./tracker');
const Deferred = require('./deferred');
const asyncEmit = require('./async-emit');

let browserPool;

class RequestTracker extends Deferred {
	#count = 0;

	constructor(page) {
		super();
		page.on('request', () => this.creates());
		page.on('requestfinished', () => this.completes());
		page.on('requestfailed', () => this.completes());
	}
	creates() {
		this.#count++;
	}
	completes() {
		if (--this.#count == 0) setImmediate(this.resolve);
	}
}

module.exports = class Handler {
	static pool = {
		max: 20
	};
	static plugins = plugins;
	static helpers = helpers;
	static settings = {
		helpers: [
			helpers.view,
			helpers.prioritize
		],
		prepare: {
			plugins: [
				plugins.hide,
				plugins.noreq,
				plugins.html
			]
		},
		load: {
			disable: Boolean(process.env.DEVELOP),
			plugins: [
				plugins.hide,
				plugins.nomedia,
				plugins.prerender,
				plugins.redirect,
				plugins.referrer,
				plugins.html
			]
		},
		console: process.env.NODE_ENV != "production",
		stall: 15000,
		allow: "same-origin",
		navigation: false,
		verbose: true
	};

	constructor(view, ...helpers) {
		const h = this;
		if (typeof view == "function") {
			helpers.unshift(view);
			view = null;
		} else {
			h.view = view;
		}
		debug("new handler", view);
		h.settings = emptySettings(Handler.settings);
		h.settings.helpers = helpers.concat(Handler.settings.helpers);
		h.settings.prepare = emptySettings(Handler.settings, true);
		h.settings.load = emptySettings(Handler.settings, true);

		h.chain = (...args) => h.middleware(...args);
		h.chain.load = (...args) => h.load(...args);
		h.chain.prepare = (...args) => h.prepare(...args);
	}
	#init(settings, defaults, opts, args) {
		if (!browserPool) browserPool = new BrowserPool({
			browserPlugins: [new PlaywrightPlugin(chromium, {
				maxOpenPagesPerBrowser: Handler.pool.max,
				retireBrowserAfterPageCount: Handler.pool.maxloads,
				useIncognitoPages: true, // each page can have its cookies
				launchOptions: {
					channel: 'chrome',
					timeout: 5000
				}
			})],
		});
		if (typeof opts == "function") opts = null;
		else args.shift();
		let plugins = settings.plugins;
		Object.assign(settings, defaults, opts);
		if ((!opts || !opts.plugins) && plugins.length) settings.plugins = plugins;
		else plugins = settings.plugins;
		if (typeof plugins == "function") plugins = [plugins];
		settings.plugins = args.concat(plugins);
	}

	static async destroy() {
		await browserPool.destroy();
		browserPool = null;
	}
	clone() {
		const h = new Handler(this.view);
		h.settings = clone(this.settings);
		return h;
	}
	prepare(opts) {
		this.#init(
			this.settings.prepare,
			Handler.settings.prepare,
			opts,
			Array.from(arguments)
		);
		return this.chain;
	}

	load(opts) {
		this.#init(
			this.settings.load,
			Handler.settings.load,
			opts,
			Array.from(arguments)
		);
		return this.chain;
	}
	async middleware(req, res, next) {
		const manual = {};
		if (typeof req == "string") {
			debug("Called in manual mode");
			const loc = new URL(req);
			req = {
				headers: {
					host: loc.host
				},
				protocol: loc.protocol,
				url: loc.path
			};
			req.get = function (key) {
				return this.headers[key];
			}.bind(req);
			res = { statusCode: 200 };
			res.sendStatus = function (status) {
				res.statusCode = manual.status = status;
			};
			res.send = function (str) {
				manual.body = str;
			};
			res.status = res.sendStatus;
			req.res = res;
		}
		// per request settings
		const rh = this.clone();
		try {
			await rh.runMiddleware(req, res);
			if (!next) {
				return manual;
			}
		} catch (err) {
			if (next) next(err);
			else throw err;
		}
	}

	async runMiddleware(req, res) {
		const { settings } = this;

		const loc = settings.location = new URL(
			req.url,
			(req.get('X-Forwarded-Proto') ?? req.protocol) + '://' + req.headers.host
		);
		if (req.headers.cookie) loc.headers = {
			cookie: req.headers.cookie
		};

		for (const helper of settings.helpers) {
			await helper(this, settings, req, res);
		}

		debug('view loaded for', loc.pathname, "with", settings.prepare.plugins.length, "prepare plugins, and", settings.load.plugins.length, "load plugins");

		if (!settings.prepare.disable && settings.prepare.plugins.length > 0) {
			await this.runMethod('prepare', settings, req, res);
		}

		if (!settings.load.disable && settings.load.plugins.length > 0) {
			await this.runMethod('load', settings, req, res);
		}

		let data = settings.input;
		// hopefully free faster that memory
		delete settings.input;
		if (res.get && !res.get('Content-Type')) {
			res.set('Content-Type', "text/html");
		}
		let status = res.statusCode;
		if (data === false) {
			debug("data handled by plugin");
			return;
		}
		if (!data) {
			if (!settings.prepare && !settings.load) {
				return res.sendStatus(501);
			} else {
				return res.sendStatus(404);
			}
		}
		let isPath = true, isStream = false;
		if (data instanceof Error) {
			isPath = false;
			data = null;
		} else if (Buffer.isBuffer(data)) {
			isPath = false;
		} else if (typeof data.pipe == "function") {
			isPath = false;
			isStream = true;
		} else if (typeof data != "string") {
			data = data.toString();
		}
		if (isPath && data.startsWith('<')) {
			isPath = false;
		}
		if (isPath) {
			try {
				res.sendFile(data);
			} catch (ex) {
				// eslint-disable-next-line no-console
				console.error("express-dom thought input was a file path", data);
				status = 500;
				isPath = false;
			}
		}
		if (!isPath) {
			if (status) res.status(status);
			if (isStream) {
				data.once('error', (err) => {
					res.emit('error', err);
					data.unpipe(res);
				});
				data.pipe(res);
			} else {
				res.send(data);
			}
		}
	}

	async runMethod(method, settings, req, res) {
		const page = await browserPool.newPage();
		asyncEmit(page);
		const mSettings = Object.assign({}, settings, settings[method]);
		mSettings.location = settings.location;
		const plugins = mSettings.plugins;
		delete mSettings.plugins;
		for (const plugin of plugins) {
			await plugin(page, mSettings, req, res);
		}

		settings.location = mSettings.location;
		settings.input = mSettings.input;
		delete mSettings.location;
		delete mSettings.input;
		const url = settings.location.toString();
		debug(method, url);

		page.route(url, route => {
			route.fulfill({
				body: settings.input,
				contentType: 'text/html'
			});
		});

		if (mSettings.verbose) page.on('console', msg => {
			// eslint-disable-next-line no-console
			console[msg.type()](msg.text());
		});
		page.on('pageerror', err => {
			res.statusCode = 500;
			settings.input = err;
			if (mSettings.verbose) console.error(err);
		});
		const reqTrack = new RequestTracker(page);

		const fnid = 'signal' + Math.floor(Math.random() * 10000000);
		await page.addInitScript(tracker, {
			id: fnid,
			stall: mSettings.stall
		});
		try {
			await page.goto(url, {
				waitUntil: 'domcontentloaded'
			});
			debug("page loaded");
			await reqTrack;
			debug("page requests finished");
			const event = await page.evaluate(id => window[id], fnid);
			debug("page idle");
			if (event == "idle") {
				await asyncEmit(page, 'idle');
				debug("async emit done");
			} else {
				console.warn("Unknown event", event);
			}
		} catch (ex) {
			console.error(ex);
		}

		if (mSettings.output !== undefined) {
			settings.input = mSettings.output;
		}
		await page.close();
	}
};

function emptySettings(defaults, withArrays) {
	// use defaults but not .load, .prepare sub global settings
	let obj = Object.assign({}, defaults);
	delete obj.load;
	delete obj.prepare;
	obj = clone(obj);
	if (withArrays) {
		if (!obj.filters) obj.filters = [];
		if (!obj.plugins) obj.plugins = [];
		if (!obj.scripts) obj.scripts = [];
	}
	return obj;
}

<commit_msg>handler: use randomUUID<commit_after>const { BrowserPool, PlaywrightPlugin } = require('browser-pool');
const { chromium } = require('playwright-core');
const debug = require('debug')('express-dom');
const clone = require('clone');
const { randomUUID } = require('crypto');

const plugins = require('./plugins');
const helpers = require('./helpers');
const tracker = require('./tracker');
const Deferred = require('./deferred');
const asyncEmit = require('./async-emit');

let browserPool;

class RequestTracker extends Deferred {
	#count = 0;

	constructor(page) {
		super();
		page.on('request', () => this.creates());
		page.on('requestfinished', () => this.completes());
		page.on('requestfailed', () => this.completes());
	}
	creates() {
		this.#count++;
	}
	completes() {
		if (--this.#count == 0) setImmediate(this.resolve);
	}
}

module.exports = class Handler {
	static pool = {
		max: 20
	};
	static plugins = plugins;
	static helpers = helpers;
	static settings = {
		helpers: [
			helpers.view,
			helpers.prioritize
		],
		prepare: {
			plugins: [
				plugins.hide,
				plugins.noreq,
				plugins.html
			]
		},
		load: {
			disable: Boolean(process.env.DEVELOP),
			plugins: [
				plugins.hide,
				plugins.nomedia,
				plugins.prerender,
				plugins.redirect,
				plugins.referrer,
				plugins.html
			]
		},
		console: process.env.NODE_ENV != "production",
		stall: 15000,
		allow: "same-origin",
		navigation: false,
		verbose: true
	};

	constructor(view, ...helpers) {
		const h = this;
		if (typeof view == "function") {
			helpers.unshift(view);
			view = null;
		} else {
			h.view = view;
		}
		debug("new handler", view);
		h.settings = emptySettings(Handler.settings);
		h.settings.helpers = helpers.concat(Handler.settings.helpers);
		h.settings.prepare = emptySettings(Handler.settings, true);
		h.settings.load = emptySettings(Handler.settings, true);

		h.chain = (...args) => h.middleware(...args);
		h.chain.load = (...args) => h.load(...args);
		h.chain.prepare = (...args) => h.prepare(...args);
	}
	#init(settings, defaults, opts, args) {
		if (!browserPool) browserPool = new BrowserPool({
			browserPlugins: [new PlaywrightPlugin(chromium, {
				maxOpenPagesPerBrowser: Handler.pool.max,
				retireBrowserAfterPageCount: Handler.pool.maxloads,
				useIncognitoPages: true, // each page can have its cookies
				launchOptions: {
					channel: 'chrome',
					timeout: 5000
				}
			})],
		});
		if (typeof opts == "function") opts = null;
		else args.shift();
		let plugins = settings.plugins;
		Object.assign(settings, defaults, opts);
		if ((!opts || !opts.plugins) && plugins.length) settings.plugins = plugins;
		else plugins = settings.plugins;
		if (typeof plugins == "function") plugins = [plugins];
		settings.plugins = args.concat(plugins);
	}

	static async destroy() {
		await browserPool.destroy();
		browserPool = null;
	}
	clone() {
		const h = new Handler(this.view);
		h.settings = clone(this.settings);
		return h;
	}
	prepare(opts) {
		this.#init(
			this.settings.prepare,
			Handler.settings.prepare,
			opts,
			Array.from(arguments)
		);
		return this.chain;
	}

	load(opts) {
		this.#init(
			this.settings.load,
			Handler.settings.load,
			opts,
			Array.from(arguments)
		);
		return this.chain;
	}
	async middleware(req, res, next) {
		const manual = {};
		if (typeof req == "string") {
			debug("Called in manual mode");
			const loc = new URL(req);
			req = {
				headers: {
					host: loc.host
				},
				protocol: loc.protocol,
				url: loc.path
			};
			req.get = function (key) {
				return this.headers[key];
			}.bind(req);
			res = { statusCode: 200 };
			res.sendStatus = function (status) {
				res.statusCode = manual.status = status;
			};
			res.send = function (str) {
				manual.body = str;
			};
			res.status = res.sendStatus;
			req.res = res;
		}
		// per request settings
		const rh = this.clone();
		try {
			await rh.runMiddleware(req, res);
			if (!next) {
				return manual;
			}
		} catch (err) {
			if (next) next(err);
			else throw err;
		}
	}

	async runMiddleware(req, res) {
		const { settings } = this;

		const loc = settings.location = new URL(
			req.url,
			(req.get('X-Forwarded-Proto') ?? req.protocol) + '://' + req.headers.host
		);
		if (req.headers.cookie) loc.headers = {
			cookie: req.headers.cookie
		};

		for (const helper of settings.helpers) {
			await helper(this, settings, req, res);
		}

		debug('view loaded for', loc.pathname, "with", settings.prepare.plugins.length, "prepare plugins, and", settings.load.plugins.length, "load plugins");

		if (!settings.prepare.disable && settings.prepare.plugins.length > 0) {
			await this.runMethod('prepare', settings, req, res);
		}

		if (!settings.load.disable && settings.load.plugins.length > 0) {
			await this.runMethod('load', settings, req, res);
		}

		let data = settings.input;
		// hopefully free faster that memory
		delete settings.input;
		if (res.get && !res.get('Content-Type')) {
			res.set('Content-Type', "text/html");
		}
		let status = res.statusCode;
		if (data === false) {
			debug("data handled by plugin");
			return;
		}
		if (!data) {
			if (!settings.prepare && !settings.load) {
				return res.sendStatus(501);
			} else {
				return res.sendStatus(404);
			}
		}
		let isPath = true, isStream = false;
		if (data instanceof Error) {
			isPath = false;
			data = null;
		} else if (Buffer.isBuffer(data)) {
			isPath = false;
		} else if (typeof data.pipe == "function") {
			isPath = false;
			isStream = true;
		} else if (typeof data != "string") {
			data = data.toString();
		}
		if (isPath && data.startsWith('<')) {
			isPath = false;
		}
		if (isPath) {
			try {
				res.sendFile(data);
			} catch (ex) {
				// eslint-disable-next-line no-console
				console.error("express-dom thought input was a file path", data);
				status = 500;
				isPath = false;
			}
		}
		if (!isPath) {
			if (status) res.status(status);
			if (isStream) {
				data.once('error', (err) => {
					res.emit('error', err);
					data.unpipe(res);
				});
				data.pipe(res);
			} else {
				res.send(data);
			}
		}
	}

	async runMethod(method, settings, req, res) {
		const page = await browserPool.newPage();
		asyncEmit(page);
		const mSettings = Object.assign({}, settings, settings[method]);
		mSettings.location = settings.location;
		const plugins = mSettings.plugins;
		delete mSettings.plugins;
		for (const plugin of plugins) {
			await plugin(page, mSettings, req, res);
		}

		settings.location = mSettings.location;
		settings.input = mSettings.input;
		delete mSettings.location;
		delete mSettings.input;
		const url = settings.location.toString();
		debug(method, url);

		page.route(url, route => {
			route.fulfill({
				body: settings.input,
				contentType: 'text/html'
			});
		});

		if (mSettings.verbose) page.on('console', msg => {
			// eslint-disable-next-line no-console
			console[msg.type()](msg.text());
		});
		page.on('pageerror', err => {
			res.statusCode = 500;
			settings.input = err;
			if (mSettings.verbose) console.error(err);
		});
		const reqTrack = new RequestTracker(page);

		const fnid = 'signal_' + randomUUID();
		await page.addInitScript(tracker, {
			id: fnid,
			stall: mSettings.stall
		});
		try {
			await page.goto(url, {
				waitUntil: 'domcontentloaded'
			});
			debug("page loaded");
			await reqTrack;
			debug("page requests finished");
			const event = await page.evaluate(id => window[id], fnid);
			debug("page idle");
			if (event == "idle") {
				await asyncEmit(page, 'idle');
				debug("async emit done");
			} else {
				console.warn("Unknown event", event);
			}
		} catch (ex) {
			console.error(ex);
		}

		if (mSettings.output !== undefined) {
			settings.input = mSettings.output;
		}
		await page.close();
	}
};

function emptySettings(defaults, withArrays) {
	// use defaults but not .load, .prepare sub global settings
	let obj = Object.assign({}, defaults);
	delete obj.load;
	delete obj.prepare;
	obj = clone(obj);
	if (withArrays) {
		if (!obj.filters) obj.filters = [];
		if (!obj.plugins) obj.plugins = [];
		if (!obj.scripts) obj.scripts = [];
	}
	return obj;
}

<|endoftext|>


<commit_before>"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.swapArrayElements = swapArrayElements;
exports.isMouseBeyond = isMouseBeyond;
/*** Helper functions - they are decoupled because of testability */

/**
 * @param {array} items
 * @param {number} indexFrom
 * @param {number} indexTo
 * @returns {array}
 */
function swapArrayElements(items, indexFrom, indexTo) {
  var item = items[indexTo];
  items[indexTo] = items[indexFrom];
  items[indexFrom] = item;
  return items;
}

/**
 * @param {number} mousePos
 * @param {number} elementPos
 * @param {number} elementSize
 * @returns {boolean}
 */
function isMouseBeyond(mousePos, elementPos, elementSize) {
  //TODO refactor for UP
  var breakPoint = elementSize / 2; //break point is set to the middle line of element
  var mouseOverlap = mousePos - elementPos;
  return mouseOverlap > breakPoint;
}<commit_msg>fix breakpoint settings<commit_after>"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.swapArrayElements = swapArrayElements;
exports.isMouseBeyond = isMouseBeyond;
/*** Helper functions - they are decoupled because of testability */

/**
 * @param {array} items
 * @param {number} indexFrom
 * @param {number} indexTo
 * @returns {array}
 */
function swapArrayElements(items, indexFrom, indexTo) {
  var item = items[indexTo];
  items[indexTo] = items[indexFrom];
  items[indexFrom] = item;
  return items;
}

/**
 * @param {number} mousePos
 * @param {number} elementPos
 * @param {number} elementSize
 * @returns {boolean}
 */
function isMouseBeyond(mousePos, elementPos, elementSize, moveInMiddle) {
  var breakPoint;
  if (moveInMiddle) {
    breakPoint = elementSize / 2; //break point is set to the middle line of element
  } else {
    breakPoint = 0;
  }
  var mouseOverlap = mousePos - elementPos;
  return mouseOverlap > breakPoint;
}<|endoftext|>


<commit_before>var express = require('express');
var pathUtil = require('path');

exports.notifyLivereload = function( basePath, livereload, event ) {

    // `gulp.watch()` events provide an absolute path
    // so we need to make it relative to the server root
    var fileName = pathUtil.relative( basePath.build, event.path );

    livereload.changed( fileName );
};

exports.startExpress = function( basePath, port ) {

    port = port || 4000;

    var app = express();

    app.use(express.static( basePath ));
    app.listen( port );

    console.log('express server started on localhost:'+ port );
};<commit_msg>full basePath given as an argument, not an object<commit_after>var express = require('express');
var pathUtil = require('path');

exports.notifyLivereload = function( basePath, livereload, event ) {

    // `gulp.watch()` events provide an absolute path
    // so we need to make it relative to the server root
    var fileName = pathUtil.relative( basePath, event.path );

    livereload.changed( fileName );
};

exports.startExpress = function( basePath, port ) {

    port = port || 4000;

    var app = express();

    app.use(express.static( basePath ));
    app.listen( port );

    console.log('express server started on localhost:'+ port );
};<|endoftext|>


<commit_before>/* eslint no-console: 0 */

// Helpers
// ---------------
'use strict'

const _     = require('lodash');
const chalk = require('chalk')

const helpers = {

  // Sets the constraints necessary during a `model.save` call.
  saveConstraints: function(model, relatedData) {
    const data = {};
    if (relatedData
        && !relatedData.isThrough()
        && relatedData.type !== 'belongsToMany'
        && relatedData.type !== 'belongsTo'
    ) {
      data[relatedData.key('foreignKey')] = relatedData.parentFk || model.get(relatedData.key('foreignKey'));
      if (relatedData.isMorph()) data[relatedData.key('morphKey')] = relatedData.key('morphValue');
    }
    return model.set(model.parse(data));
  },

  // Finds the specific `morphTo` target Model we should be working with, or throws
  // an error if none is matched.
  morphCandidate: function(candidates, morphValue) {
    const Target = _.find(candidates, (candidate) => candidate[1] === morphValue);

    if (!Target)
      throw new Error('The target polymorphic type "' + morphValue + '" is not one of the defined target types');

    return Target[0];
  },

  // If there are no arguments, return the current object's
  // query builder (or create and return a new one). If there are arguments,
  // call the query builder with the first argument, applying the rest.
  // If the first argument is an object, assume the keys are query builder
  // methods, and the values are the arguments for the query.
  query: function(obj, args) {

    // Ensure the object has a query builder.
    if (!obj._knex) {
      const tableName = _.result(obj, 'tableName');
      obj._knex = obj._builder(tableName);
    }

    // If there are no arguments, return the query builder.
    if (args.length === 0) return obj._knex;

    const method = args[0];

    if (_.isFunction(method)) {

      // `method` is a query builder callback. Call it on the query builder
      // object.
      method.call(obj._knex, obj._knex);
    } else if (_.isObject(method)) {

      // `method` is an object. Use keys as methods and values as arguments to
      // the query builder.
      for (const key in method) {
        const target = _.isArray(method[key]) ?  method[key] : [method[key]];
        obj._knex[key].apply(obj._knex, target);
      }
    } else {

      // Otherwise assume that the `method` is string name of a query builder
      // method, and use the remaining args as arguments to that method.
      obj._knex[method].apply(obj._knex, args.slice(1));
    }
    return obj;
  },

  error: function(msg) {
    console.log(chalk.red(msg))
  },

  warn: function(msg) {
    console.log(chalk.yellow(msg))
  },

  deprecate: function(a, b) {
    helpers.warn(a + ' has been deprecated, please use ' + b + ' instead')
  },

  orderBy (obj, sort, order) {

    let tableName;
    let idAttribute;

    if (obj.model) {
      tableName = obj.model.prototype.tableName;
      idAttribute = obj.model.prototype.idAttribute ?
        obj.model.prototype.idAttribute : 'id';
    } else {
      tableName = obj.constructor.prototype.tableName;
      idAttribute = obj.constructor.prototype.idAttribute ?
        obj.constructor.prototype.idAttribute : 'id';
    }

    let _sort;

    if (sort && sort.indexOf('-') === 0) {
      _sort = sort.slice(1);
    } else if (sort) {
      _sort = sort;
    } else {
      _sort = idAttribute;
    }

    const _order = order || (
      (sort && sort.indexOf('-') === 0) ? 'DESC' : 'ASC'
    );

    if (_sort.indexOf('.') === -1) {
      _sort = `${tableName}.${_sort}`;
    }

    return obj.query(qb => {
      qb.orderBy(_sort, _order);
    });
  }

};

module.exports = helpers
<commit_msg>Remove unused helper method<commit_after>/* eslint no-console: 0 */

// Helpers
// ---------------
'use strict'

const _     = require('lodash');
const chalk = require('chalk')

const helpers = {

  // Sets the constraints necessary during a `model.save` call.
  saveConstraints: function(model, relatedData) {
    const data = {};
    if (relatedData
        && !relatedData.isThrough()
        && relatedData.type !== 'belongsToMany'
        && relatedData.type !== 'belongsTo'
    ) {
      data[relatedData.key('foreignKey')] = relatedData.parentFk || model.get(relatedData.key('foreignKey'));
      if (relatedData.isMorph()) data[relatedData.key('morphKey')] = relatedData.key('morphValue');
    }
    return model.set(model.parse(data));
  },

  // Finds the specific `morphTo` target Model we should be working with, or throws
  // an error if none is matched.
  morphCandidate: function(candidates, morphValue) {
    const Target = _.find(candidates, (candidate) => candidate[1] === morphValue);

    if (!Target)
      throw new Error('The target polymorphic type "' + morphValue + '" is not one of the defined target types');

    return Target[0];
  },

  // If there are no arguments, return the current object's
  // query builder (or create and return a new one). If there are arguments,
  // call the query builder with the first argument, applying the rest.
  // If the first argument is an object, assume the keys are query builder
  // methods, and the values are the arguments for the query.
  query: function(obj, args) {

    // Ensure the object has a query builder.
    if (!obj._knex) {
      const tableName = _.result(obj, 'tableName');
      obj._knex = obj._builder(tableName);
    }

    // If there are no arguments, return the query builder.
    if (args.length === 0) return obj._knex;

    const method = args[0];

    if (_.isFunction(method)) {

      // `method` is a query builder callback. Call it on the query builder
      // object.
      method.call(obj._knex, obj._knex);
    } else if (_.isObject(method)) {

      // `method` is an object. Use keys as methods and values as arguments to
      // the query builder.
      for (const key in method) {
        const target = _.isArray(method[key]) ?  method[key] : [method[key]];
        obj._knex[key].apply(obj._knex, target);
      }
    } else {
      // Otherwise assume that the `method` is string name of a query builder
      // method, and use the remaining args as arguments to that method.
      obj._knex[method].apply(obj._knex, args.slice(1));
    }

    return obj;
  },

  warn: function(msg) {
    console.log(chalk.yellow(msg))
  },

  deprecate: function(a, b) {
    helpers.warn(a + ' has been deprecated, please use ' + b + ' instead')
  },

  orderBy (obj, sort, order) {

    let tableName;
    let idAttribute;

    if (obj.model) {
      tableName = obj.model.prototype.tableName;
      idAttribute = obj.model.prototype.idAttribute ?
        obj.model.prototype.idAttribute : 'id';
    } else {
      tableName = obj.constructor.prototype.tableName;
      idAttribute = obj.constructor.prototype.idAttribute ?
        obj.constructor.prototype.idAttribute : 'id';
    }

    let _sort;

    if (sort && sort.indexOf('-') === 0) {
      _sort = sort.slice(1);
    } else if (sort) {
      _sort = sort;
    } else {
      _sort = idAttribute;
    }

    const _order = order || (
      (sort && sort.indexOf('-') === 0) ? 'DESC' : 'ASC'
    );

    if (_sort.indexOf('.') === -1) {
      _sort = `${tableName}.${_sort}`;
    }

    return obj.query(qb => {
      qb.orderBy(_sort, _order);
    });
  }

};

module.exports = helpers
<|endoftext|>