<commit_before>define('isr/player', ['jquery', 'isr', 'isr/config', 'isr/entity'], function($, Game, Config, Entity) {
   var $activeScene;
   var $activeCharacter;
   var scenePlayer = {
      'church' : {'x': 5, 'y' : 8},
      'family' : {'x': 5, 'y' : 8},
      'temple-1' : {'x': 5, 'y' : 8},
      'temple-2' : {'x': 5, 'y' : 8},
      'war-1' : {'x': 0, 'y' : 3},
      'war-2' : {'x': 0, 'y' : 3}
   };
   // current player position
   var playerMovState = {
      'x' : 0,
      'y' : 2,
      'facing' : 'up',
      // var to indicate if character is currently moving
      'moving' : false,
      'level' : 0
   };
   var lives = 3;
   var startTime = 0;

   Game.director.on('scene:change', function(sceneName) {
      // save current player pos to scenePlayer
      if ($activeScene) {
         scenePlayer[$activeScene[0].id].x = playerMovState.x;
         scenePlayer[$activeScene[0].id].y = playerMovState.y;
      }
      console.log('switched scene to ' +sceneName);
      $activeScene = $('#' + sceneName);
      $activeCharacter = $activeScene.find('.character');
      // get last player pos of player of this scene
      playerMovState.x = scenePlayer[sceneName].x;
      playerMovState.y = scenePlayer[sceneName].y;
      $activeCharacter.css('left', playerMovState.x * Config.tile.width);
      $activeCharacter.css('top', playerMovState.y * Config.tile.height);

   });

   $('body').keydown(function(e) {
      var charPos = $activeCharacter.offset();
      // W || Arrow up
      var up = ~~((e.keyCode === 87) || (e.keyCode === 38));
      // S || Arrow down
      var down = ~~((e.keyCode === 83) || (e.keyCode === 40));
      // A || Arrow left
      var left = ~~((e.keyCode === 65) || (e.keyCode === 37));
      // D || Arrow right
      var right = ~~((e.keyCode === 68) || (e.keyCode === 39));
      // F for interaction
      var action = ~~(e.keyCode === 70);
      var direction;

      if (up) {
         direction = 'up';
      } else if (down) {
         direction = 'down';
      } else if (left) {
         direction = 'left';
      } else if (right) {
         direction = 'right';
      }

      /*var newPos = {
         left : charPos.left,
         top : charPos.top
      };*/

      // move player
      if (!playerMovState.moving && direction) {
        if ($('#dialog').length > 0) {
          $('#dialog').fadeOut(500);
        }
        Entity.move({
           'elemMovState' : playerMovState,
           '$scene' : $activeScene,
           '$element' : $activeCharacter,
           'direction' : direction,
           'callback' : function() {
              // trigger global playerMoved and scene specific playerMoved events
              Game.director.currentScene.trigger('playerMoved', playerMovState, $activeScene[0].id);
              //Game.director.trigger('playerMoved', playerMovState);
           }
        });
      }

      if (action) {
         doAction();
      }
      e.stopPropagation();
      e.preventDefault();
      e.stopImmediatePropagation();
      return false;
   });

   /**
    * Function to do stuff like fight or talk
    */
   var doAction = function() {
     var $dialogWindow = $('#dialog');

      // first check direction
      var targetTile = {
         x : playerMovState.x,
         y : playerMovState.y
      };
      switch(playerMovState.facing) {
         case 'up':
            targetTile.y -= 1;
         break;
         case 'down':
            targetTile.y += 1;
         break;
         case 'left':
            targetTile.x -= 1;
         break;
         case 'right':
            targetTile.x += 1;
         break;
      }
      // check if on there is sth on the tile the player is facing to interact with
      var foundSth = false;
      $activeScene.find('#x' + targetTile.x + '-y' + targetTile.y + '.talkable').each(function(index) {
         foundSth = true;
         $dialogWindow.css('left', parseInt($(this).css('left')) + Config.tile.width).css('top', $(this).css('top'));
         $dialogWho = $dialogWindow.find('.who');
         $dialogWhat = $dialogWindow.find('.what');
         console.log('Your are talking to someone');
         //TODO open dialog or sth similar
         if ($(this).hasClass('knight old')) {
            $dialogWho.text(Config.dialog.masterTemplar.who);
            $dialogWhat.text(Config.dialog.masterTemplar.what);
            playerMovState.level = 1;
         } else if ($(this).hasClass('socket tablecloth')) {
            $dialogWho.text(Config.dialog.socket.who);
            $dialogWhat.text(Config.dialog.socket.what);
            $(this).removeClass('tablecloth');
            playerMovState.level = 2;
            // show scene arrows
            $(this).parents('tile-container:first').find('.tile.arrow.left').addClass('items');
         } else if ($(this).hasClass('wife')) {
            $dialogWho.text(Config.dialog.wife2.who);
            $dialogWhat.text(Config.dialog.wife2.what);
            playerMovState.level = 3;
            // show scene arrows
            $(this).parents('tile-container:first').find('.tile.arrow.up').addClass('items');
         } else if ($(this).hasClass('sign')) {
            $dialogWho.text(Config.dialog.sign.who);
            $dialogWhat.text(Config.dialog.sign.what);
         } else if ($(this).hasClass('knight')) {
            $dialogWho.text(Config.dialog.knight.who);
            $dialogWhat.text(Config.dialog.knight.what);
         }
         $dialogWindow.fadeIn(300);
      });
      // attack
      $activeScene.find('.attackable[data-x="' + targetTile.x + '"][data-y="' + targetTile.y + '"]').each(function(index) {
         foundSth = true;
      // TODO do fancy animation
         $(this).addClass('dead');
         var tileX = $(this).attr('data-x');
         var tileY = $(this).attr('data-y');
         $(this).remove();
         // remove enemy
         $(this).fadeOut('slow');
         var $tile = $activeScene.find('#x' + tileX + '-y' + tileY);
         $tile.hide();
         $tile.fadeIn('slow', function() {
            $(this).addClass('items dead');
         });
      });
      if (!foundSth) {
         //TODO add fancy sword swing into empty air
      }
   };

   var decreaseLives = function() {
      lives--;
      if (lives === 0) {
         alert('Game over');
         location.reload();
      }
   };

   return {
      'playerMovState' : playerMovState,
      'decreaseLives' : decreaseLives
   };
}); <commit_msg>added possibility to kill dummies<commit_after>define('isr/player', ['jquery', 'isr', 'isr/config', 'isr/entity'], function($, Game, Config, Entity) {
   var $activeScene;
   var $activeCharacter;
   var scenePlayer = {
      'church' : {'x': 5, 'y' : 8},
      'family' : {'x': 5, 'y' : 8},
      'temple-1' : {'x': 5, 'y' : 8},
      'temple-2' : {'x': 5, 'y' : 8},
      'war-1' : {'x': 0, 'y' : 3},
      'war-2' : {'x': 0, 'y' : 3}
   };
   // current player position
   var playerMovState = {
      'x' : 0,
      'y' : 2,
      'facing' : 'up',
      // var to indicate if character is currently moving
      'moving' : false,
      'level' : 0
   };
   var lives = 3;
   var startTime = 0;

   Game.director.on('scene:change', function(sceneName) {
      // save current player pos to scenePlayer
      if ($activeScene) {
         scenePlayer[$activeScene[0].id].x = playerMovState.x;
         scenePlayer[$activeScene[0].id].y = playerMovState.y;
      }
      console.log('switched scene to ' +sceneName);
      $activeScene = $('#' + sceneName);
      $activeCharacter = $activeScene.find('.character');
      // get last player pos of player of this scene
      playerMovState.x = scenePlayer[sceneName].x;
      playerMovState.y = scenePlayer[sceneName].y;
      $activeCharacter.css('left', playerMovState.x * Config.tile.width);
      $activeCharacter.css('top', playerMovState.y * Config.tile.height);

   });

   $('body').keydown(function(e) {
      var charPos = $activeCharacter.offset();
      // W || Arrow up
      var up = ~~((e.keyCode === 87) || (e.keyCode === 38));
      // S || Arrow down
      var down = ~~((e.keyCode === 83) || (e.keyCode === 40));
      // A || Arrow left
      var left = ~~((e.keyCode === 65) || (e.keyCode === 37));
      // D || Arrow right
      var right = ~~((e.keyCode === 68) || (e.keyCode === 39));
      // F for interaction
      var action = ~~(e.keyCode === 70);
      var direction;

      if (up) {
         direction = 'up';
      } else if (down) {
         direction = 'down';
      } else if (left) {
         direction = 'left';
      } else if (right) {
         direction = 'right';
      }

      /*var newPos = {
         left : charPos.left,
         top : charPos.top
      };*/

      // move player
      if (!playerMovState.moving && direction) {
        if ($('#dialog').length > 0) {
          $('#dialog').fadeOut(500);
        }
        Entity.move({
           'elemMovState' : playerMovState,
           '$scene' : $activeScene,
           '$element' : $activeCharacter,
           'direction' : direction,
           'callback' : function() {
              // trigger global playerMoved and scene specific playerMoved events
              Game.director.currentScene.trigger('playerMoved', playerMovState, $activeScene[0].id);
              //Game.director.trigger('playerMoved', playerMovState);
           }
        });
      }

      if (action) {
         doAction();
      }
      e.stopPropagation();
      e.preventDefault();
      e.stopImmediatePropagation();
      return false;
   });

   /**
    * Function to do stuff like fight or talk
    */
   var doAction = function() {
     var $dialogWindow = $('#dialog');

      // first check direction
      var targetTile = {
         x : playerMovState.x,
         y : playerMovState.y
      };
      switch(playerMovState.facing) {
         case 'up':
            targetTile.y -= 1;
         break;
         case 'down':
            targetTile.y += 1;
         break;
         case 'left':
            targetTile.x -= 1;
         break;
         case 'right':
            targetTile.x += 1;
         break;
      }
      // check if on there is sth on the tile the player is facing to interact with
      var foundSth = false;
      $activeScene.find('#x' + targetTile.x + '-y' + targetTile.y + '.talkable').each(function(index) {
         foundSth = true;
         $dialogWindow.css('left', parseInt($(this).css('left')) + Config.tile.width).css('top', $(this).css('top'));
         $dialogWho = $dialogWindow.find('.who');
         $dialogWhat = $dialogWindow.find('.what');
         console.log('Your are talking to someone');
         //TODO open dialog or sth similar
         if ($(this).hasClass('knight old')) {
            $dialogWho.text(Config.dialog.masterTemplar.who);
            $dialogWhat.text(Config.dialog.masterTemplar.what);
            playerMovState.level = 1;
         } else if ($(this).hasClass('socket tablecloth')) {
            $dialogWho.text(Config.dialog.socket.who);
            $dialogWhat.text(Config.dialog.socket.what);
            $(this).removeClass('tablecloth');
            playerMovState.level = 2;
            // show scene arrows
            $(this).parents('tile-container:first').find('.tile.arrow.left').addClass('items');
         } else if ($(this).hasClass('wife')) {
            $dialogWho.text(Config.dialog.wife2.who);
            $dialogWhat.text(Config.dialog.wife2.what);
            playerMovState.level = 3;
            // show scene arrows
            $(this).parents('tile-container:first').find('.tile.arrow.up').addClass('items');
         } else if ($(this).hasClass('sign')) {
            $dialogWho.text(Config.dialog.sign.who);
            $dialogWhat.text(Config.dialog.sign.what);
         } else if ($(this).hasClass('knight')) {
            $dialogWho.text(Config.dialog.knight.who);
            $dialogWhat.text(Config.dialog.knight.what);
         }
         $dialogWindow.fadeIn(300);
      });
      // TODO make die animation more generic
      $activeScene.find('#x' + targetTile.x + '-y' + targetTile.y + '.attackable').each(function(index) {
         $(this).remove();
      });
      // attack non attackable element
      // attack moving attackable element
      $activeScene.find('.attackable[data-x="' + targetTile.x + '"][data-y="' + targetTile.y + '"]').each(function(index) {
         foundSth = true;
      // TODO do fancy animation
         $(this).addClass('dead');
         var tileX = $(this).attr('data-x');
         var tileY = $(this).attr('data-y');
         $(this).remove();
         // remove enemy
         $(this).fadeOut('slow');
         var $tile = $activeScene.find('#x' + tileX + '-y' + tileY);
         $tile.hide();
         $tile.fadeIn('slow', function() {
            $(this).addClass('items dead');
         });
      });
      if (!foundSth) {
         //TODO add fancy sword swing into empty air
      }
   };

   var decreaseLives = function() {
      lives--;
      if (lives === 0) {
         alert('Game over');
         location.reload();
      }
   };

   return {
      'playerMovState' : playerMovState,
      'decreaseLives' : decreaseLives
   };
}); <|endoftext|>


<commit_before>const Vec3 = require('vec3').Vec3
const assert = require('assert')
const math = require('../math')
const conv = require('../conversions')
const { performance } = require('perf_hooks')

const { Physics, PlayerState } = require('../physics')

module.exports = inject

const PI = Math.PI
const PI_2 = Math.PI * 2
const PHYSICS_INTERVAL_MS = 50
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000

function inject (bot) {
  const mcData = require('minecraft-data')(bot.version)

  const world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
  const physics = Physics(mcData, world)

  bot.jumpQueued = false
  bot.jumpTicks = 0 // autojump cooldown

  const controlState = {
    forward: false,
    back: false,
    left: false,
    right: false,
    jump: false,
    sprint: false,
    sneak: false
  }
  let lastSentYaw = null
  let doPhysicsTimer = null
  let lastPhysicsFrameTime = null
  let physicEnabled = false

  const lastSent = {
    x: 0,
    y: 0,
    z: 0,
    yaw: 0,
    pitch: 0,
    onGround: false,
    time: 0
  }

  // This function should be executed each tick (every 0.05 seconds)
  // How it works: https://gafferongames.com/post/fix_your_timestep/
  let timeAccumulator = 0
  function doPhysics () {
    const now = performance.now()
    const deltaSeconds = (now - lastPhysicsFrameTime) / 1000
    lastPhysicsFrameTime = now

    timeAccumulator += deltaSeconds

    while (timeAccumulator >= PHYSICS_TIMESTEP) {
      if (physicEnabled) {
        physics.simulatePlayer(new PlayerState(bot, controlState), world).apply(bot)
        bot.emit('physicTick')
      }
      updatePosition(PHYSICS_TIMESTEP)
      timeAccumulator -= PHYSICS_TIMESTEP
    }
  }

  function cleanup () {
    clearInterval(doPhysicsTimer)
    doPhysicsTimer = null
  }

  function sendPacketPosition (position, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.onGround = onGround
    bot._client.write('position', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketLook (yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('look', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketPositionAndLook (position, yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('position_look', lastSent)
    bot.emit('move', oldPos)
  }

  function updatePosition (dt) {
    // If you're dead, you're probably on the ground though ...
    if (!bot.isAlive) bot.entity.onGround = true

    // Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
    let deltaYaw = (bot.entity.yaw - lastSentYaw) % PI_2
    if (deltaYaw < -PI) deltaYaw += PI_2
    else if (deltaYaw > PI) deltaYaw -= PI_2

    // Vanilla doesn't clamp yaw, so we don't want to do it either
    const maxDeltaYaw = dt * physics.yawSpeed
    lastSentYaw += math.clamp(-maxDeltaYaw, deltaYaw, maxDeltaYaw)

    const yaw = conv.toNotchianYaw(lastSentYaw)
    const pitch = conv.toNotchianPitch(bot.entity.pitch)
    const position = bot.entity.position
    const onGround = bot.entity.onGround

    // Only send a position update if necessary, select the appropriate packet
    const positionUpdated = lastSent.x !== position.x || lastSent.y !== position.y || lastSent.z !== position.z
    const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch

    if (positionUpdated && lookUpdated && bot.isAlive) {
      sendPacketPositionAndLook(position, yaw, pitch, onGround)
    } else if (positionUpdated && bot.isAlive) {
      sendPacketPosition(position, onGround)
    } else if (lookUpdated && bot.isAlive) {
      sendPacketLook(yaw, pitch, onGround)
    } else if (performance.now() - lastSent.time >= 1000) {
      // Send a position packet every second, even if no update was made
      sendPacketPosition(position, onGround)
      lastSent.time = performance.now()
    } else if (bot.supportFeature('positionUpdateSentEveryTick') && bot.isAlive) {
      // For versions < 1.12, one player packet should be sent every tick
      // for the server to update health correctly
      bot._client.write('flying', { onGround: bot.entity.onGround })
    }
  }

  bot.physics = physics

  bot.setControlState = (control, state) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    assert.ok(typeof state === 'boolean', `invalid state: ${state}`)
    if (controlState[control] === state) return
    controlState[control] = state
    if (control === 'jump' && state) {
      bot.jumpQueued = true
    } else if (control === 'sprint') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 3 : 4,
        jumpBoost: 0
      })
    } else if (control === 'sneak') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 0 : 1,
        jumpBoost: 0
      })
    }
  }

  bot.clearControlStates = () => {
    for (const control in controlState) {
      bot.setControlState(control, false)
    }
  }

  bot.controlState = {}

  for (const control of Object.keys(controlState)) {
    Object.defineProperty(bot.controlState, control, {
      get () {
        return controlState[control]
      },
      set (state) {
        bot.setControlState(control, state)
        return state
      }
    })
  }

  function noop (err) {
    if (err) throw err
  }

  let lookAtCb = noop
  function checkYaw () {

    const delta = Math.abs(((lastSentYaw % PI_2) - bot.entity.yaw) % PI_2);

    if (delta < 0.001 || PI_2 - delta < 0.001) {
      lookAtCb()
      lookAtCb = noop
    }
  }
  bot.on('move', checkYaw)

  bot.look = (yaw, pitch, force, cb) => {
    bot.entity.yaw = yaw
    bot.entity.pitch = pitch
    if (force) {
      lastSentYaw = yaw
      lookAtCb = noop
      cb()
    } else {
      lookAtCb = cb || noop
    }
  }

  bot.lookAt = (point, force, cb) => {
    const delta = point.minus(bot.entity.position.offset(0, bot.entity.height, 0))
    const yaw = Math.atan2(-delta.x, -delta.z)
    const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z)
    const pitch = Math.atan2(delta.y, groundDistance)
    bot.look(yaw, pitch, force, cb)
  }

  // player position and look (clientbound)
  bot._client.on('position', (packet) => {
    bot.entity.height = 1.62
    bot.entity.velocity.set(0, 0, 0)

    // If flag is set, then the corresponding value is relative, else it is absolute
    const pos = bot.entity.position
    pos.set(
      packet.flags & 1 ? (pos.x + packet.x) : packet.x,
      packet.flags & 2 ? (pos.y + packet.y) : packet.y,
      packet.flags & 4 ? (pos.z + packet.z) : packet.z
    )

    const newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
    const newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
    bot.entity.yaw = conv.fromNotchianYaw(newYaw)
    bot.entity.pitch = conv.fromNotchianPitch(newPitch)
    bot.entity.onGround = false

    if (bot.supportFeature('teleportUsesPositionPacket')) {
      sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)
    } else if (bot.supportFeature('teleportUsesOwnPacket')) {
      bot._client.write('teleport_confirm', { teleportId: packet.teleportId })
    }

    physicEnabled = true
    bot.entity.timeSinceOnGround = 0
    lastSentYaw = bot.entity.yaw

    if (doPhysicsTimer === null) {
      lastPhysicsFrameTime = performance.now()
      doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS)
    }
    bot.emit('forcedMove')
  })

  bot.on('mount', () => { physicEnabled = false })
  bot.on('respawn', () => { physicEnabled = false })
  bot.on('end', cleanup)
}
<commit_msg>Update physics.js<commit_after>const Vec3 = require('vec3').Vec3
const assert = require('assert')
const math = require('../math')
const conv = require('../conversions')
const { performance } = require('perf_hooks')

const { Physics, PlayerState } = require('../physics')

module.exports = inject

const PI = Math.PI
const PI_2 = Math.PI * 2
const PHYSICS_INTERVAL_MS = 50
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000

function inject (bot) {
  const mcData = require('minecraft-data')(bot.version)

  const world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
  const physics = Physics(mcData, world)

  bot.jumpQueued = false
  bot.jumpTicks = 0 // autojump cooldown

  const controlState = {
    forward: false,
    back: false,
    left: false,
    right: false,
    jump: false,
    sprint: false,
    sneak: false
  }
  let lastSentYaw = null
  let doPhysicsTimer = null
  let lastPhysicsFrameTime = null
  let physicEnabled = false

  const lastSent = {
    x: 0,
    y: 0,
    z: 0,
    yaw: 0,
    pitch: 0,
    onGround: false,
    time: 0
  }

  // This function should be executed each tick (every 0.05 seconds)
  // How it works: https://gafferongames.com/post/fix_your_timestep/
  let timeAccumulator = 0
  function doPhysics () {
    const now = performance.now()
    const deltaSeconds = (now - lastPhysicsFrameTime) / 1000
    lastPhysicsFrameTime = now

    timeAccumulator += deltaSeconds

    while (timeAccumulator >= PHYSICS_TIMESTEP) {
      if (physicEnabled) {
        physics.simulatePlayer(new PlayerState(bot, controlState), world).apply(bot)
        bot.emit('physicTick')
      }
      updatePosition(PHYSICS_TIMESTEP)
      timeAccumulator -= PHYSICS_TIMESTEP
    }
  }

  function cleanup () {
    clearInterval(doPhysicsTimer)
    doPhysicsTimer = null
  }

  function sendPacketPosition (position, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.onGround = onGround
    bot._client.write('position', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketLook (yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('look', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketPositionAndLook (position, yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('position_look', lastSent)
    bot.emit('move', oldPos)
  }

  let deltaYaw;
  function updatePosition (dt) {
    // If you're dead, you're probably on the ground though ...
    if (!bot.isAlive) bot.entity.onGround = true

    // Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
    deltaYaw = (bot.entity.yaw - lastSentYaw) % PI_2
    if (deltaYaw < -PI) deltaYaw += PI_2
    else if (deltaYaw > PI) deltaYaw -= PI_2

    // Vanilla doesn't clamp yaw, so we don't want to do it either
    const maxDeltaYaw = dt * physics.yawSpeed
    lastSentYaw += math.clamp(-maxDeltaYaw, deltaYaw, maxDeltaYaw)

    const yaw = conv.toNotchianYaw(lastSentYaw)
    const pitch = conv.toNotchianPitch(bot.entity.pitch)
    const position = bot.entity.position
    const onGround = bot.entity.onGround

    // Only send a position update if necessary, select the appropriate packet
    const positionUpdated = lastSent.x !== position.x || lastSent.y !== position.y || lastSent.z !== position.z
    const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch

    if (positionUpdated && lookUpdated && bot.isAlive) {
      sendPacketPositionAndLook(position, yaw, pitch, onGround)
    } else if (positionUpdated && bot.isAlive) {
      sendPacketPosition(position, onGround)
    } else if (lookUpdated && bot.isAlive) {
      sendPacketLook(yaw, pitch, onGround)
    } else if (performance.now() - lastSent.time >= 1000) {
      // Send a position packet every second, even if no update was made
      sendPacketPosition(position, onGround)
      lastSent.time = performance.now()
    } else if (bot.supportFeature('positionUpdateSentEveryTick') && bot.isAlive) {
      // For versions < 1.12, one player packet should be sent every tick
      // for the server to update health correctly
      bot._client.write('flying', { onGround: bot.entity.onGround })
    }
  }

  bot.physics = physics

  bot.setControlState = (control, state) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    assert.ok(typeof state === 'boolean', `invalid state: ${state}`)
    if (controlState[control] === state) return
    controlState[control] = state
    if (control === 'jump' && state) {
      bot.jumpQueued = true
    } else if (control === 'sprint') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 3 : 4,
        jumpBoost: 0
      })
    } else if (control === 'sneak') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 0 : 1,
        jumpBoost: 0
      })
    }
  }

  bot.clearControlStates = () => {
    for (const control in controlState) {
      bot.setControlState(control, false)
    }
  }

  bot.controlState = {}

  for (const control of Object.keys(controlState)) {
    Object.defineProperty(bot.controlState, control, {
      get () {
        return controlState[control]
      },
      set (state) {
        bot.setControlState(control, state)
        return state
      }
    })
  }

  function noop (err) {
    if (err) throw err
  }

  let lookAtCb = noop
  function checkYaw () {

    if (Math.abs(deltaYaw)<0.001) {
      lookAtCb()
      lookAtCb = noop
    }
  }
  bot.on('move', checkYaw)

  bot.look = (yaw, pitch, force, cb) => {
    bot.entity.yaw = yaw
    bot.entity.pitch = pitch
    if (force) {
      lastSentYaw = yaw
      lookAtCb = noop
      cb()
    } else {
      lookAtCb = cb || noop
    }
  }

  bot.lookAt = (point, force, cb) => {
    const delta = point.minus(bot.entity.position.offset(0, bot.entity.height, 0))
    const yaw = Math.atan2(-delta.x, -delta.z)
    const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z)
    const pitch = Math.atan2(delta.y, groundDistance)
    bot.look(yaw, pitch, force, cb)
  }

  // player position and look (clientbound)
  bot._client.on('position', (packet) => {
    bot.entity.height = 1.62
    bot.entity.velocity.set(0, 0, 0)

    // If flag is set, then the corresponding value is relative, else it is absolute
    const pos = bot.entity.position
    pos.set(
      packet.flags & 1 ? (pos.x + packet.x) : packet.x,
      packet.flags & 2 ? (pos.y + packet.y) : packet.y,
      packet.flags & 4 ? (pos.z + packet.z) : packet.z
    )

    const newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
    const newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
    bot.entity.yaw = conv.fromNotchianYaw(newYaw)
    bot.entity.pitch = conv.fromNotchianPitch(newPitch)
    bot.entity.onGround = false

    if (bot.supportFeature('teleportUsesPositionPacket')) {
      sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)
    } else if (bot.supportFeature('teleportUsesOwnPacket')) {
      bot._client.write('teleport_confirm', { teleportId: packet.teleportId })
    }

    physicEnabled = true
    bot.entity.timeSinceOnGround = 0
    lastSentYaw = bot.entity.yaw

    if (doPhysicsTimer === null) {
      lastPhysicsFrameTime = performance.now()
      doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS)
    }
    bot.emit('forcedMove')
  })

  bot.on('mount', () => { physicEnabled = false })
  bot.on('respawn', () => { physicEnabled = false })
  bot.on('end', cleanup)
}
<|endoftext|>


<commit_before>window.showAlert = function(msg, type) {
	var html = '
';
	html += ' x ';
	html += '
';

	var container = $('.asset-view-container');

	if (!container.has('.alert').length) {
		container.prepend(html);
	}
	var alert = container.find('.alert');
	alert.removeClass().addClass('info-div alert alert-' + type).find('span').html(msg);
	alert.stop().fadeIn("fast");
}

$(document).ready(function() {

	/* creating a new version */
	//The existing versions of the asset
	var existingVersionList = null;
	var ASSET_VERSION_CONTAINER = '#asset-new-version-control-group';
	var ASSET_NEW_VERSION_MSG_CONTAINER = '#asset-new-version-msgs';
	var CSS_ERR = 'control-group error';
	var CSS_SUCCESS = 'control-group success';

	$('.dropdown-toggle').dropdown();
	$('#asset_view_tabs a').click(function(e) {
		e.preventDefault();
		$(this).tab('show');
	});

	$('.list-asset-table').on('click', 'tr', function() {
		var link = $(this).find('.asset-listing-name a').attr('href');
		window.location = link;
	});

	$('#search-button').on('click', search);

	$('#inp_searchAsset').keypress(function(e) {
		if (e.which == 13)
			search();
	});

	$('#asset-new-version-control-group').click(function(e) {
		e.stopPropagation();
	});

	/* creating a new version */
	//initCssContainer(ASSET_VERSION_CONTAINER,[CSS_ERR,CSS_SUCCESS]);

	$('#btn-increase-version-asset').on('click', function(e) {
		e.preventDefault();

		var MSG_ERR_NO_VERSION = 'Version number is empty.';

		var url = window.location.pathname;

		//Obtain the asset id
		var assetId = getAssetIdFromUrl(url);

		var assetType = $('#meta-asset-type').val();

		//Obtain the version entered by the user
		var userProvidedVersion = $('#asset-new-version').val();

		//Check if the user has entered a version
		if (!userProvidedVersion) {
			//alert(MSG_ERR_NO_VERSION);
			displayVersionMessage({
				msgCss : CSS_ERR,
				message : MSG_ERR_NO_VERSION
			});
			return;
		}

		displayVersionMessage({
			msgCss : CSS_SUCCESS,
			message : 'Checking version..'
		});

		//alert('id: '+assetId+' version: '+userProvidedVersion);

		var path = '/publisher/api/version/' + assetType + '/' + assetId;

		//Make a call an obtain the existing asset versions
		$.ajax({
			url : path,
			type : 'GET',
			success : function(response) {
				// alert(response);

				var versionList = JSON.parse(response);

				//Check if the version entered by the user is an existing one
				var existingVersion = checkIfExisting(versionList, userProvidedVersion);

				//Display a message to the user indicating the version exists
				if (existingVersion) {
					displayVersionMessage({
						msgCss : CSS_ERR,
						message : 'Version already exists.'
					});
					return;
				}

				/*
				displayVersionMessage({
									msgCss : CSS_SUCCESS,
									message : 'Creating new version..'
								});*/

				createNewVersion(userProvidedVersion, assetId, assetType);
			},
			error : function() {
				alert('unable to retrieve asset version details.');
			}
		});

	});

	$('.btn-upload').on('click', function(e) {
		e.preventDefault();
		$(this).next().click();
	});

	$('input[type="file"]').change(function() {
		$(this).closest('.tbl-upload').find('.txt-filepath').val($(this).val());
	})
	/*
	 The function is used to check if the version entered by the user exists
	 @versionList: A list of versions
	 @newVersion: The new version entered by the user
	 @return: True if the version exists,else False
	 */
	function checkIfExisting(versionList, newVersion) {
		var found = false;

		for (var index = 0; (index < versionList.length) && (!found); index++) {
			if (versionList[index].version == newVersion) {
				found = true;
			}
		}

		return found;
	}

	/*
	 The function is used to create a new version of a given asset
	 @assetId: The id of the asset to be created
	 @newVersion: The new version of the asset to be created
	 */
	function createNewVersion(newVersion, assetId, assetType) {
		var path = '/publisher/api/version/' + assetType + '/' + assetId + '/' + newVersion;

		$.ajax({
			url : path,
			type : 'POST',
			success : function(response) {
				$('#modal-redirect').modal('show');
				setTimeout(function() {
					var newVersionDetails = JSON.parse(response);
					window.location = newVersionDetails.url;
				}, 2000);

			},
			error : function() {
				displayVersionMessage({
					msgCss : CSS_ERR,
					message : 'A new version of this ' + assetType + ' was not created.'
				})
			}
		})
	}

	/*
	 The function is used to display a message in-line
	 @msg: The message to be displayed
	 */
	function displayVersionMessage(msg) {
		var container = ASSET_VERSION_CONTAINER;
		var containerCss = msg.msgCss;
		var msgContainer = ASSET_NEW_VERSION_MSG_CONTAINER;
		var message = msg.message;

		if ($(msgContainer).attr('expanded') != 'true') {
			$(msgContainer).attr('expanded', 'true');
			$(msgContainer).closest('.dropdown-menu').height('+=10');
		}

		$(container).attr('class', containerCss);
		$(msgContainer).html(message);

	}

	/*
	 The function is used to initialize the container housing the version
	 input
	 @container: The container within which the version details reside
	 @classes: The CSS classes to be added to the container.
	 */
	function initCssContainer(container, classes) {
		/* for(var index in classes){
		 console.log('container: '+container+' added class: '+classes[index]);

		 $(container).addClass(classes[index]);
		 $(container).toggleClass(classes[index]);
		 }  */

	}

	/*
	 The function is used to parse a url to obtain the
	 asset id and type.
	 @return: The id component of the url
	 */
	function getAssetIdFromUrl(url) {
		//The id
		//Break the url into components
		var comps = url.split('/');

		//Given a url of the form /pub/api/asset/{asset-type}/{asset-id}
		//length=5
		//then: length-2 = {asset-type} length-1 = {asset-id}
		var id = comps[comps.length - 1];

		return id;
	}

	function search() {
		var searchAssetString = $('#inp_searchAsset').val();
		var searchPrefix = $('#search-prefix').val();

		if (searchAssetString != "") {
			var link = '/publisher/assets/' + searchPrefix + '/?query=' + searchAssetString;
			window.location = link;
		}
	}

});
<commit_msg>Fixing table header links in Publisher<commit_after>window.showAlert = function(msg, type) {
	var html = '
';
	html += ' x ';
	html += '
';

	var container = $('.asset-view-container');

	if (!container.has('.alert').length) {
		container.prepend(html);
	}
	var alert = container.find('.alert');
	alert.removeClass().addClass('info-div alert alert-' + type).find('span').html(msg);
	alert.stop().fadeIn("fast");
}

$(document).ready(function() {

	/* creating a new version */
	//The existing versions of the asset
	var existingVersionList = null;
	var ASSET_VERSION_CONTAINER = '#asset-new-version-control-group';
	var ASSET_NEW_VERSION_MSG_CONTAINER = '#asset-new-version-msgs';
	var CSS_ERR = 'control-group error';
	var CSS_SUCCESS = 'control-group success';

	$('.dropdown-toggle').dropdown();
	$('#asset_view_tabs a').click(function(e) {
		e.preventDefault();
		$(this).tab('show');
	});

	$('.list-asset-table').on('click', 'tr:not(:first-child)', function() {
		var link = $(this).find('.asset-listing-name a').attr('href');
		window.location = link;
	});

	$('#search-button').on('click', search);

	$('#inp_searchAsset').keypress(function(e) {
		if (e.which == 13)
			search();
	});

	$('#asset-new-version-control-group').click(function(e) {
		e.stopPropagation();
	});

	/* creating a new version */
	//initCssContainer(ASSET_VERSION_CONTAINER,[CSS_ERR,CSS_SUCCESS]);

	$('#btn-increase-version-asset').on('click', function(e) {
		e.preventDefault();

		var MSG_ERR_NO_VERSION = 'Version number is empty.';

		var url = window.location.pathname;

		//Obtain the asset id
		var assetId = getAssetIdFromUrl(url);

		var assetType = $('#meta-asset-type').val();

		//Obtain the version entered by the user
		var userProvidedVersion = $('#asset-new-version').val();

		//Check if the user has entered a version
		if (!userProvidedVersion) {
			//alert(MSG_ERR_NO_VERSION);
			displayVersionMessage({
				msgCss : CSS_ERR,
				message : MSG_ERR_NO_VERSION
			});
			return;
		}

		displayVersionMessage({
			msgCss : CSS_SUCCESS,
			message : 'Checking version..'
		});

		//alert('id: '+assetId+' version: '+userProvidedVersion);

		var path = '/publisher/api/version/' + assetType + '/' + assetId;

		//Make a call an obtain the existing asset versions
		$.ajax({
			url : path,
			type : 'GET',
			success : function(response) {
				// alert(response);

				var versionList = JSON.parse(response);

				//Check if the version entered by the user is an existing one
				var existingVersion = checkIfExisting(versionList, userProvidedVersion);

				//Display a message to the user indicating the version exists
				if (existingVersion) {
					displayVersionMessage({
						msgCss : CSS_ERR,
						message : 'Version already exists.'
					});
					return;
				}

				/*
				displayVersionMessage({
									msgCss : CSS_SUCCESS,
									message : 'Creating new version..'
								});*/

				createNewVersion(userProvidedVersion, assetId, assetType);
			},
			error : function() {
				alert('unable to retrieve asset version details.');
			}
		});

	});

	$('.btn-upload').on('click', function(e) {
		e.preventDefault();
		$(this).next().click();
	});

	$('input[type="file"]').change(function() {
		$(this).closest('.tbl-upload').find('.txt-filepath').val($(this).val());
	})
	/*
	 The function is used to check if the version entered by the user exists
	 @versionList: A list of versions
	 @newVersion: The new version entered by the user
	 @return: True if the version exists,else False
	 */
	function checkIfExisting(versionList, newVersion) {
		var found = false;

		for (var index = 0; (index < versionList.length) && (!found); index++) {
			if (versionList[index].version == newVersion) {
				found = true;
			}
		}

		return found;
	}

	/*
	 The function is used to create a new version of a given asset
	 @assetId: The id of the asset to be created
	 @newVersion: The new version of the asset to be created
	 */
	function createNewVersion(newVersion, assetId, assetType) {
		var path = '/publisher/api/version/' + assetType + '/' + assetId + '/' + newVersion;

		$.ajax({
			url : path,
			type : 'POST',
			success : function(response) {
				$('#modal-redirect').modal('show');
				setTimeout(function() {
					var newVersionDetails = JSON.parse(response);
					window.location = newVersionDetails.url;
				}, 2000);

			},
			error : function() {
				displayVersionMessage({
					msgCss : CSS_ERR,
					message : 'A new version of this ' + assetType + ' was not created.'
				})
			}
		})
	}

	/*
	 The function is used to display a message in-line
	 @msg: The message to be displayed
	 */
	function displayVersionMessage(msg) {
		var container = ASSET_VERSION_CONTAINER;
		var containerCss = msg.msgCss;
		var msgContainer = ASSET_NEW_VERSION_MSG_CONTAINER;
		var message = msg.message;

		if ($(msgContainer).attr('expanded') != 'true') {
			$(msgContainer).attr('expanded', 'true');
			$(msgContainer).closest('.dropdown-menu').height('+=10');
		}

		$(container).attr('class', containerCss);
		$(msgContainer).html(message);

	}

	/*
	 The function is used to initialize the container housing the version
	 input
	 @container: The container within which the version details reside
	 @classes: The CSS classes to be added to the container.
	 */
	function initCssContainer(container, classes) {
		/* for(var index in classes){
		 console.log('container: '+container+' added class: '+classes[index]);

		 $(container).addClass(classes[index]);
		 $(container).toggleClass(classes[index]);
		 }  */

	}

	/*
	 The function is used to parse a url to obtain the
	 asset id and type.
	 @return: The id component of the url
	 */
	function getAssetIdFromUrl(url) {
		//The id
		//Break the url into components
		var comps = url.split('/');

		//Given a url of the form /pub/api/asset/{asset-type}/{asset-id}
		//length=5
		//then: length-2 = {asset-type} length-1 = {asset-id}
		var id = comps[comps.length - 1];

		return id;
	}

	function search() {
		var searchAssetString = $('#inp_searchAsset').val();
		var searchPrefix = $('#search-prefix').val();

		if (searchAssetString != "") {
			var link = '/publisher/assets/' + searchPrefix + '/?query=' + searchAssetString;
			window.location = link;
		}
	}

});
<|endoftext|>


<commit_before>var http = require("http");
var fs = require('fs');
var cwd = process.cwd(); // does not end with a slash
var mime = require('mime');

module.exports = {
  start: function (options) {

    options = options || {};
    options.port = options.port || 8080;

    this.server = http.createServer(function (request, response) {
      var match = request.url.match(/(^[^\?]*).*/);
      var filePath = cwd + match[1];
      if (!filePath.match(/\.[^\/]+$/)) {
        if (request.method === 'GET') {
          filePath += '.json';
        } else {
          filePath += ('_' + request.method + '.json');
        }
      }

      if (fs.existsSync(filePath)) {
        var stat = fs.statSync(filePath);
        var headers = {
          "Content-Type": "text/json",
          "Content-Length": stat.size
        };
        var contentType = mime.lookup(filePath);
        if (contentType) {
          headers['Content-Type'] = contentType;
        }
        if (options.cors) {
          headers['Access-Control-Allow-Origin'] = '*';
        }
        if (filePath.match(/.*\.json/)) {
          var statusCode = 200;
          var _headers = {};
          fs.readFile(filePath, 'utf8', function (err, data) {
            var content = data;
            var match = data.match(/\*({[^}]*})\s*\n(.*)/);
            if (match) {
              try {
                var meta = JSON.parse(match[1]);
                statusCode = meta.code || 200;
                headers = meta.headers || {};
              } catch (e) {
                console.log('invalid meta data for ' + filePath + ': ' + e);
              }
              content = match[2];
            }
            for (var name in _headers) {
              headers[name] = _headers[_name];
            }
            response.writeHead(statusCode, headers);
            response.write(content);
            response.end();
          });
        } else {
          response.writeHead(200, headers);

          var readStream = fs.createReadStream(filePath);
          readStream.on('data', function (data) {
            response.write(data);
          });
          readStream.on('end', function () {
            response.end();
          });
        }
      } else {
        response.writeHead(404, {});
        response.write(filePath);
        response.end();
      }
    });

    this.server.listen(options.port);
    console.log('server started on port: ' + options.port);
  }
};
<commit_msg>Added debug output for non-silent mode<commit_after>var http = require("http");
var fs = require('fs');
var cwd = process.cwd(); // does not end with a slash
var mime = require('mime');

module.exports = {
  start: function (options) {

    options = options || {};
    options.port = options.port || 8080;

    this.server = http.createServer(function (request, response) {
      if (!options.silent) {
        console.log("Incoming %s request to %s", request.method, request.url);
        console.log("Headers:", '\n', request.headers);

        body = "";
        request.on('data', function (data) {
          body += data;
        });
        request.on('end', function () {
          console.log("Body:", '\n', body, '\n');
        });
      }

      var match = request.url.match(/(^[^\?]*).*/);
      var filePath = cwd + match[1];
      if (!filePath.match(/\.[^\/]+$/)) {
        if (request.method === 'GET') {
          filePath += '.json';
        } else {
          filePath += ('_' + request.method + '.json');
        }
      }

      if (fs.existsSync(filePath)) {
        var stat = fs.statSync(filePath);
        var headers = {
          "Content-Type": "text/json",
          "Content-Length": stat.size
        };
        var contentType = mime.lookup(filePath);
        if (contentType) {
          headers['Content-Type'] = contentType;
        }
        if (options.cors) {
          headers['Access-Control-Allow-Origin'] = '*';
        }
        if (filePath.match(/.*\.json/)) {
          var statusCode = 200;
          var _headers = {};
          fs.readFile(filePath, 'utf8', function (err, data) {
            var content = data;
            var match = data.match(/\*({[^}]*})\s*\n(.*)/);
            if (match) {
              try {
                var meta = JSON.parse(match[1]);
                statusCode = meta.code || 200;
                headers = meta.headers || {};
              } catch (e) {
                console.log('invalid meta data for ' + filePath + ': ' + e);
              }
              content = match[2];
            }
            for (var name in _headers) {
              headers[name] = _headers[_name];
            }
            response.writeHead(statusCode, headers);
            response.write(content);
            response.end();
          });
        } else {
          response.writeHead(200, headers);

          var readStream = fs.createReadStream(filePath);
          readStream.on('data', function (data) {
            response.write(data);
          });
          readStream.on('end', function () {
            response.end();
          });
        }
      } else {
        response.writeHead(404, {});
        response.write(filePath);
        response.end();
      }
    });

    this.server.listen(options.port);
    console.log('server started on port: ' + options.port);
  }
};
<|endoftext|>


<commit_before>// All credit goes to Rick Olson.
(function($) {

  $.relatizeDate = function(ds) {
    return $.relatizeDate.timeFromNowInWords( new Date(ds) );
  }

  // shortcut
  $r = $.relatizeDate

  $.extend($.relatizeDate, {
    shortDays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
    months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],

    /**
     * Given a formatted string, replace the necessary items and return.
     * Example: Time.now().strftime("%B %d, %Y") => February 11, 2008
     * @param {String} format The formatted string used to format the results
     */
    strftime: function(date, format) {
      var day = date.getDay(), month = date.getMonth();
      var hours = date.getHours(), minutes = date.getMinutes();

      var pad = function(num) {
        var string = num.toString(10);
        return new Array((2 - string.length) + 1).join('0') + string
      };

      return format.replace(/\%([aAbBcdHImMpSwyY])/g, function(part) {
        switch(part[1]) {
          case 'a': return $r.shortDays[day]; break;
          case 'A': return $r.days[day]; break;
          case 'b': return $r.shortMonths[month]; break;
          case 'B': return $r.months[month]; break;
          case 'c': return date.toString(); break;
          case 'd': return pad(date.getDate()); break;
          case 'H': return pad(hours); break;
          case 'I': return pad((hours + 12) % 12); break;
          case 'm': return pad(month + 1); break;
          case 'M': return pad(minutes); break;
          case 'p': return hours > 12 ? 'PM' : 'AM'; break;
          case 'S': return pad(date.getSeconds()); break;
          case 'w': return day; break;
          case 'y': return pad(date.getFullYear() % 100); break;
          case 'Y': return date.getFullYear().toString(); break;
        }
      })
    },

    timeFromNowInWords: function(targetDate, includeTime) {
      return $r.distanceOfTimeInWords(new Date(), targetDate, includeTime);
    },

    /**
     * Return the distance of time in words between two Date's
     * Example: '5 days ago', 'about an hour ago'
     * @param {Date} fromTime The start date to use in the calculation
     * @param {Date} toTime The end date to use in the calculation
     * @param {Boolean} Include the time in the output
     */
    distanceOfTimeInWords: function(fromTime, toTime, includeTime) {
      var delta = parseInt((toTime.getTime() - fromTime.getTime()) / 1000);
      var figure, unit;
      if (delta < 120) {
        figure = '0';
        unit = 'minutes';
      } else if (delta < (45*60)) {
        figure = (parseInt(delta / 60)).toString();
        unit = 'minutes';
      } else if (delta < (120*60)) {
        figure = '1';
        unit = 'hour';
      } else if (delta < (24*60*60)) {
        figure = (parseInt(delta / 3600)).toString();
        unit = 'hours';
      } else if (delta < (48*60*60)) {
        figure = '1';
        unit = 'day';
      } else {
        figure = (parseInt(delta / 86400)).toString();
        unit = 'days';
      }
      return {
        figure: figure,
        unit: unit
      };
    }
  })
})(jQuery);
<commit_msg>shorten minutes to min<commit_after>// All credit goes to Rick Olson.
(function($) {

  $.relatizeDate = function(ds) {
    return $.relatizeDate.timeFromNowInWords( new Date(ds) );
  }

  // shortcut
  $r = $.relatizeDate

  $.extend($.relatizeDate, {
    shortDays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
    months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],

    /**
     * Given a formatted string, replace the necessary items and return.
     * Example: Time.now().strftime("%B %d, %Y") => February 11, 2008
     * @param {String} format The formatted string used to format the results
     */
    strftime: function(date, format) {
      var day = date.getDay(), month = date.getMonth();
      var hours = date.getHours(), minutes = date.getMinutes();

      var pad = function(num) {
        var string = num.toString(10);
        return new Array((2 - string.length) + 1).join('0') + string
      };

      return format.replace(/\%([aAbBcdHImMpSwyY])/g, function(part) {
        switch(part[1]) {
          case 'a': return $r.shortDays[day]; break;
          case 'A': return $r.days[day]; break;
          case 'b': return $r.shortMonths[month]; break;
          case 'B': return $r.months[month]; break;
          case 'c': return date.toString(); break;
          case 'd': return pad(date.getDate()); break;
          case 'H': return pad(hours); break;
          case 'I': return pad((hours + 12) % 12); break;
          case 'm': return pad(month + 1); break;
          case 'M': return pad(minutes); break;
          case 'p': return hours > 12 ? 'PM' : 'AM'; break;
          case 'S': return pad(date.getSeconds()); break;
          case 'w': return day; break;
          case 'y': return pad(date.getFullYear() % 100); break;
          case 'Y': return date.getFullYear().toString(); break;
        }
      })
    },

    timeFromNowInWords: function(targetDate, includeTime) {
      return $r.distanceOfTimeInWords(new Date(), targetDate, includeTime);
    },

    /**
     * Return the distance of time in words between two Date's
     * Example: '5 days ago', 'about an hour ago'
     * @param {Date} fromTime The start date to use in the calculation
     * @param {Date} toTime The end date to use in the calculation
     * @param {Boolean} Include the time in the output
     */
    distanceOfTimeInWords: function(fromTime, toTime, includeTime) {
      var delta = parseInt((toTime.getTime() - fromTime.getTime()) / 1000);
      var figure, unit;
      if (delta < 120) {
        figure = '0';
        unit = 'min';
      } else if (delta < (45*60)) {
        figure = (parseInt(delta / 60)).toString();
        unit = 'mins';
      } else if (delta < (120*60)) {
        figure = '1';
        unit = 'hour';
      } else if (delta < (24*60*60)) {
        figure = (parseInt(delta / 3600)).toString();
        unit = 'hours';
      } else if (delta < (48*60*60)) {
        figure = '1';
        unit = 'day';
      } else {
        figure = (parseInt(delta / 86400)).toString();
        unit = 'days';
      }
      return {
        figure: figure,
        unit: unit
      };
    }
  })
})(jQuery);
<|endoftext|>


<commit_before>// Storybook deps
import { storiesOf, addDecorator } from '@storybook/vue'
import _ from 'lodash'
import {
  withKnobs,
  number,
  text,
  boolean,
  object,
} from '@storybook/addon-knobs'

// Simple component to test with
import 'intersection-observer' // For IE
import Box from './Box'

// Shared props
const props = ({
  marginTop = 'calc(100vh + 1px)',
  marginBottom = 'calc(100vh + 1px)',
  height = '',
  inViewportActive = true,
  inViewportOnce = false,
  inViewportRootMargin = undefined,
  inViewportThreshold = [0, 1],
}) => { return {
  marginTop: { default: text('CSS margin-top', marginTop) },
  marginBottom: { default: text('CSS margin-bottom', marginBottom) },
  height: { default: text('CSS height', height) },

  inViewportActive: { default: boolean('inViewportActive', inViewportActive) },
  inViewportOnce: { default: boolean('inViewportOnce', inViewportOnce) },

  inViewportRootMargin: { default: text('inViewportRootMargin', inViewportRootMargin) },
  inViewportThreshold: { default: object('inViewportThreshold', inViewportThreshold) },
  inViewportRoot: { default: '.viewport' },
}}

// Shared box template.  I had to make an artifical viewport box because
// I couldn't set the iframe that Storybook uses as the viewport but the
// without that, the viewport of the parent document was getting measured,
// which was too tall
// https://github.com/w3c/IntersectionObserver/issues/283
const box = `

    <box
      :style='{
        marginTop: marginTop,
        marginBottom: marginBottom,
        height: height,
      }'
      :in-viewport-active='inViewportActive'
      :in-viewport-once='inViewportOnce'
      :in-viewport-rootMargin='inViewportRootMargin'
      :in-viewport-root='inViewportRoot'
      :in-viewport-threshold='inViewportThreshold'
    />

`

// Scroll down to box
const initiallyHiddenBox = `

👇👇👇👇👇👇👇👇👇

    ${box}

`

// Create a bucket of stories
addDecorator(withKnobs)
storiesOf('Examples', module)

  .add('Initially visible', () => ({
    components: { Box },
    props: props({ marginTop: '0vh' }),
    template: box,
  }))

  .add('Scroll to reveal', () => ({
    components: { Box },
    props: props({}),
    template: initiallyHiddenBox,
  }))

  .add('Trigger once', () => ({
    components: { Box },
    props: props({ inViewportOnce: true }),
    template: initiallyHiddenBox,
  }))

  .add('Initially inactive', () => ({
    components: { Box },
    props: props({ inViewportActive: false }),
    template: initiallyHiddenBox,
  }))

  .add('Trigger late', () => ({
    components: { Box },
    props: props({ inViewportRootMargin: '-20% 0%' }),
    template: initiallyHiddenBox,
  }))

  .add('Trigger early', () => ({
    components: { Box },
    props: props({ inViewportRootMargin: '200px 0%' }),
    template: initiallyHiddenBox,
  }))

  .add('Full height', () => ({
    components: { Box },
    props: props({ height: '120vh' }),
    template: initiallyHiddenBox,
  }))

<commit_msg>Fixing issue with margin prop<commit_after>// Storybook deps
import { storiesOf, addDecorator } from '@storybook/vue'
import _ from 'lodash'
import {
  withKnobs,
  number,
  text,
  boolean,
  object,
} from '@storybook/addon-knobs'

// Simple component to test with
import 'intersection-observer' // For IE
import Box from './Box'

// Shared props
const props = ({
  marginTop = 'calc(100vh + 1px)',
  marginBottom = 'calc(100vh + 1px)',
  height = '',
  inViewportActive = true,
  inViewportOnce = false,
  inViewportRootMargin = undefined,
  inViewportThreshold = [0, 1],
}) => { return {
  marginTop: { default: text('CSS margin-top', marginTop) },
  marginBottom: { default: text('CSS margin-bottom', marginBottom) },
  height: { default: text('CSS height', height) },

  inViewportActive: { default: boolean('inViewportActive', inViewportActive) },
  inViewportOnce: { default: boolean('inViewportOnce', inViewportOnce) },

  inViewportRootMargin: { default: text('inViewportRootMargin', inViewportRootMargin) },
  inViewportThreshold: { default: object('inViewportThreshold', inViewportThreshold) },
}}

// Shared box template.  I had to make an artifical viewport box because
// I couldn't set the iframe that Storybook uses as the viewport but the
// without that, the viewport of the parent document was getting measured,
// which was too tall
// https://github.com/w3c/IntersectionObserver/issues/283
const box = `

    <box
      :style='{
        marginTop: marginTop,
        marginBottom: marginBottom,
        height: height,
      }'
      :in-viewport-active='inViewportActive'
      :in-viewport-once='inViewportOnce'
      :in-viewport-root-margin='inViewportRootMargin'
      in-viewport-root='.viewport'
      :in-viewport-threshold='inViewportThreshold'
    />

`

// Scroll down to box
const initiallyHiddenBox = `

👇👇👇👇👇👇👇👇👇

    ${box}

`

// Create a bucket of stories
addDecorator(withKnobs)
storiesOf('Examples', module)

  .add('Initially visible', () => ({
    components: { Box },
    props: props({ marginTop: '0vh' }),
    template: box,
  }))

  .add('Scroll to reveal', () => ({
    components: { Box },
    props: props({}),
    template: initiallyHiddenBox,
  }))

  .add('Trigger once', () => ({
    components: { Box },
    props: props({ inViewportOnce: true }),
    template: initiallyHiddenBox,
  }))

  .add('Initially inactive', () => ({
    components: { Box },
    props: props({ inViewportActive: false }),
    template: initiallyHiddenBox,
  }))

  .add('Trigger late', () => ({
    components: { Box },
    props: props({ inViewportRootMargin: '-20% 0%' }),
    template: initiallyHiddenBox,
  }))

  .add('Trigger early', () => ({
    components: { Box },
    props: props({ inViewportRootMargin: '200px 0%' }),
    template: initiallyHiddenBox,
  }))

  .add('Full height', () => ({
    components: { Box },
    props: props({ height: '120vh' }),
    template: initiallyHiddenBox,
  }))

<|endoftext|>


<commit_before>// articles.js

var
    async = require('async'),
    api = require('../api'),
    db = require('../db'),
    dao = require('./_dao'),
    utils = require('./_utils'),
    images = require('./_images'),
    constants = require('../constants');

var attachmentsApi = require('./attachments_api');

var
    User = db.user,
    Article = db.article,
    Category = db.category,
    Text = db.text,
    sequelize = db.sequelize,
    next_id = db.next_id;

exports = module.exports = {

    'GET /api/articles/:id': function(req, res, next) {
        dao.find(Article, req.params.id, function(err, entity) {
            return err ? next(err) : res.send(entity);
        });
    },

    'GET /api/articles': function(req, res, next) {
        dao.findAll(Article, {
            where: ['publish_time < ?', Date.now()],
            order: 'publish_time desc'
        }, function(err, entities) {
            return err ? next(err) : res.send({ articles: entities });
        });
    },

    'POST /api/articles': function(req, res, next) {
        /**
         * Create a new article.
         *
         * @return {object} The created article object.
         */
        if (utils.isForbidden(req, constants.ROLE_EDITOR)) {
            return next(api.not_allowed('Permission denied.'));
        }
        try {
            var name = utils.get_required_param('name', req),
                category_id = utils.get_required_param('category_id', req),
                content = utils.get_required_param('content', req);
        }
        catch (e) {
            return next(e);
        }
        var description = utils.get_param('description', '', req),
            tags = utils.format_tags(utils.get_param('tags', '', req));

        var file = req.files.file;

        var publish_time = Date.now(); //req.body.publish_time;

        var content_id = next_id();
        var article_id = next_id();
        var cover_id = '';

        var tx_tasks = [];

        var checkCategoryTask = function(prev, tx, callback) {
            dao.find(Category, category_id, tx, callback);
        };
        var createTextTask = function(prev, tx, callback) {
            dao.save(Text, {
                id: content_id,
                ref_id: article_id,
                value: content
            }, tx, callback);
        };
        var createArticleTask = function(prev, tx, callback) {
            dao.save(Article, {
                id: article_id,
                user_id: req.user.id,
                user_name: req.user.name,
                category_id: category_id,
                cover_id: (prev && prev.id) || '',
                content_id: content_id,
                name: name,
                tags: tags,
                description: description,
                publish_time: publish_time
            }, tx, callback);
        };

        if (file) {
            return checkAttachment(file, function(err, attachFileObject) {
                if (err) {
                    return next(err);
                }
                // override name:
                attachFileObject.name = name;
                dao.transaction([
                    checkCategoryTask,
                    createTextTask,
                    createAttachmentTaskInTransaction(attachFileObject, req.user.id),
                    createArticleTask
                ], function(err, result) {
                    if (err) {
                        return next(err);
                    }
                    return res.send(result);
                });
            });
        }
        else {
            dao.transaction([
                checkCategoryTask,
                createTextTask,
                function(prev, tx, callback) {
                    callback(null, null);
                },
                createArticleTask
            ], function(err, result) {
                if (err) {
                    return next(err);
                }
                return res.send(result);
            });
        }
    }
}
<commit_msg>update articles api<commit_after>// articles.js

var
    async = require('async'),
    api = require('../api'),
    db = require('../db'),
    dao = require('./_dao'),
    utils = require('./_utils'),
    images = require('./_images'),
    constants = require('../constants');

var
    attachmentsApi = require('./attachments_api'),
    checkAttachment = attachmentsApi.checkAttachment,
    createAttachmentTaskInTransaction = attachmentsApi.createAttachmentTaskInTransaction;

var
    User = db.user,
    Article = db.article,
    Category = db.category,
    Text = db.text,
    sequelize = db.sequelize,
    next_id = db.next_id;

exports = module.exports = {

    'GET /api/articles/:id': function(req, res, next) {
        dao.find(Article, req.params.id, function(err, entity) {
            return err ? next(err) : res.send(entity);
        });
    },

    'GET /api/articles': function(req, res, next) {
        dao.findAll(Article, {
            where: ['publish_time < ?', Date.now()],
            order: 'publish_time desc'
        }, function(err, entities) {
            return err ? next(err) : res.send({ articles: entities });
        });
    },

    'POST /api/articles': function(req, res, next) {
        /**
         * Create a new article.
         *
         * @return {object} The created article object.
         */
        if (utils.isForbidden(req, constants.ROLE_EDITOR)) {
            return next(api.not_allowed('Permission denied.'));
        }
        try {
            var name = utils.get_required_param('name', req),
                category_id = utils.get_required_param('category_id', req),
                content = utils.get_required_param('content', req);
        }
        catch (e) {
            return next(e);
        }
        var description = utils.get_param('description', '', req),
            tags = utils.format_tags(utils.get_param('tags', '', req));

        var file = req.files.file;

        var publish_time = Date.now(); //req.body.publish_time;

        var content_id = next_id();
        var article_id = next_id();
        var cover_id = '';

        var tx_tasks = [];

        var checkCategoryTask = function(prev, tx, callback) {
            dao.find(Category, category_id, tx, callback);
        };
        var createTextTask = function(prev, tx, callback) {
            dao.save(Text, {
                id: content_id,
                ref_id: article_id,
                value: content
            }, tx, callback);
        };
        var createArticleTask = function(prev, tx, callback) {
            dao.save(Article, {
                id: article_id,
                user_id: req.user.id,
                user_name: req.user.name,
                category_id: category_id,
                cover_id: (prev && prev.id) || '',
                content_id: content_id,
                name: name,
                tags: tags,
                description: description,
                publish_time: publish_time
            }, tx, callback);
        };

        if (file) {
            return checkAttachment(file, function(err, attachFileObject) {
                if (err) {
                    return next(err);
                }
                // override name:
                attachFileObject.name = name;
                dao.transaction([
                    checkCategoryTask,
                    createTextTask,
                    createAttachmentTaskInTransaction(attachFileObject, req.user.id),
                    createArticleTask
                ], function(err, article) {
                    if (err) {
                        return next(err);
                    }
                    article.dataValues.content = content;
                    return res.send(article);
                });
            });
        }
        else {
            dao.transaction([
                checkCategoryTask,
                createTextTask,
                function(prev, tx, callback) {
                    callback(null, null);
                },
                createArticleTask
            ], function(err, article) {
                if (err) {
                    return next(err);
                }
                article.dataValues.content = content;
                return res.send(article);
            });
        }
    }
}
<|endoftext|>


<commit_before>var debug = require('debug')('unum:webapplication');
var WebSocketRequest = require('websocket').request;
var events = require('events');
var util = require('util');
var URL = require('url');

function merge(a, b){
  if (a && b) {
    for (var key in b) {
      a[key] = b[key];
    }
  }
  return a;
}
function pathRegexp(path, keys) {
  if (path instanceof RegExp) return path;
  if (Array.isArray(path)) path = '(' + path.join('|') + ')';
  path = path
    .replace(/\/\(/g, '(?:/')
    .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
      keys.push(key);
      slash = slash || '';
      return '' +
        slash +
        '(?:' +
        (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' +
        (star ? '(/*)?' : '');
    })
    .replace(/([\/.])/g, '\\$1')
    .replace(/\*/g, '(.*)');
  return new RegExp('^' + path + '$', '');
}

function createHttpServer(handler, configuration) {
  return require('http').createServer(handler);
}
function createHttpsServer(handler, configuration) {
  return require('https').createServer(configuration, handler);
}

// WebApplication class definition
// Accepts HTTP requests and sends them through the handler-chain.
function WebApplication(serverContext,configuration) {
  var me = this;
  configuration = configuration || {};
  me.context = serverContext;
  me.stack = [];
  me.errorStack = [];
  var createServer = configuration.https ? createHttpsServer : createHttpServer;
  function getRequestURL(req) {
    var protocol = req.connection.encrypted ? 'https' : 'http';
    return URL.parse(protocol + '://' + req.headers.host + req.url);
  }
  me.server = createServer(function(req,res) {
    var requestContext = {
      __proto__: me.context,
      req: req,
      res: res,
      url: getRequestURL(req),
      close: function() {
        if (this.error) {
          this.res.statusCode = this.error.status ? this.error.status : 500;
        }
        this.res.end();
      }
    };
    me.handle(requestContext);
  });
  me.server.on('upgrade',function(req,socket,head) {
    var upgradeContext = {
      __proto__: me.context,
      req: req,
      socket: socket,
      url: getRequestURL(req),
      close: function() {
        this.socket.destroy();
      }
    };
    me.handle(upgradeContext);
  });
}

// Define the basic functions of WebApplication
(function(p){
  // Add a handler to the end of the handler-chain.
  p.use = function(fn) {
    if (typeof fn !== 'function') {
      debug('Invalid handler:',fn);
      throw new Error('Invalid handler');
    }
    this.stack.push(fn);
    return this;
  };
  // Executes the handler-chain with the specified context.
  // This is used internally from WebApplication.
  p.handle = function(context) {
    var app = this;

    var appRequestContext = {
      __proto__: context,
      next: next
    };

    var stack = this.stack;
    var index = 0;
    function next(err) {
      if (err) {
        debug('Error occured in handler:',err);
        app.handleError(err,stack[index],appRequestContext);
        return;
      }
      if (index >= stack.length) {
        debug('Handler chain ended');
        app.handleError(new Error('Handler chain ended'),null,appRequestContext);
        return;
      }
      var handler = stack[index++];
      debug('Handling',handler);
      handler(appRequestContext, next);
    }
    next();
  };
  // Start listening for HTTP/HTTPS on the specified port and address.
  p.listen = function(port,address,backlog,callback) {
    this.server.listen(port,address,backlog,callback);
  };
}(WebApplication.prototype));

// Define error handling functions
(function(p){
  p.error = function(fn) {
    if (typeof fn !== 'function') {
      debug('Invalid handler:',fn);
      throw new Error('Invalid handler');
    }
    this.errorStack.push(fn);
    return this;
  };
  p.handleError = function(err,handler,context) {
    var app = this;

    var errorContext = {
      __proto__: context,
      next: next,
      error: err,
      handler: handler,
      context: context
    };

    var stack = this.errorStack;
    var index = 0;
    function next(err) {
      if (err) {
        return;
      }
      if (index >= stack.length) {
        debug('End of error handler chain: continueing request-chain');
        return context.__proto__.next();
      }
      var handler = stack[index++];
      debug('Handling error',handler);
      handler(errorContext, next);
    }
    next();
  };
}(WebApplication.prototype));

// Define routing functions on top of 'use'.
(function(p){
  function all(conditions) {
    return function(context) {
      return conditions.every(function(condition) {
        return condition(context);
      });
    };
  }
  function isReqRes(context) {
    return context.req && context.res;
  }
  function isMethod(method) {
    return function(context) {
      return context.req.method.toLowerCase() === method;
    };
  }
  var isGet = isMethod('get');
  var isPost = isMethod('post');
  var isDelete = isMethod('delete');
  var isPut = isMethod('put');
  function isUpgrade(context) {
    return context.req && !context.res && context.socket;
  }
  p.useIf = function(condition,fn) {
    this.stack.push(function(context) {
      if (!condition(context)) {
        return context.next();
      }
      fn(context);
    });
  };
  var routeIf = this.routeIf = function(condition) {
    return function(path,fn) {
      var keys = [];
      var regexp = pathRegexp(path,keys);
      this.useIf(condition,function(context){
        var match = regexp.exec(context.url.pathname);
        debug(context.url.pathname,'matches',path,!!match);
        if (!match) { return context.next(); }
        var params = {};
        for (var i = 0; i < match.length; i++) {
            params[i] = match[i];
        }
        for(var i=0;i<keys.length;i++) {
          params[keys[i]] = decodeURIComponent(match[i+1]);
        }
        fn({
          __proto__: context,
          params: params
        });
      });
    };
  };
  p.get = routeIf(all([isReqRes,isGet]));
  p.post= routeIf(all([isReqRes,isPost]));
  p.del = routeIf(all([isReqRes,isDelete]));
  p.put = routeIf(all([isReqRes,isPut]));
  p.upgrade = routeIf(isUpgrade);
}(WebApplication.prototype));

// Define the websocket routing function.
(function(p) {
  var wsConfig = {
        maxReceivedFrameSize: 0x10000,
        maxReceivedMessageSize: 0x100000,
        fragmentOutgoingMessages: true,
        fragmentationThreshold: 0x4000,
        keepalive: true,
        keepaliveInterval: 20000,
        dropConnectionOnKeepaliveTimeout: true,
        keepaliveGracePeriod: 10000,
        useNativeKeepalive: false,
        assembleFragments: true,
        autoAcceptConnections: false,
        disableNagleAlgorithm: true,
        closeTimeout: 5000
    };

  p.ws = function(path,fn) {
    this.upgrade(path,function(context) {
      var wsReq = new WebSocketRequest(context.socket,context.req,wsConfig);
      try {
        wsReq.readHandshake();
      } catch(e) {
        debug('Error in websocket handshake:',e);
        return context.next();
      }
      fn({
        __proto__: context,
        socket: undefined,
        wsreq: wsReq
      });
    });
  };
}(WebApplication.prototype));

// Define the server-side event routing function.
(function(p) {
  function ServerSideEvents(req,res) {
    var me = this;
    events.EventEmitter.call(me);
    me.req = req;
    me.res = res;
    res.on('close',function() {
      debug('sse response closed');
      me.emit('close');
    });
  }
  util.inherits(ServerSideEvents, events.EventEmitter);
  ServerSideEvents.prototype.send = function(msg) {
    var res = this.res;
    ['id','event','data'].forEach(function(name) {
      var value = msg[name];
      if (!value) { return; }
      value.split('\n').forEach(function(line) {
        var l = name + ': ' + line + '\n';
        res.write(l);
      });
    });
    res.write('\n');
  };
  ServerSideEvents.prototype.close = function() {
    var res = this.res;
    try {
      res.end();
    } catch(e) { }
  };

  p.sse = function(path,fn) {
    this.get(path,function(context) {
      var req = context.req;
      var res = context.res;
      if (req.headers.accept && req.headers.accept === 'text/event-stream') {
        var sseContext = {
        __proto__: context,
          sse: {
            accept: function() {
              res.setHeader('Content-Type','text/event-stream');
              res.writeHead(200);
              return new ServerSideEvents(req,res);
            },
            reject: function reject(code,msg) {
              res.writeHead(code||403,msg);
              res.end();
            }
          }
        };

        fn(sseContext);
      } else {
        context.next();
      }
    });
  };
})(WebApplication.prototype);
module.exports = WebApplication;<commit_msg>Fixed crash for parameters with invalid encoding<commit_after>var debug = require('debug')('unum:webapplication');
var WebSocketRequest = require('websocket').request;
var events = require('events');
var util = require('util');
var URL = require('url');

function merge(a, b){
  if (a && b) {
    for (var key in b) {
      a[key] = b[key];
    }
  }
  return a;
}
function pathRegexp(path, keys) {
  if (path instanceof RegExp) return path;
  if (Array.isArray(path)) path = '(' + path.join('|') + ')';
  path = path
    .replace(/\/\(/g, '(?:/')
    .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
      keys.push(key);
      slash = slash || '';
      return '' +
        slash +
        '(?:' +
        (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' +
        (star ? '(/*)?' : '');
    })
    .replace(/([\/.])/g, '\\$1')
    .replace(/\*/g, '(.*)');
  return new RegExp('^' + path + '$', '');
}

function createHttpServer(handler, configuration) {
  return require('http').createServer(handler);
}
function createHttpsServer(handler, configuration) {
  return require('https').createServer(configuration, handler);
}

// WebApplication class definition
// Accepts HTTP requests and sends them through the handler-chain.
function WebApplication(serverContext,configuration) {
  var me = this;
  configuration = configuration || {};
  me.context = serverContext;
  me.stack = [];
  me.errorStack = [];
  var createServer = configuration.https ? createHttpsServer : createHttpServer;
  function getRequestURL(req) {
    var protocol = req.connection.encrypted ? 'https' : 'http';
    return URL.parse(protocol + '://' + req.headers.host + req.url);
  }
  me.server = createServer(function(req,res) {
    var requestContext = {
      __proto__: me.context,
      req: req,
      res: res,
      url: getRequestURL(req),
      close: function() {
        if (this.error) {
          this.res.statusCode = this.error.status ? this.error.status : 500;
        }
        this.res.end();
      }
    };
    me.handle(requestContext);
  });
  me.server.on('upgrade',function(req,socket,head) {
    var upgradeContext = {
      __proto__: me.context,
      req: req,
      socket: socket,
      url: getRequestURL(req),
      close: function() {
        this.socket.destroy();
      }
    };
    me.handle(upgradeContext);
  });
}

// Define the basic functions of WebApplication
(function(p){
  // Add a handler to the end of the handler-chain.
  p.use = function(fn) {
    if (typeof fn !== 'function') {
      debug('Invalid handler:',fn);
      throw new Error('Invalid handler');
    }
    this.stack.push(fn);
    return this;
  };
  // Executes the handler-chain with the specified context.
  // This is used internally from WebApplication.
  p.handle = function(context) {
    var app = this;

    var appRequestContext = {
      __proto__: context,
      next: next
    };

    var stack = this.stack;
    var index = 0;
    function next(err) {
      if (err) {
        debug('Error occured in handler:',err);
        app.handleError(err,stack[index],appRequestContext);
        return;
      }
      if (index >= stack.length) {
        debug('Handler chain ended');
        app.handleError(new Error('Handler chain ended'),null,appRequestContext);
        return;
      }
      var handler = stack[index++];
      debug('Handling',handler);
      handler(appRequestContext, next);
    }
    next();
  };
  // Start listening for HTTP/HTTPS on the specified port and address.
  p.listen = function(port,address,backlog,callback) {
    this.server.listen(port,address,backlog,callback);
  };
}(WebApplication.prototype));

// Define error handling functions
(function(p){
  p.error = function(fn) {
    if (typeof fn !== 'function') {
      debug('Invalid handler:',fn);
      throw new Error('Invalid handler');
    }
    this.errorStack.push(fn);
    return this;
  };
  p.handleError = function(err,handler,context) {
    var app = this;

    var errorContext = {
      __proto__: context,
      next: next,
      error: err,
      handler: handler,
      context: context
    };

    var stack = this.errorStack;
    var index = 0;
    function next(err) {
      if (err) {
        return;
      }
      if (index >= stack.length) {
        debug('End of error handler chain: continueing request-chain');
        return context.__proto__.next();
      }
      var handler = stack[index++];
      debug('Handling error',handler);
      handler(errorContext, next);
    }
    next();
  };
}(WebApplication.prototype));

// Define routing functions on top of 'use'.
(function(p){
  function all(conditions) {
    return function(context) {
      return conditions.every(function(condition) {
        return condition(context);
      });
    };
  }
  function isReqRes(context) {
    return context.req && context.res;
  }
  function isMethod(method) {
    return function(context) {
      return context.req.method.toLowerCase() === method;
    };
  }
  var isGet = isMethod('get');
  var isPost = isMethod('post');
  var isDelete = isMethod('delete');
  var isPut = isMethod('put');
  function isUpgrade(context) {
    return context.req && !context.res && context.socket;
  }
  p.useIf = function(condition,fn) {
    this.stack.push(function(context) {
      if (!condition(context)) {
        return context.next();
      }
      fn(context);
    });
  };
  var routeIf = this.routeIf = function(condition) {
    return function(path,fn) {
      var keys = [];
      var regexp = pathRegexp(path,keys);
      this.useIf(condition,function(context){
        var match = regexp.exec(context.url.pathname);
        debug(context.url.pathname,'matches',path,!!match);
        if (!match) { return context.next(); }
        var params = {};
        var n = 0;
        for (var i = 0; i < match.length; i++) {
          var key = keys[i-1];
          var val;
          try {
            val = (typeof match[i] === 'string'
              ? decodeURIComponent(match[i])
              : match[i]
            );
          } catch(e) {
            var error = new Error('Invalid parameter '+key+' at group '+i+': '+match[i]);
            error.status = 400;
            return context.next(error);
          }
          if (key) {
            params[key] = val;
          } else {
            params[n++] = val;
          }
        }
        fn({
          __proto__: context,
          params: params
        });
      });
    };
  };
  p.get = routeIf(all([isReqRes,isGet]));
  p.post= routeIf(all([isReqRes,isPost]));
  p.del = routeIf(all([isReqRes,isDelete]));
  p.put = routeIf(all([isReqRes,isPut]));
  p.upgrade = routeIf(isUpgrade);
}(WebApplication.prototype));

// Define the websocket routing function.
(function(p) {
  var wsConfig = {
        maxReceivedFrameSize: 0x10000,
        maxReceivedMessageSize: 0x100000,
        fragmentOutgoingMessages: true,
        fragmentationThreshold: 0x4000,
        keepalive: true,
        keepaliveInterval: 20000,
        dropConnectionOnKeepaliveTimeout: true,
        keepaliveGracePeriod: 10000,
        useNativeKeepalive: false,
        assembleFragments: true,
        autoAcceptConnections: false,
        disableNagleAlgorithm: true,
        closeTimeout: 5000
    };

  p.ws = function(path,fn) {
    this.upgrade(path,function(context) {
      var wsReq = new WebSocketRequest(context.socket,context.req,wsConfig);
      try {
        wsReq.readHandshake();
      } catch(e) {
        debug('Error in websocket handshake:',e);
        return context.next();
      }
      fn({
        __proto__: context,
        socket: undefined,
        wsreq: wsReq
      });
    });
  };
}(WebApplication.prototype));

// Define the server-side event routing function.
(function(p) {
  function ServerSideEvents(req,res) {
    var me = this;
    events.EventEmitter.call(me);
    me.req = req;
    me.res = res;
    res.on('close',function() {
      debug('sse response closed');
      me.emit('close');
    });
  }
  util.inherits(ServerSideEvents, events.EventEmitter);
  ServerSideEvents.prototype.send = function(msg) {
    var res = this.res;
    ['id','event','data'].forEach(function(name) {
      var value = msg[name];
      if (!value) { return; }
      value.split('\n').forEach(function(line) {
        var l = name + ': ' + line + '\n';
        res.write(l);
      });
    });
    res.write('\n');
  };
  ServerSideEvents.prototype.close = function() {
    var res = this.res;
    try {
      res.end();
    } catch(e) { }
  };

  p.sse = function(path,fn) {
    this.get(path,function(context) {
      var req = context.req;
      var res = context.res;
      if (req.headers.accept && req.headers.accept === 'text/event-stream') {
        var sseContext = {
        __proto__: context,
          sse: {
            accept: function() {
              res.setHeader('Content-Type','text/event-stream');
              res.writeHead(200);
              return new ServerSideEvents(req,res);
            },
            reject: function reject(code,msg) {
              res.writeHead(code||403,msg);
              res.end();
            }
          }
        };

        fn(sseContext);
      } else {
        context.next();
      }
    });
  };
})(WebApplication.prototype);
module.exports = WebApplication;<|endoftext|>