<commit_before>/**
 * ゲームメインクラス
 * @param {Object} spec
 * @param {Object} my
 */
function game(spec, my) {
    /**
     * 継承元クラス
     */
    var core = new Core(320, 320);

    /**
     * 定数
     */
    const MAX_PLAYER_NUM = 2;

    /**
     * プライベート変数の宣言
     */
    var socket = spec.socket;
    var roomId = spec.roomId;
    var userId = spec.userId;
    var enemyUserId = spec.enemyUserId;
    var statusMap = {};
    var playerSprite;
    var enemySprite;
    var labelUnitName;
    var labelHp;
    var labelActive;
    var labelBattery;
    var labelEnemyUnitName;
    var labelEnemyHp;
    var labelEnemyActive;
    var labelEnemyBattery;
    var labelEnemySelectBattery;
    var iconPlus;
    var iconMinus;
    var executePhase = waitPhase;
    var playerSelectBatterySprite;
    var inputs = null;
    var atackUserId = null;
    var defenthUserId = null;

    /**
     * 定数の宣言
     */
    const MAX_ACTIVE = 5000;

    /**
     * コンストラクタ
     */
    function constructor() {
        //FPSの設定
        core.fps = 60;

        //キャラクターのステータスをセット
        statusMap[userId] = spec.usersInfo[userId].status;
        statusMap[enemyUserId] = spec.usersInfo[enemyUserId].status;

        //素材のプリロード
        preLoad();

        //プリロード完了後の処理
        core.onload = function() {
            init();
            core.rootScene.addEventListener('enterframe', enterFrame);
        };

        //ゲーム開始処理
        core.start();
    };
    constructor();

    /**
     * 全ユーザの入力を受け取る
     */
    socket.on("resp", function(data) {
        inputs = data.inputs;
    });

    /**
     * リソースのプリロード
     */
    function preLoad(){
        core.preload('/images/'+statusMap[userId].pictName);
        core.preload('/images/'+statusMap[enemyUserId].pictName);
        core.preload('/images/plus.png');
        core.preload('/images/minus.png');
        core.preload('/images/Battery.png');
    }

    /**
     * リフレッシュレートごとの処理
     * @param {Object} e
     */
    function enterFrame(e) {
        refreshMertor();
        executePhase();
    }

    /**
     * 初期化
     */
    function init(){

        for(var uid in statusMap){
            statusMap[uid].active = 0;
            statusMap[uid].battery = 5;
            statusMap[uid].selectBattery = 0;
        }

        playerSprite = new Sprite(128, 128);
        playerSprite.image = core.assets['/images/'+statusMap[userId].pictName];
        playerSprite.x = 192;
        playerSprite.y = 80;
        core.rootScene.addChild(playerSprite);

        enemySprite = new Sprite(128, 128);
        enemySprite.image = core.assets['/images/'+statusMap[enemyUserId].pictName];
        enemySprite.x = 0;
        enemySprite.y = 80;
        enemySprite.scaleX = -1;
        core.rootScene.addChild(enemySprite);

        labelUnitName = new Label();
        labelUnitName.x = 200;
        labelUnitName.y = 0;
        labelUnitName.color = '#fff';
        core.rootScene.addChild(labelUnitName);

        labelHp = new Label('HP');
        labelHp.x = 200;
        labelHp.y = 16;
        labelHp.color = '#fff';
        core.rootScene.addChild(labelHp);

        labelActive = new Label('Active');
        labelActive.x = 200;
        labelActive.y = 32;
        labelActive.color = '#fff';
        core.rootScene.addChild(labelActive);

        labelBattery = new Label('Battery');
        labelBattery.x = 200;
        labelBattery.y = 48;
        labelBattery.color = '#fff';
        core.rootScene.addChild(labelBattery);

        labelEnemyUnitName = new Label();
        labelEnemyUnitName.x = 32;
        labelEnemyUnitName.y = 0;
        labelEnemyUnitName.color = '#fff';
        core.rootScene.addChild(labelEnemyUnitName);

        labelEnemyHp = new Label('HP');
        labelEnemyHp.x = 32;
        labelEnemyHp.y = 16;
        labelEnemyHp.color = '#fff';
        core.rootScene.addChild(labelEnemyHp);

        labelEnemyActive = new Label('Active');
        labelEnemyActive.x = 32;
        labelEnemyActive.y = 32;
        labelEnemyActive.color = '#fff';
        core.rootScene.addChild(labelEnemyActive);

        labelEnemyBattery = new Label('Battery');
        labelEnemyBattery.x = 32;
        labelEnemyBattery.y = 48;
        labelEnemyBattery.color = '#fff';
        core.rootScene.addChild(labelEnemyBattery);

        labelEnemySelectBattery = new Label('');
        labelEnemySelectBattery.x = 32;
        labelEnemySelectBattery.y = 64;
        labelEnemySelectBattery.color = '#fff';
        core.rootScene.addChild(labelEnemySelectBattery);

        iconPlus = new Sprite(64, 64);
        iconPlus.image = core.assets['/images/plus.png'];
        iconPlus.x = 256;
        iconPlus.y = 180;
        iconPlus.addEventListener(Event.TOUCH_START,function(e){
            playerSelectBatterySprite.value += 1;
            playerSelectBatterySprite.frame = playerSelectBatterySprite.value;
        });

        iconMinus = new Sprite(64, 64);
        iconMinus.image = core.assets['/images/minus.png'];
        iconMinus.x = 180;
        iconMinus.y = 180;
        iconMinus.addEventListener(Event.TOUCH_START,function(e){
            playerSelectBatterySprite.value += -1;
            playerSelectBatterySprite.frame = playerSelectBatterySprite.value;
        });

        playerSelectBatterySprite = new Sprite(64,64);
        playerSelectBatterySprite.image = core.assets['/images/Battery.png'];
        playerSelectBatterySprite.x = 220;
        playerSelectBatterySprite.y = 100;
        playerSelectBatterySprite.frame = 1;
        playerSelectBatterySprite.value = 1;
        playerSelectBatterySprite.addEventListener(Event.TOUCH_START,function(e){
            //アイコンを消す
            core.rootScene.removeChild(playerSelectBatterySprite);
            core.rootScene.removeChild(iconPlus);
            core.rootScene.removeChild(iconMinus);

            //入寮情報を送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : playerSelectBatterySprite.value
            });
        });

        core.rootScene.backgroundColor = "black";
    }

    /**
     * メータ系更新
     */
    function refreshMertor() {
        labelHp.text = 'HP ' + statusMap[userId].hp;
        labelActive.text = 'Active ' + statusMap[userId].active;
        labelBattery.text = 'Battery' + statusMap[userId].battery;

        labelEnemyHp.text = 'HP ' + statusMap[enemyUserId].hp;
        labelEnemyActive.text = 'Active ' + statusMap[enemyUserId].active;
        labelEnemyBattery.text = 'Battery' + statusMap[enemyUserId].battery;
    }

    /**
     * ウェイトフェイズ
     */
    function waitPhase(){
        //アクティブゲージを加算
        for(var uid in statusMap){
            statusMap[uid].active += statusMap[uid].speed;
        }

        if(statusMap[userId].active >= MAX_ACTIVE){
            //プレイヤーを攻撃側に設定する
            atackUserId = userId;
            defenthUserId = enemyUserId;

            //コマンドボタンを表示する
            core.rootScene.addChild(iconPlus);
            core.rootScene.addChild(iconMinus);
            core.rootScene.addChild(playerSelectBatterySprite);

            //攻撃バッテリー決定フェイズへ
            executePhase = atackBatteryPhase;
        } else if(statusMap[enemyUserId].active >= MAX_ACTIVE) {
            //敵を攻撃側に設定する
            atackUserId = enemyUserId;
            defenthUserId = userId;

            //コマンドを送信する
            //待ちフェイズの場合、OKという文字を入力としてサーバへ送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : 'OK'
            });

            //攻撃バッテリー決定フェイズに遷移
            executePhase = atackBatteryPhase;
        }
    }

    /**
     * 攻撃バッテリー決定フェイズ
     */
    function atackBatteryPhase(){
        //サーバからのレスポンスがない場合、関数を終了する
        if(inputs === null){
            return;
        }

        //サーバの入力を受け取る
        statusMap[atackUserId].selectBattery = inputs[atackUserId];
        inputs = null;

        //攻撃側がプレイヤーの場合
        if (atackUserId === userId) {
            //コマンドを送信する
            //待ちフェイズの場合、OKという文字を入力としてサーバへ送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : 'OK'
            });
        }
        //攻撃側が敵の場合
        else {
            //コマンドボタンを表示する
            core.rootScene.addChild(iconPlus);
            core.rootScene.addChild(iconMinus);
            core.rootScene.addChild(playerSelectBatterySprite);
        }

        //防御バッテリー決定待フェイズへ遷移
        executePhase = defenthBatteryPhase;
    }

    /**
     * 防御バッテリー決定フェイズ
     */
    function defenthBatteryPhase(){
        //サーバからのレスポンスがない場合、関数を終了する
        if(inputs === null){
            return;
        }

        //サーバの入力を受け取る
        statusMap[defenthUserId].selectBattery = inputs[defenthUserId];
        inputs = null;

        console.log('player Battery : ' + statusMap[userId].selectBattery);
        console.log('enemy Battery : ' + statusMap[enemyUserId].selectBattery);

    }

    return core;
};<commit_msg>バッテリー表示フェイズを追加した。<commit_after>/**
 * ゲームメインクラス
 * @param {Object} spec
 * @param {Object} my
 */
function game(spec, my) {
    /**
     * 継承元クラス
     */
    var core = new Core(320, 320);

    /**
     * 定数
     */
    const MAX_PLAYER_NUM = 2;

    /**
     * プライベート変数の宣言
     */
    var socket = spec.socket;
    var roomId = spec.roomId;
    var userId = spec.userId;
    var enemyUserId = spec.enemyUserId;
    var statusMap = {};
    var playerSprite;
    var enemySprite;
    var labelUnitName;
    var labelHp;
    var labelActive;
    var labelBattery;
    var labelEnemyUnitName;
    var labelEnemyHp;
    var labelEnemyActive;
    var labelEnemyBattery;
    var iconPlus;
    var iconMinus;
    var executePhase = waitPhase;
    var playerSelectBatterySprite;
    var enemySelectBatterySprite;
    var inputs = null;
    var atackUserId = null;
    var defenthUserId = null;

    /**
     * 定数の宣言
     */
    const MAX_ACTIVE = 5000;

    /**
     * コンストラクタ
     */
    function constructor() {
        //FPSの設定
        core.fps = 60;

        //キャラクターのステータスをセット
        statusMap[userId] = spec.usersInfo[userId].status;
        statusMap[enemyUserId] = spec.usersInfo[enemyUserId].status;

        //素材のプリロード
        preLoad();

        //プリロード完了後の処理
        core.onload = function() {
            init();
            core.rootScene.addEventListener('enterframe', enterFrame);
        };

        //ゲーム開始処理
        core.start();
    };
    constructor();

    /**
     * 全ユーザの入力を受け取る
     */
    socket.on("resp", function(data) {
        inputs = data.inputs;
    });

    /**
     * リソースのプリロード
     */
    function preLoad(){
        core.preload('/images/'+statusMap[userId].pictName);
        core.preload('/images/'+statusMap[enemyUserId].pictName);
        core.preload('/images/plus.png');
        core.preload('/images/minus.png');
        core.preload('/images/Battery.png');
    }

    /**
     * リフレッシュレートごとの処理
     * @param {Object} e
     */
    function enterFrame(e) {
        refreshMertor();
        executePhase();
    }

    /**
     * 初期化
     */
    function init(){

        for(var uid in statusMap){
            statusMap[uid].active = 0;
            statusMap[uid].battery = 5;
            statusMap[uid].selectBattery = 0;
        }

        playerSprite = new Sprite(128, 128);
        playerSprite.image = core.assets['/images/'+statusMap[userId].pictName];
        playerSprite.x = 192;
        playerSprite.y = 80;
        core.rootScene.addChild(playerSprite);

        enemySprite = new Sprite(128, 128);
        enemySprite.image = core.assets['/images/'+statusMap[enemyUserId].pictName];
        enemySprite.x = 0;
        enemySprite.y = 80;
        enemySprite.scaleX = -1;
        core.rootScene.addChild(enemySprite);

        labelUnitName = new Label();
        labelUnitName.x = 200;
        labelUnitName.y = 0;
        labelUnitName.color = '#fff';
        core.rootScene.addChild(labelUnitName);

        labelHp = new Label('HP');
        labelHp.x = 200;
        labelHp.y = 16;
        labelHp.color = '#fff';
        core.rootScene.addChild(labelHp);

        labelActive = new Label('Active');
        labelActive.x = 200;
        labelActive.y = 32;
        labelActive.color = '#fff';
        core.rootScene.addChild(labelActive);

        labelBattery = new Label('Battery');
        labelBattery.x = 200;
        labelBattery.y = 48;
        labelBattery.color = '#fff';
        core.rootScene.addChild(labelBattery);

        labelEnemyUnitName = new Label();
        labelEnemyUnitName.x = 32;
        labelEnemyUnitName.y = 0;
        labelEnemyUnitName.color = '#fff';
        core.rootScene.addChild(labelEnemyUnitName);

        labelEnemyHp = new Label('HP');
        labelEnemyHp.x = 32;
        labelEnemyHp.y = 16;
        labelEnemyHp.color = '#fff';
        core.rootScene.addChild(labelEnemyHp);

        labelEnemyActive = new Label('Active');
        labelEnemyActive.x = 32;
        labelEnemyActive.y = 32;
        labelEnemyActive.color = '#fff';
        core.rootScene.addChild(labelEnemyActive);

        labelEnemyBattery = new Label('Battery');
        labelEnemyBattery.x = 32;
        labelEnemyBattery.y = 48;
        labelEnemyBattery.color = '#fff';
        core.rootScene.addChild(labelEnemyBattery);

        iconPlus = new Sprite(64, 64);
        iconPlus.image = core.assets['/images/plus.png'];
        iconPlus.x = 256;
        iconPlus.y = 180;
        iconPlus.addEventListener(Event.TOUCH_START,function(e){
            playerSelectBatterySprite.frame ++;
        });

        iconMinus = new Sprite(64, 64);
        iconMinus.image = core.assets['/images/minus.png'];
        iconMinus.x = 180;
        iconMinus.y = 180;
        iconMinus.addEventListener(Event.TOUCH_START,function(e){
            playerSelectBatterySprite.frame ++;
        });

        playerSelectBatterySprite = new Sprite(64,64);
        playerSelectBatterySprite.image = core.assets['/images/Battery.png'];
        playerSelectBatterySprite.x = 220;
        playerSelectBatterySprite.y = 100;
        playerSelectBatterySprite.frame = 1;
        playerSelectBatterySprite.addEventListener(Event.TOUCH_START,function(e){
            //アイコンを消す
            core.rootScene.removeChild(playerSelectBatterySprite);
            core.rootScene.removeChild(iconPlus);
            core.rootScene.removeChild(iconMinus);

            //入寮情報を送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : playerSelectBatterySprite.frame
            });
        });

        enemySelectBatterySprite = new Sprite(64,64);
        enemySelectBatterySprite.image = core.assets['/images/Battery.png'];
        enemySelectBatterySprite.x = 32;
        enemySelectBatterySprite.y = 100;
        enemySelectBatterySprite.frame = 1;
        enemySelectBatterySprite.value = 1;

        core.rootScene.backgroundColor = "black";
    }

    /**
     * メータ系更新
     */
    function refreshMertor() {
        labelHp.text = 'HP ' + statusMap[userId].hp;
        labelActive.text = 'Active ' + statusMap[userId].active;
        labelBattery.text = 'Battery' + statusMap[userId].battery;

        labelEnemyHp.text = 'HP ' + statusMap[enemyUserId].hp;
        labelEnemyActive.text = 'Active ' + statusMap[enemyUserId].active;
        labelEnemyBattery.text = 'Battery' + statusMap[enemyUserId].battery;
    }

    /**
     * ウェイトフェイズ
     */
    function waitPhase(){
        //アクティブゲージを加算
        for(var uid in statusMap){
            statusMap[uid].active += statusMap[uid].speed;
        }

        if(statusMap[userId].active >= MAX_ACTIVE){
            //プレイヤーを攻撃側に設定する
            atackUserId = userId;
            defenthUserId = enemyUserId;

            //コマンドボタンを表示する
            core.rootScene.addChild(iconPlus);
            core.rootScene.addChild(iconMinus);
            core.rootScene.addChild(playerSelectBatterySprite);

            //攻撃バッテリー決定フェイズへ
            executePhase = atackBatteryPhase;
        } else if(statusMap[enemyUserId].active >= MAX_ACTIVE) {
            //敵を攻撃側に設定する
            atackUserId = enemyUserId;
            defenthUserId = userId;

            //コマンドを送信する
            //待ちフェイズの場合、OKという文字を入力としてサーバへ送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : 'OK'
            });

            //攻撃バッテリー決定フェイズに遷移
            executePhase = atackBatteryPhase;
        }
    }

    /**
     * 攻撃バッテリー決定フェイズ
     */
    function atackBatteryPhase(){
        //サーバからのレスポンスがない場合、関数を終了する
        if(inputs === null){
            return;
        }

        //サーバの入力を受け取る
        statusMap[atackUserId].selectBattery = inputs[atackUserId];
        inputs = null;

        //攻撃側がプレイヤーの場合
        if (atackUserId === userId) {
            //コマンドを送信する
            //待ちフェイズの場合、OKという文字を入力としてサーバへ送信する
            socket.emit("input", {
                roomId : roomId,
                userId : userId,
                input : 'OK'
            });
        }
        //攻撃側が敵の場合
        else {
            //コマンドボタンを表示する
            core.rootScene.addChild(iconPlus);
            core.rootScene.addChild(iconMinus);
            core.rootScene.addChild(playerSelectBatterySprite);
        }

        //防御バッテリー決定待フェイズへ遷移
        executePhase = defenthBatteryPhase;
    }

    /**
     * 防御バッテリー決定フェイズ
     */
    function defenthBatteryPhase(){
        //サーバからのレスポンスがない場合、関数を終了する
        if(inputs === null){
            return;
        }

        //サーバの入力を受け取る
        statusMap[defenthUserId].selectBattery = inputs[defenthUserId];
        inputs = null;

        //バッテリー表示の準備
        playerSelectBatterySprite.frame = statusMap[userId].selectBattery;
        enemySelectBatterySprite.frame = statusMap[enemyUserId].selectBattery;
        core.rootScene.addChild(playerSelectBatterySprite);
        core.rootScene.addChild(enemySelectBatterySprite);

        //バッテリー表示フェイズに遷移
        executePhase　= viewBatteryPhase;
    }

    /**
     * バッテリー表示フェイズ
     */
    function viewBatteryPhase() {

    }

    return core;
};<|endoftext|>


<commit_before>/*global Java, exports, org, __plugin */
var bkEventPriority = org.bukkit.event.EventPriority,
  bkEventExecutor = org.bukkit.plugin.EventExecutor,
  bkRegisteredListener = org.bukkit.plugin.RegisteredListener,
  bkEventPackage = 'org.bukkit.event.';

var nashorn = (typeof Java != 'undefined');

function getHandlerListForEventType( eventType ){
  var result = null;
  var clazz = null;
  if (nashorn) {

    //Nashorn doesn't like when getHandlerList is in a superclass of your event
    //so to avoid this problem, call getHandlerList using java.lang.reflect
    //methods
    clazz = eventType['class'];
    result = clazz.getMethod("getHandlerList").invoke(null);

  } else {
    result = eventType.getHandlerList();
  }

  return result;
}
exports.on = function(
  /* Java Class */
  eventType,
  /* function( registeredListener, event) */
  handler,
  /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */
  priority   ) {
  var handlerList,
    regd,
    eventExecutor;

  if ( typeof priority == 'undefined' ) {
    priority = bkEventPriority.HIGHEST;
  } else {
    priority = bkEventPriority[priority.toUpperCase().trim()];
  }
  handlerList = getHandlerListForEventType (eventType);

  var result = { };
  eventExecutor = new bkEventExecutor( {
    execute: function( l, evt ) {
      function cancel(){
        if (evt instanceof org.bukkit.event.Cancellable){
          evt.setCancelled(true);
        }
      }
      /*
       let handlers use this.cancel() to cancel the current event
       or this.unregister() to unregister from future events.
       */
      var bound = {};
      for (var i in result){
        bound[i] = result[i];
      }
      bound.cancel = cancel;
      handler.call( bound, evt, cancel );
    }
  } );
  /*
   wph 20130222 issue #64 bad interaction with Essentials plugin
   if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener)
   then BOOM! the other plugin will throw an error because Rhino can't coerce an
   equals() method from an Interface.
   The workaround is to make the ScriptCraftPlugin java class a Listener.
   Should only unregister() registered plugins in ScriptCraft js code.
   */
  regd = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, true );
  handlerList.register( regd );
  result.unregister = function(){
    handlerList.unregister( regd );
  };
  return result;
};
<commit_msg>Fix issue #256 and fix issue #287<commit_after>/*global Java, exports, org, __plugin */
var bkEventPriority = org.bukkit.event.EventPriority,
  bkEventExecutor = org.bukkit.plugin.EventExecutor,
  bkRegisteredListener = org.bukkit.plugin.RegisteredListener,
  bkEventPackage = 'org.bukkit.event.';

var nashorn = (typeof Java != 'undefined');

function getHandlerListForEventType( eventType ){
  var result = null;
  var clazz = null;
  if (nashorn) {

    //Nashorn doesn't like when getHandlerList is in a superclass of your event
    //so to avoid this problem, call getHandlerList using java.lang.reflect
    //methods
    clazz = eventType['class'];
    result = clazz.getMethod("getHandlerList").invoke(null);

  } else {
    result = eventType.getHandlerList();
  }

  return result;
}
exports.on = function(
  /* Java Class */
  eventType,
  /* function( registeredListener, event) */
  handler,
  /* (optional) String (HIGH, HIGHEST, LOW, LOWEST, NORMAL, MONITOR), */
  priority   ) {
  var handlerList,
    regd,
    eventExecutor;

  if ( typeof priority == 'undefined' ) {
    priority = bkEventPriority.HIGHEST;
  } else {
    priority = bkEventPriority[priority.toUpperCase().trim()];
  }
  handlerList = getHandlerListForEventType (eventType);

  var result = { };
  eventExecutor = new bkEventExecutor( {
    execute: function( l, evt ) {
      function cancel(){
        if (evt instanceof org.bukkit.event.Cancellable){
          evt.setCancelled(true);
        }
      }
      /*
       let handlers use this.cancel() to cancel the current event
       or this.unregister() to unregister from future events.
       */
      var bound = {};
      for (var i in result){
        bound[i] = result[i];
      }
      bound.cancel = cancel;
      handler.call( bound, evt, cancel );
    }
  } );
  /*
   wph 20130222 issue #64 bad interaction with Essentials plugin
   if another plugin tries to unregister a Listener (not a Plugin or a RegisteredListener)
   then BOOM! the other plugin will throw an error because Rhino can't coerce an
   equals() method from an Interface.
   The workaround is to make the ScriptCraftPlugin java class a Listener.
   Should only unregister() registered plugins in ScriptCraft js code.
   */
  regd = new bkRegisteredListener( __plugin, eventExecutor, priority, __plugin, false );
  handlerList.register( regd );
  result.unregister = function(){
    handlerList.unregister( regd );
  };
  return result;
};
<|endoftext|>


<commit_before>const authMiddleware = require('../helpers/authMiddleware.js');
const requestTable = require('../database/tables/requests.js');
const userTable = require('../database/tables/users.js');
const userHelper = require('../helpers/userHelper.js');
const requestHelper = require('../helpers/requestHelper.js');
const formatDates = require('../helpers/dateHelper.js');
const errorMessages = require('../constants/errorMessages.js');
const dateHelper = require('../helpers/dateHelper.js');

module.exports = {
  method: 'GET',
  path:'/dashboard',
  config: {
    auth: {
      strategy: 'base'
    },
    pre: [
        { method: authMiddleware, assign: 'user' }
    ],
    handler: (req, reply) => {
      if (req.pre.user.admin === true) {
        requestTable.retrieve(function (err, dashboardData) {
          if (err) {
            return reply.view('error', {
              error: errorMessages.databaseError
            });
          }
          let dashboardDataDate = formatDates.fixDate(dashboardData);
          let formattedDashData = dashboardDataDate.map(function(request) {
            request.activeCap = requestHelper.formatStatus(request.active);
            if (request.admin_names) {
              request.admin_names = userHelper.getFirstName(request.admin_names);
              request.admin_family = userHelper.getLastName(request.admin_family);
            }
            return request;
          });
          userTable.retrieveAdmins(function (err, adminData) {
            if (err) {
              return reply.view('error', {
                error: errorMessages.databaseError
              });
            }
            let formattedAdminData = adminData.map(function(admin) {
              return {
                firstName: userHelper.getFirstName(admin.given_names),
                lastName: userHelper.getLastName(admin.family_name),
                id: admin.user_id
              }
            });
            return reply.view('dashboard', {
              requests: formattedDashData,
              admins: formattedAdminData
            });
          });
        });
      } else {
        return reply.view('error', {
          error : errorMessages.authenticationError
        });
      }
    }
  }
};
<commit_msg>Refactor dashboard.js<commit_after>const authMiddleware = require('../helpers/authMiddleware.js');
const requestTable = require('../database/tables/requests.js');
const userTable = require('../database/tables/users.js');
const userHelper = require('../helpers/userHelper.js');
const requestHelper = require('../helpers/requestHelper.js');
const formatDates = require('../helpers/dateHelper.js');
const errorMessages = require('../constants/errorMessages.js');
const dateHelper = require('../helpers/dateHelper.js');

module.exports = {
  method: 'GET',
  path:'/dashboard',
  config: {
    auth: {
      strategy: 'base'
    },
    pre: [
        { method: authMiddleware, assign: 'user' }
    ],
    handler: (req, reply) => {
      if (req.pre.user.admin === true) {
        requestTable.retrieve(function (err, dashboardData) {
          if (err) {
            return reply.view('error', {
              error: errorMessages.databaseError
            });
          }
          console.log('1 ',dashboardData);
          let formattedDashboardData = dashboardData.map(function (request) {
            request.birth_date = request.birth_date.toString().slice(0,15);
            request.time_stamp = request.time_stamp.toString().slice(0,21);
            request.activeCap = requestHelper.formatStatus(request.active);
            if (request.admin_names) {
              request.admin_names = userHelper.getFirstName(request.admin_names);
              request.admin_family = userHelper.getLastName(request.admin_family);
            }
            return request;
          });
          console.log('2 ',dashboardData);
          console.log('3 ',formattedDashboardData);
          userTable.retrieveAdmins(function (err, adminData) {
            if (err) {
              return reply.view('error', {
                error: errorMessages.databaseError
              });
            }
            let formattedAdminData = adminData.map(function(admin) {
              return {
                firstName: userHelper.getFirstName(admin.given_names),
                lastName: userHelper.getLastName(admin.family_name),
                id: admin.user_id
              }
            });
            return reply.view('dashboard', {
              requests: formattedDashboardData,
              admins: formattedAdminData
            });
          });
        });
      } else {
        return reply.view('error', {
          error : errorMessages.authenticationError
        });
      }
    }
  }
};
<|endoftext|>


<commit_before>'use strict';

const PIXI = require('pixi.js');
const Renderer = require('incheon').render.Renderer;
const Utils= require('./Utils');

const Missile = require('../common/Missile');
const Ship = require('../common/Ship');
const ShipActor = require('./ShipActor');

/**
 * Renderer for the Spaaace client - based on Pixi.js
 */
class SpaaaceRenderer extends Renderer {

    get ASSETPATHS(){
        return {
            ship: 'assets/ship1.png',
            missile: 'assets/shot.png',
            bg1: 'assets/space3.png',
            bg2: 'assets/space2.png',
            bg3: 'assets/clouds2.png',
            bg4: 'assets/clouds1.png',
            smokeParticle: 'assets/smokeparticle.png'
        }
    }

    constructor(gameEngine) {
        super(gameEngine);
        this.sprites = {};
        this.isReady = false;

        //asset prefix
        this.assetPathPrefix = this.gameEngine.options.assetPathPrefix?this.gameEngine.options.assetPathPrefix:'';

        // these define how many gameWorlds the player ship has "scrolled" through
        this.bgPhaseX = 0;
        this.bgPhaseY = 0;
    }

    init() {
        this.viewportWidth = window.innerWidth;
        this.viewportHeight = window.innerHeight;

        this.stage = new PIXI.Container();
        this.layer1 = new PIXI.Container();
        this.layer2 = new PIXI.Container();

        this.stage.addChild(this.layer1, this.layer2);

        this.renderer = PIXI.autoDetectRenderer(this.viewportWidth, this.viewportHeight);
        document.addEventListener('DOMContentLoaded', ()=>{
            document.body.querySelector('.pixiContainer').appendChild(this.renderer.view);
        });

        return new Promise((resolve, reject)=>{
            PIXI.loader.add(Object.keys(this.ASSETPATHS).map((x)=>{
                return{
                    name: x,
                    url: this.assetPathPrefix +this.ASSETPATHS[x]
                }
            }))
            .load(() => {
                this.isReady = true;
                this.setupStage();
                this.gameEngine.emit('renderer.ready');
                resolve();
            });
        });
    }

    setupStage() {
        window.addEventListener('resize', ()=>{ this.setRendererSize(); });

        this.camera = new PIXI.Container();
        this.camera.addChild(this.layer1, this.layer2);

        // parallax background
        this.bg1 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg1.texture,
                this.viewportWidth, this.viewportHeight);
        this.bg2 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg2.texture,
            this.viewportWidth, this.viewportHeight);
        this.bg3 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg3.texture,
            this.viewportWidth, this.viewportHeight);
        this.bg4 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg4.texture,
            this.viewportWidth, this.viewportHeight);

        this.bg3.blendMode = PIXI.BLEND_MODES.ADD;
        this.bg4.blendMode = PIXI.BLEND_MODES.ADD;
        this.bg4.alpha = 0.6;

        this.stage.addChild(this.bg1, this.bg2, this.bg3, this.bg4);
        this.stage.addChild(this.camera);

        // this.debug= new PIXI.Graphics();
        // this.stage.addChild(this.debug);

        this.elapsedTime = Date.now();

        // events
        this.gameEngine.on('client.keyChange', (e)=>{
            if (this.playerShip) {
                if (e.keyName == 'up') {
                    this.playerShip.actor.thrustEmitter.emit = e.isDown;
                }
            }
        });

        // debug
        if ('showworldbounds' in Utils.getUrlVars()) {
            let graphics = new PIXI.Graphics();
            graphics.beginFill(0xFFFFFF);
            graphics.alpha = 0.1;
            graphics.drawRect(0, 0, this.gameEngine.worldSettings.width, this.gameEngine.worldSettings.height);
            this.camera.addChild(graphics);
        }

    }

    setRendererSize() {
        this.viewportWidth = window.innerWidth;
        this.viewportHeight = window.innerHeight;

        this.bg1.width = this.viewportWidth;
        this.bg1.height = this.viewportHeight;
        this.bg2.width = this.viewportWidth;
        this.bg2.height = this.viewportHeight;
        this.bg3.width = this.viewportWidth;
        this.bg3.height = this.viewportHeight;
        this.bg4.width = this.viewportWidth;
        this.bg4.height = this.viewportHeight;

        this.renderer.resize(this.viewportWidth, this.viewportHeight);
    }

    draw() {
        super.draw();

        let now = Date.now();

        if (!this.isReady) return; // assets might not have been loaded yet
        let worldWidth = this.gameEngine.worldSettings.width;
        let worldHeight = this.gameEngine.worldSettings.height;

        let viewportSeesRightBound = this.camera.x < this.viewportWidth - worldWidth;
        let viewportSeesLeftBound = this.camera.x > 0;
        let viewportSeesTopBound = this.camera.y > 0;
        let viewportSeesBottomBound = this.camera.y < this.viewportHeight - worldHeight;

        for (let objId of Object.keys(this.sprites)) {
            let objData = this.gameEngine.world.objects[objId];
            let sprite = this.sprites[objId];

            if (objData) {

                // if the object requests a "showThrust" then invoke it in the actor
                if ((sprite !== this.playerShip) && sprite.actor) {
                    sprite.actor.thrustEmitter.emit = !!objData.showThrust;
                }

                if (sprite == this.playerShip) {
                    if (objData.x - sprite.x < -worldWidth/2) { this.bgPhaseX++; }
                    if (objData.x - sprite.x > worldWidth/2) { this.bgPhaseX--; }
                    if (objData.y - sprite.y < -worldHeight/2) { this.bgPhaseY++; }
                    if (objData.y - sprite.y > worldHeight/2) { this.bgPhaseY--; }
                }

                if (objData.class == Ship && sprite != this.playerShip) {
                    this.updateOffscreenIndicator(objData);
                }

                sprite.x = objData.x;
                sprite.y = objData.y;
                sprite.rotation = this.gameEngine.world.objects[objId].angle * Math.PI/180;

                // make the wraparound seamless for objects other than the player ship
                if (sprite != this.playerShip && viewportSeesLeftBound && objData.x > this.viewportWidth - this.camera.x) {
                    sprite.x = objData.x - worldWidth;
                }
                if (sprite != this.playerShip && viewportSeesRightBound && objData.x < -this.camera.x) {
                    sprite.x = objData.x + worldWidth;
                }
                if (sprite != this.playerShip && viewportSeesTopBound && objData.y > this.viewportHeight - this.camera.y) {
                    sprite.y = objData.y - worldHeight;
                }
                if (sprite != this.playerShip && viewportSeesBottomBound && objData.y < -this.camera.y) {
                    sprite.y = objData.y + worldHeight;
                }
            }

            if (sprite) {
                // object is either a Pixi sprite or an Actor. Actors have renderSteps
                if (sprite.actor && sprite.actor.renderStep) {
                    sprite.actor.renderStep(now - this.elapsedTime);
                }
            }
        }

        if(this.playerShip) {
            let bgOffsetX = -this.bgPhaseX * worldWidth - this.playerShip.x;
            let bgOffsetY = -this.bgPhaseY * worldHeight - this.playerShip.y;

            // let bgOffsetX = this.bgPhaseX * worldWidth + this.camera.x;
            // let bgOffsetY = this.bgPhaseY * worldHeight + this.camera.y;

            this.bg1.tilePosition.x = bgOffsetX * 0.01;
            this.bg1.tilePosition.y = bgOffsetY * 0.01;

            this.bg2.tilePosition.x = bgOffsetX * 0.04;
            this.bg2.tilePosition.y = bgOffsetY * 0.04;

            this.bg3.tilePosition.x = bgOffsetX * 0.3;
            this.bg3.tilePosition.y = bgOffsetY * 0.3;

            this.bg4.tilePosition.x = bgOffsetX * 0.45;
            this.bg4.tilePosition.y = bgOffsetY * 0.45;

            if ('cameraroam' in Utils.getUrlVars()) {
                // always center the playership, do this smoothly
                let targetCamX = this.viewportWidth / 2 - this.playerShip.x;
                let targetCamY = this.viewportHeight / 2 - this.playerShip.y;
                let xCamDelta = (targetCamX - this.camera.x);
                let yCamDelta = (targetCamY - this.camera.y);
                console.log(xCamDelta, yCamDelta);

                if (xCamDelta > worldWidth/2) {
                    this.camera.x = this.camera.x + worldWidth + xCamDelta / 50;
                }
                if (xCamDelta < -worldWidth/2) {
                    this.camera.x = this.camera.x - worldWidth + xCamDelta / 50;
                } else{
                    this.camera.x = this.camera.x + xCamDelta / 50;
                }

                this.camera.y = this.camera.y + yCamDelta / 50;
            } else{
                this.camera.x = this.viewportWidth/2 - this.playerShip.x;
                this.camera.y = this.viewportHeight/2 - this.playerShip.y;
            }
        }

        this.elapsedTime = now;

        // Render the stage
        this.renderer.render(this.stage);
    }

    addObject(objData, options) {
        let sprite;

        if (objData.class == Ship) {
            let shipActor = new ShipActor(this);
            sprite = shipActor.sprite;
            this.sprites[objData.id] = sprite;
            sprite.id = objData.id;

            if (objData.isPlayerControlled) {
                this.playerShip = sprite; // save reference to the player ship
                sprite.actor.shipSprite.tint = 0XFF00FF; // color  player ship
                document.body.classList.remove("lostGame");
            }
            else {
                this.addOffscreenIndicator(objData);
            }

        } else if (objData.class == Missile) {
            sprite = new PIXI.Sprite(PIXI.loader.resources.missile.texture);
            this.sprites[objData.id] = sprite;

            sprite.width = 81 * 0.5;
            sprite.height = 46 * 0.5;

            sprite.anchor.set(0.5, 0.5);
        }

        sprite.position.set(objData.x, objData.y);
        this.layer2.addChild(sprite);

        Object.assign(sprite, options);

        return sprite;
    }

    removeObject(obj) {
        if (this.playerShip && obj.id == this.playerShip.id) {
            this.playerShip = null;
        }

        if (obj.class == Ship && this.playerShip && obj.id != this.playerShip.id) {
            this.removeOffscreenIndicator(obj);

        }

        let sprite = this.sprites[obj.id];
        if (sprite.actor) {
            // removal "takes time"
            sprite.actor.destroy().then(()=>{
                console.log('deleted sprite');
                delete this.sprites[obj.id];
            });
        } else{
            this.sprites[obj.id].destroy();
            delete this.sprites[obj.id];
        }
    }

    addOffscreenIndicator(objData) {
        let container = document.querySelector('#offscreenIndicatorContainer');
        let indicatorEl = document.createElement('div');
        indicatorEl.setAttribute('id','offscreenIndicator'+objData.id);
        indicatorEl.classList.add('offscreenIndicator');
        container.appendChild(indicatorEl);
    }

    updateOffscreenIndicator(objData){
        //player ship might have been destroyed
        if (!this.playerShip) return;

        let indicatorEl = document.querySelector('#offscreenIndicator'+objData.id);
        if (!indicatorEl) {
            console.error(`No indicatorEl found with id ${objData.id}`);
            return;
        }
        let playerShipObj = this.gameEngine.world.objects[this.playerShip.id];
        let slope = (objData.y - playerShipObj.y)/(objData.x - playerShipObj.x);
        let b = this.viewportHeight/2;

        // this.debug.clear();
        // this.debug.lineStyle(1, 0xFF0000 ,1);
        // this.debug.moveTo(this.viewportWidth/2,this.viewportHeight/2);
        // this.debug.lineTo(this.viewportWidth/2 + b/-slope, 0);
        // this.debug.endFill();

        let padding = 30;
        let indicatorPos = { x: 0, y: 0};

        if (objData.y < playerShipObj.y - this.viewportHeight/2) {
            indicatorPos.x = this.viewportWidth/2 + (padding - b)/slope;
            indicatorPos.y = padding;
        } else if (objData.y > playerShipObj.y + this.viewportHeight/2) {
            indicatorPos.x = this.viewportWidth/2 + (this.viewportHeight - padding - b)/slope;
            indicatorPos.y = this.viewportHeight - padding;
        }

        if (objData.x < playerShipObj.x - this.viewportWidth/2) {
            indicatorPos.x = padding;
            indicatorPos.y = slope * (-this.viewportWidth/2 + padding) + b;
        } else if (objData.x > playerShipObj.x + this.viewportWidth/2) {
            indicatorPos.x = this.viewportWidth - padding;
            indicatorPos.y = slope * (this.viewportWidth/2 - padding) + b;
        }

        if (indicatorPos.x == 0 && indicatorPos.y == 0){
            indicatorEl.style.opacity = 0;
        }
        else{
            indicatorEl.style.opacity = 1;
            let rotation = Math.atan2(objData.y - playerShipObj.y,objData.x - playerShipObj.x);
            rotation = rotation * 180/Math.PI; //rad2deg
            indicatorEl.style.transform = `translateX(${indicatorPos.x}px) translateY(${indicatorPos.y}px) rotate(${rotation}deg) `;
        }
    }

    removeOffscreenIndicator(objData) {
        let indicatorEl = document.querySelector('#offscreenIndicator'+objData.id);
        indicatorEl.parentNode.removeChild(indicatorEl);
    }

}

module.exports = SpaaaceRenderer;
<commit_msg>Renderer: append pixi container if DOM already loaded<commit_after>'use strict';

const PIXI = require('pixi.js');
const Renderer = require('incheon').render.Renderer;
const Utils= require('./Utils');

const Missile = require('../common/Missile');
const Ship = require('../common/Ship');
const ShipActor = require('./ShipActor');

/**
 * Renderer for the Spaaace client - based on Pixi.js
 */
class SpaaaceRenderer extends Renderer {

    get ASSETPATHS(){
        return {
            ship: 'assets/ship1.png',
            missile: 'assets/shot.png',
            bg1: 'assets/space3.png',
            bg2: 'assets/space2.png',
            bg3: 'assets/clouds2.png',
            bg4: 'assets/clouds1.png',
            smokeParticle: 'assets/smokeparticle.png'
        }
    }

    constructor(gameEngine) {
        super(gameEngine);
        this.sprites = {};
        this.isReady = false;

        //asset prefix
        this.assetPathPrefix = this.gameEngine.options.assetPathPrefix?this.gameEngine.options.assetPathPrefix:'';

        // these define how many gameWorlds the player ship has "scrolled" through
        this.bgPhaseX = 0;
        this.bgPhaseY = 0;
    }

    init() {
        this.viewportWidth = window.innerWidth;
        this.viewportHeight = window.innerHeight;

        this.stage = new PIXI.Container();
        this.layer1 = new PIXI.Container();
        this.layer2 = new PIXI.Container();

        this.stage.addChild(this.layer1, this.layer2);

        this.renderer = PIXI.autoDetectRenderer(this.viewportWidth, this.viewportHeight);
        if (document.readyState === "complete" || document.readyState === "loaded") {
            document.body.querySelector('.pixiContainer').appendChild(this.renderer.view);
        } else{
            document.addEventListener('DOMContentLoaded', ()=>{
                document.body.querySelector('.pixiContainer').appendChild(this.renderer.view);
            });
        }

        return new Promise((resolve, reject)=>{
            PIXI.loader.add(Object.keys(this.ASSETPATHS).map((x)=>{
                return{
                    name: x,
                    url: this.assetPathPrefix +this.ASSETPATHS[x]
                }
            }))
            .load(() => {
                this.isReady = true;
                this.setupStage();
                this.gameEngine.emit('renderer.ready');
                resolve();
            });
        });
    }

    setupStage() {
        window.addEventListener('resize', ()=>{ this.setRendererSize(); });

        this.camera = new PIXI.Container();
        this.camera.addChild(this.layer1, this.layer2);

        // parallax background
        this.bg1 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg1.texture,
                this.viewportWidth, this.viewportHeight);
        this.bg2 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg2.texture,
            this.viewportWidth, this.viewportHeight);
        this.bg3 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg3.texture,
            this.viewportWidth, this.viewportHeight);
        this.bg4 = new PIXI.extras.TilingSprite(PIXI.loader.resources.bg4.texture,
            this.viewportWidth, this.viewportHeight);

        this.bg3.blendMode = PIXI.BLEND_MODES.ADD;
        this.bg4.blendMode = PIXI.BLEND_MODES.ADD;
        this.bg4.alpha = 0.6;

        this.stage.addChild(this.bg1, this.bg2, this.bg3, this.bg4);
        this.stage.addChild(this.camera);

        // this.debug= new PIXI.Graphics();
        // this.stage.addChild(this.debug);

        this.elapsedTime = Date.now();

        // events
        this.gameEngine.on('client.keyChange', (e)=>{
            if (this.playerShip) {
                if (e.keyName == 'up') {
                    this.playerShip.actor.thrustEmitter.emit = e.isDown;
                }
            }
        });

        // debug
        if ('showworldbounds' in Utils.getUrlVars()) {
            let graphics = new PIXI.Graphics();
            graphics.beginFill(0xFFFFFF);
            graphics.alpha = 0.1;
            graphics.drawRect(0, 0, this.gameEngine.worldSettings.width, this.gameEngine.worldSettings.height);
            this.camera.addChild(graphics);
        }

    }

    setRendererSize() {
        this.viewportWidth = window.innerWidth;
        this.viewportHeight = window.innerHeight;

        this.bg1.width = this.viewportWidth;
        this.bg1.height = this.viewportHeight;
        this.bg2.width = this.viewportWidth;
        this.bg2.height = this.viewportHeight;
        this.bg3.width = this.viewportWidth;
        this.bg3.height = this.viewportHeight;
        this.bg4.width = this.viewportWidth;
        this.bg4.height = this.viewportHeight;

        this.renderer.resize(this.viewportWidth, this.viewportHeight);
    }

    draw() {
        super.draw();

        let now = Date.now();

        if (!this.isReady) return; // assets might not have been loaded yet
        let worldWidth = this.gameEngine.worldSettings.width;
        let worldHeight = this.gameEngine.worldSettings.height;

        let viewportSeesRightBound = this.camera.x < this.viewportWidth - worldWidth;
        let viewportSeesLeftBound = this.camera.x > 0;
        let viewportSeesTopBound = this.camera.y > 0;
        let viewportSeesBottomBound = this.camera.y < this.viewportHeight - worldHeight;

        for (let objId of Object.keys(this.sprites)) {
            let objData = this.gameEngine.world.objects[objId];
            let sprite = this.sprites[objId];

            if (objData) {

                // if the object requests a "showThrust" then invoke it in the actor
                if ((sprite !== this.playerShip) && sprite.actor) {
                    sprite.actor.thrustEmitter.emit = !!objData.showThrust;
                }

                if (sprite == this.playerShip) {
                    if (objData.x - sprite.x < -worldWidth/2) { this.bgPhaseX++; }
                    if (objData.x - sprite.x > worldWidth/2) { this.bgPhaseX--; }
                    if (objData.y - sprite.y < -worldHeight/2) { this.bgPhaseY++; }
                    if (objData.y - sprite.y > worldHeight/2) { this.bgPhaseY--; }
                }

                if (objData.class == Ship && sprite != this.playerShip) {
                    this.updateOffscreenIndicator(objData);
                }

                sprite.x = objData.x;
                sprite.y = objData.y;
                sprite.rotation = this.gameEngine.world.objects[objId].angle * Math.PI/180;

                // make the wraparound seamless for objects other than the player ship
                if (sprite != this.playerShip && viewportSeesLeftBound && objData.x > this.viewportWidth - this.camera.x) {
                    sprite.x = objData.x - worldWidth;
                }
                if (sprite != this.playerShip && viewportSeesRightBound && objData.x < -this.camera.x) {
                    sprite.x = objData.x + worldWidth;
                }
                if (sprite != this.playerShip && viewportSeesTopBound && objData.y > this.viewportHeight - this.camera.y) {
                    sprite.y = objData.y - worldHeight;
                }
                if (sprite != this.playerShip && viewportSeesBottomBound && objData.y < -this.camera.y) {
                    sprite.y = objData.y + worldHeight;
                }
            }

            if (sprite) {
                // object is either a Pixi sprite or an Actor. Actors have renderSteps
                if (sprite.actor && sprite.actor.renderStep) {
                    sprite.actor.renderStep(now - this.elapsedTime);
                }
            }
        }

        if(this.playerShip) {
            let bgOffsetX = -this.bgPhaseX * worldWidth - this.playerShip.x;
            let bgOffsetY = -this.bgPhaseY * worldHeight - this.playerShip.y;

            // let bgOffsetX = this.bgPhaseX * worldWidth + this.camera.x;
            // let bgOffsetY = this.bgPhaseY * worldHeight + this.camera.y;

            this.bg1.tilePosition.x = bgOffsetX * 0.01;
            this.bg1.tilePosition.y = bgOffsetY * 0.01;

            this.bg2.tilePosition.x = bgOffsetX * 0.04;
            this.bg2.tilePosition.y = bgOffsetY * 0.04;

            this.bg3.tilePosition.x = bgOffsetX * 0.3;
            this.bg3.tilePosition.y = bgOffsetY * 0.3;

            this.bg4.tilePosition.x = bgOffsetX * 0.45;
            this.bg4.tilePosition.y = bgOffsetY * 0.45;

            if ('cameraroam' in Utils.getUrlVars()) {
                // always center the playership, do this smoothly
                let targetCamX = this.viewportWidth / 2 - this.playerShip.x;
                let targetCamY = this.viewportHeight / 2 - this.playerShip.y;
                let xCamDelta = (targetCamX - this.camera.x);
                let yCamDelta = (targetCamY - this.camera.y);
                console.log(xCamDelta, yCamDelta);

                if (xCamDelta > worldWidth/2) {
                    this.camera.x = this.camera.x + worldWidth + xCamDelta / 50;
                }
                if (xCamDelta < -worldWidth/2) {
                    this.camera.x = this.camera.x - worldWidth + xCamDelta / 50;
                } else{
                    this.camera.x = this.camera.x + xCamDelta / 50;
                }

                this.camera.y = this.camera.y + yCamDelta / 50;
            } else{
                this.camera.x = this.viewportWidth/2 - this.playerShip.x;
                this.camera.y = this.viewportHeight/2 - this.playerShip.y;
            }
        }

        this.elapsedTime = now;

        // Render the stage
        this.renderer.render(this.stage);
    }

    addObject(objData, options) {
        let sprite;

        if (objData.class == Ship) {
            let shipActor = new ShipActor(this);
            sprite = shipActor.sprite;
            this.sprites[objData.id] = sprite;
            sprite.id = objData.id;

            if (objData.isPlayerControlled) {
                this.playerShip = sprite; // save reference to the player ship
                sprite.actor.shipSprite.tint = 0XFF00FF; // color  player ship
                document.body.classList.remove("lostGame");
            }
            else {
                this.addOffscreenIndicator(objData);
            }

        } else if (objData.class == Missile) {
            sprite = new PIXI.Sprite(PIXI.loader.resources.missile.texture);
            this.sprites[objData.id] = sprite;

            sprite.width = 81 * 0.5;
            sprite.height = 46 * 0.5;

            sprite.anchor.set(0.5, 0.5);
        }

        sprite.position.set(objData.x, objData.y);
        this.layer2.addChild(sprite);

        Object.assign(sprite, options);

        return sprite;
    }

    removeObject(obj) {
        if (this.playerShip && obj.id == this.playerShip.id) {
            this.playerShip = null;
        }

        if (obj.class == Ship && this.playerShip && obj.id != this.playerShip.id) {
            this.removeOffscreenIndicator(obj);

        }

        let sprite = this.sprites[obj.id];
        if (sprite.actor) {
            // removal "takes time"
            sprite.actor.destroy().then(()=>{
                console.log('deleted sprite');
                delete this.sprites[obj.id];
            });
        } else{
            this.sprites[obj.id].destroy();
            delete this.sprites[obj.id];
        }
    }

    addOffscreenIndicator(objData) {
        let container = document.querySelector('#offscreenIndicatorContainer');
        let indicatorEl = document.createElement('div');
        indicatorEl.setAttribute('id','offscreenIndicator'+objData.id);
        indicatorEl.classList.add('offscreenIndicator');
        container.appendChild(indicatorEl);
    }

    updateOffscreenIndicator(objData){
        //player ship might have been destroyed
        if (!this.playerShip) return;

        let indicatorEl = document.querySelector('#offscreenIndicator'+objData.id);
        if (!indicatorEl) {
            console.error(`No indicatorEl found with id ${objData.id}`);
            return;
        }
        let playerShipObj = this.gameEngine.world.objects[this.playerShip.id];
        let slope = (objData.y - playerShipObj.y)/(objData.x - playerShipObj.x);
        let b = this.viewportHeight/2;

        // this.debug.clear();
        // this.debug.lineStyle(1, 0xFF0000 ,1);
        // this.debug.moveTo(this.viewportWidth/2,this.viewportHeight/2);
        // this.debug.lineTo(this.viewportWidth/2 + b/-slope, 0);
        // this.debug.endFill();

        let padding = 30;
        let indicatorPos = { x: 0, y: 0};

        if (objData.y < playerShipObj.y - this.viewportHeight/2) {
            indicatorPos.x = this.viewportWidth/2 + (padding - b)/slope;
            indicatorPos.y = padding;
        } else if (objData.y > playerShipObj.y + this.viewportHeight/2) {
            indicatorPos.x = this.viewportWidth/2 + (this.viewportHeight - padding - b)/slope;
            indicatorPos.y = this.viewportHeight - padding;
        }

        if (objData.x < playerShipObj.x - this.viewportWidth/2) {
            indicatorPos.x = padding;
            indicatorPos.y = slope * (-this.viewportWidth/2 + padding) + b;
        } else if (objData.x > playerShipObj.x + this.viewportWidth/2) {
            indicatorPos.x = this.viewportWidth - padding;
            indicatorPos.y = slope * (this.viewportWidth/2 - padding) + b;
        }

        if (indicatorPos.x == 0 && indicatorPos.y == 0){
            indicatorEl.style.opacity = 0;
        }
        else{
            indicatorEl.style.opacity = 1;
            let rotation = Math.atan2(objData.y - playerShipObj.y,objData.x - playerShipObj.x);
            rotation = rotation * 180/Math.PI; //rad2deg
            indicatorEl.style.transform = `translateX(${indicatorPos.x}px) translateY(${indicatorPos.y}px) rotate(${rotation}deg) `;
        }
    }

    removeOffscreenIndicator(objData) {
        let indicatorEl = document.querySelector('#offscreenIndicator'+objData.id);
        indicatorEl.parentNode.removeChild(indicatorEl);
    }

}

module.exports = SpaaaceRenderer;
<|endoftext|>


<commit_before>var http = require('http'),
    zlib = require('zlib'),
    url = require('url'),
    extend = require('extend'),
    AdvancedAgent = require('./advanced_agent'),
    AskerError = require('./error');

/**
 * @typedef {Object} AgentOptions
 * @property {String}  [name=globalAgent] Agent name, use 'globalAgent' for http.globalAgent
 * @property {Number}  [maxSockets=1024] Pool size, used only if new agent defined
 * @property {Boolean} [persistent=true] Non-persistent agents removed when queue empty
 */

/**
 * @typedef  {Object} RequestOptions
 * @property {String}   [url] shorthand alternative for host, port and path options
 * @property {String}   [host=localhost]
 * @property {Number}   [port=80]
 * @property {String}   [path=/]
 * @property {String}   [method=GET] HTTP-method
 * @property {Object}   [headers] HTTP headers hash
 * @property {Object}   [query] Query params hash
 * @property {String}   [requestId=''] Request identifier for error messages
 * @property {*}        [body] Request body, JSON.stringify applies if Object
 * @property {Number}   [maxRetries=0] Max number of allowed retries for request
 * @property {Function} [onretry] (reason Error, retryCount Number) called on retries
 * @property {Number}   [timeout=500] Socket timeout
 * @property {Number}   [queueTimeout=timeout+50] Queue timeout
 * @property {Boolean}  [allowGzip=true] Allows response compression with gzip
 * @property {Function} [statusFilter] (code Number) Filter which determines acceptable status codes
 *      by default only 200 and 201 codes acceptable and retries allowed for all codes, except range from 400 to 499.
 *      Must returns object { accept : Boolean, isRetryAllowed : Boolean }.
 * @property {AgentOptions} [agent] http.Agent options
 */

/**
 * Create new instance and calls the `execute()` method if called as function (without `new`).
 *
 * @constructor
 * @param {RequestOptions} options object with only `host` field required
 * @param {Function} callback
 */
function Request(options, callback) {
    var parsedUrl,
        parsedPath,
        acceptEncoding;

    if ( ! (this instanceof Request)) {
        return (new Request(options, callback)).execute();
    }

    this.options = extend({}, Request.DEFAULT_OPTIONS, options);

    this._callback = callback;
    this._onretry = this.options.onretry;

    // uppercase method
    this.options.method = this.options.method.toUpperCase();

    if (typeof this.options.statusFilter === 'function') {
        this.statusCodeFilter = this.options.statusFilter;
    }

    // lowercase headers names
    this.options.headers = (options && options.headers) ?
        Object.keys(this.options.headers).reduce(function(headers, headerName) {
            headers[headerName.toLowerCase()] = options.headers[headerName];

            return headers;
        }, {}) :
        {};

    if (this.options.url) {
        if (this.options.url.indexOf('http://') !== 0) {
            this.options.url = 'http://' + this.options.url;
        }

        parsedUrl = url.parse(this.options.url, true);
        this.options.host = parsedUrl.hostname;
        this.options.port = parseInt(parsedUrl.port, 10);
        this.options.path = parsedUrl.path;

        if ( ! this.options.port) {
            this.options.port = Request.DEFAULT_OPTIONS.port;
        }
    }

    // rebuild path with query params
    // `query` hash properties has higher priority than the specified in the `path` string does
    if (this.options.query) {
        parsedPath = url.parse(this.options.path, true);

        // `search` prop has higher priority than the `query` then remove it
        delete parsedPath.search;
        extend(parsedPath.query, this.options.query);

        this.options.path = url.format(parsedPath);
    }

    // build request body for some methods
    if (typeof this.options.body !== 'undefined') {
        this.options.body = (typeof this.options.body === 'object') ?
            JSON.stringify(this.options.body) :
            String(this.options.body);

        // @todo does we must support encodings rather than 'utf8'?
        if ( ! this.options.headers['content-length']) {
            this.options.headers['content-length'] = Buffer.byteLength(this.options.body, 'utf8');
        }
    } else {
        delete this.options.body;
    }

    if (this.options.allowGzip) {
        acceptEncoding = this.options.headers['accept-encoding'] || '*';

        if (acceptEncoding.indexOf('gzip') === -1) {
            this.options.headers['accept-encoding'] = 'gzip, ' + acceptEncoding;
        }
    }

    this.agent = Request.getAgent(this);
}

Request.Error = AskerError;

// default Request options
Request.DEFAULT_OPTIONS = {
    host : 'localhost',
    port : 80,
    path : '/',
    method : 'GET',
    maxRetries : 0,
    timeout : 500,
    allowGzip : true,
    requestId : ''
};

// if not defined queueTimeout = timeout + QUEUE_TIMEOUT_DELTA
Request.prototype.QUEUE_TIMEOUT_DELTA = 50;

// pool of Agents. Yes, it's pool of pools
Request.agentsPool = {};

http.globalAgent.maxSockets = 1024;
http.Agent.defaultMaxSockets = 1024;

/**
 * @param {AgentOptions} options
 * @returns {AdvancedAgent}
 */
Request.createAgent = function(options) {
    var agentsPool = this.agentsPool,
        agent = new AdvancedAgent(extend({ persistent : true }, options));

    agentsPool[agent.options.name] = agent;

    // Setup `removeSocket` event listener for non-persistnt agents.
    // Destroy agent then requests queue and sockets pool became empty.
    if ( ! agent.options.persistent) {
        agent.on(AdvancedAgent.EVENTS.SOCKET_REMOVED, function() {
            if (( ! this.requests.keys || this.requests.keys().length === 0) &&
                ( ! this.sockets.keys || this.sockets.keys().length === 0)) {
                delete agentsPool[agent.options.name];
            }
        });
    }

    return agent;
};

/**
 * Returns existing agent from agents pool or creates new if no one exists
 * @param {Request} request instance
 * @returns {Agent|AdvancedAgent}
 */
Request.getAgent = function(request) {
    var options = request.options.agent,
        agent = false;

    // returns `http.globalAgent` if agent name is undefined or equals 'globalAgent'
    if ( ! options || ! options.name || options.name === 'globalAgent') {
        agent = http.globalAgent;
    } else if (Object.prototype.hasOwnProperty.call(this.agentsPool, options.name)) {
        agent = this.agentsPool[options.name];
    }

    return agent || this.createAgent(options);
};

/**
 * Accept or decline status code, allows or decline retries
 * @param {Number} code
 * @returns {{accept : Boolean, isRetryAllowed : Boolean}}
 */
Request.prototype.statusCodeFilter = function(code) {
    return {
        accept : code === 200 || code === 201,
        isRetryAllowed : 400 > code || code > 499
    };
};

/**
 * @returns {{network: (undefined|number), total: (undefined|number)}}
 */
Request.prototype.getTimers = function() {
    var now = Date.now();

    return {
        network : this._timeNetworkStart && ((this._timeNetworkEnd || now) - this._timeNetworkStart),
        total : this._timeExecuteStart && (now - this._timeExecuteStart)
    };
};

/**
 * timestamp for error messages in the '[in XX~YY ms]' format
 * XX – time between socket was assigned for request and response or error retrieved
 * YY – time about `execute` method call and response or error retrieved
 * @returns {String}
 */
Request.prototype.formatTimestamp = function() {
    var timers = this.getTimers();

    return ['in ', timers.network || '0', '~', timers.total, ' ms'].join('');
};

/**
 * @returns {{time:{network:number,total:number},retries:{used:number,limit:number}}}
 */
Request.prototype.getResponseMetaBase = function() {
    return {
        time : this.getTimers(),
        options : this.options,
        retries : {
            used : this.retries,
            limit : this.options.maxRetries
        }
    };
};

/**
 * @memberOf Request.prototype
 * @field {boolean} isRunning
 */
Object.defineProperty(Request.prototype, 'isRunning', {
    get : function() {
        return !! this._isRunning;
    },
    enumerable : true
});

/**
 * @param {Error} err
 * @param {Object} [data]
 * @returns {*}
 */
Request.prototype.done = function(err, data) {
    this._isRunning = false;

    return this._callback(err, data);
};

/**
 * Successfully resolve request
 * @param {Number} code http status code
 * @param {*} response data
 * @param {Object} [meta]
 * @param {Object} headers http response headers
 */
Request.prototype.resolve = function(code, response, meta, headers) {
    this.done(null, {
        data : response,
        statusCode : code,
        headers : headers,
        meta : meta ? extend(this.getResponseMetaBase(), meta) : this.getResponseMetaBase()
    });
};

/**
 * Handle request retries. Throw an error if retries limit exceeded.
 * @param {Object} requestOptions same as for _tryHttpRequest
 * @param {AskerError} retryReason error which is a reason for retry
 * @private
 */
Request.prototype._retryHttpRequest = function(requestOptions, retryReason) {
    var errorData = {
            requestId : this.options.requestId,
            timings : this.formatTimestamp(),
            maxRetries : this.options.maxRetries,
            url : [
                requestOptions.host,
                requestOptions.port ? ':' + requestOptions.port : '',
                requestOptions.path
            ].join('')
        };

    retryReason.bind(errorData);

    if (this.retries >= this.options.maxRetries) {
        // retries limit exceeded
        // throw an RETRIES_LIMIT_EXCEEDED if retries allowed for request
        // or retry reason error in another case
        if (this.options.maxRetries > 0) {
            // @todo throw following error with `reason` prop which contains retryReason
            // so user code can determine limits exceeded errors
            retryReason = AskerError
                .createError(AskerError.CODES.RETRIES_LIMIT_EXCEEDED, retryReason)
                .bind(errorData);
        }
        this.done(retryReason);
    } else {
        this.retries++;
        this._onretry && this._onretry(retryReason, this.retries);
        this._tryHttpRequest(requestOptions);
    }
};

/**
 * run the request
 * @param options Параметры запроса, аналогичны параметрам http.request
 * @private
 */
Request.prototype._tryHttpRequest = function(options) {
    var self = this,
        url = options.host + ':' + options.port + options.path,
        retryRequest = this._retryHttpRequest.bind(this, options),
        httpRequest,
        statusFilterResult;

    httpRequest = http.request(options, function(res) {
        var contentLen = parseInt(res.headers['content-length'], 10),
            isContentLength = ! isNaN(contentLen) && contentLen > 0,
            offset = 0,
            error,
            body;

        if (isContentLength) {
            body = new Buffer(contentLen);
            body.fill(0);
        }

        statusFilterResult = self.statusCodeFilter(res.statusCode);

        if ( ! statusFilterResult.accept) {
            httpRequest.break(true);
            error = AskerError.createError(
                AskerError.CODES.UNEXPECTED_STATUS_CODE, { statusCode : res.statusCode, url : url });
            return statusFilterResult.isRetryAllowed ? retryRequest(error) : self.done(error);
        }

        res.on('data', function(chunk) {
            var isBuffer = Buffer.isBuffer(chunk),
                chunkBuffer;

            httpRequest.clearTimeouts();

            if (isContentLength) {
                offset += isBuffer ? chunk.copy(body, offset) : body.write(chunk, offset);
            } else {
                chunkBuffer = isBuffer ? chunk : new Buffer(chunk);
                body = typeof body !== 'undefined' ? Buffer.concat([body, chunkBuffer]) : chunkBuffer;
            }
        });

        res.on('end', function() {
            var encoding = res.headers['content-encoding'];

            // for sure ;)
            httpRequest.clearTimeouts();

            if (encoding && encoding.toLowerCase().indexOf('gzip') > -1) {
                zlib.gunzip(body, function(err, deflatedBody) {
                    error ?
                        self.done(AskerError.createError(AskerError.CODES.GUNZIP_ERROR, error)) :
                        self.resolve(res.statusCode, deflatedBody.toString(), { gzip : true }, res.headers);
                });
            } else {
                self.resolve(res.statusCode, typeof body !== 'undefined' ? body.toString() : null, {}, res.headers);
            }
        });
    });

    httpRequest.clearTimeouts = function() {
        httpRequest.socketTimeout && clearTimeout(httpRequest.socketTimeout);
        httpRequest.queueTimeout && clearTimeout(httpRequest.queueTimeout);
    };

    httpRequest.break = function(abort, errorCode, errorData) {
        httpRequest.clearTimeouts();

        if (abort) {
            httpRequest.rejected = true;
            httpRequest.abort();
        }

        httpRequest.emit('removeSocket');
        self._timeNetworkEnd = Date.now();

        errorCode && retryRequest(AskerError.createError(errorCode, errorData));
    };

    // setup queue timeout
    httpRequest.queueTimeout = setTimeout(function() {
        httpRequest.break(true, AskerError.CODES.QUEUE_TIMEOUT);
    }, isNaN(options.queueTimeout) ? options.timeout + self.QUEUE_TIMEOUT_DELTA : options.queueTimeout);

    httpRequest.on('socket', function() {
        self._timeNetworkStart = Date.now();

        httpRequest.socketTimeout = setTimeout(function() {
            httpRequest.break(true, AskerError.CODES.SOCKET_TIMEOUT);
        }, options.timeout);
    });

    httpRequest.on('error', function(error) {
        // request manually aborted
        ! httpRequest.rejected && httpRequest.break(false, AskerError.CODES.HTTP_CLIENT_REQUEST_ERROR, error);
    });

    // send request body
    this.options.body && httpRequest.write(this.options.body);

    httpRequest.end();
};

/**
 * Execute request
 * @returns {boolean} `false` if request already running
 */
Request.prototype.execute = function() {
    if (this.isRunning) {
        return false;
    }

    this.retries = 0;
    this._timeExecuteStart = Date.now();
    this._tryHttpRequest(extend({}, this.options, { agent : this.agent }));

    return true;
};

module.exports = Request;
<commit_msg>fix #44 correct Request#isRunning value<commit_after>var http = require('http'),
    zlib = require('zlib'),
    url = require('url'),
    extend = require('extend'),
    AdvancedAgent = require('./advanced_agent'),
    AskerError = require('./error');

/**
 * @typedef {Object} AgentOptions
 * @property {String}  [name=globalAgent] Agent name, use 'globalAgent' for http.globalAgent
 * @property {Number}  [maxSockets=1024] Pool size, used only if new agent defined
 * @property {Boolean} [persistent=true] Non-persistent agents removed when queue empty
 */

/**
 * @typedef  {Object} RequestOptions
 * @property {String}   [url] shorthand alternative for host, port and path options
 * @property {String}   [host=localhost]
 * @property {Number}   [port=80]
 * @property {String}   [path=/]
 * @property {String}   [method=GET] HTTP-method
 * @property {Object}   [headers] HTTP headers hash
 * @property {Object}   [query] Query params hash
 * @property {String}   [requestId=''] Request identifier for error messages
 * @property {*}        [body] Request body, JSON.stringify applies if Object
 * @property {Number}   [maxRetries=0] Max number of allowed retries for request
 * @property {Function} [onretry] (reason Error, retryCount Number) called on retries
 * @property {Number}   [timeout=500] Socket timeout
 * @property {Number}   [queueTimeout=timeout+50] Queue timeout
 * @property {Boolean}  [allowGzip=true] Allows response compression with gzip
 * @property {Function} [statusFilter] (code Number) Filter which determines acceptable status codes
 *      by default only 200 and 201 codes acceptable and retries allowed for all codes, except range from 400 to 499.
 *      Must returns object { accept : Boolean, isRetryAllowed : Boolean }.
 * @property {AgentOptions} [agent] http.Agent options
 */

/**
 * Create new instance and calls the `execute()` method if called as function (without `new`).
 *
 * @constructor
 * @param {RequestOptions} options object with only `host` field required
 * @param {Function} callback
 */
function Request(options, callback) {
    var parsedUrl,
        parsedPath,
        acceptEncoding;

    if ( ! (this instanceof Request)) {
        return (new Request(options, callback)).execute();
    }

    this.options = extend({}, Request.DEFAULT_OPTIONS, options);

    this._callback = callback;
    this._onretry = this.options.onretry;

    // uppercase method
    this.options.method = this.options.method.toUpperCase();

    if (typeof this.options.statusFilter === 'function') {
        this.statusCodeFilter = this.options.statusFilter;
    }

    // lowercase headers names
    this.options.headers = (options && options.headers) ?
        Object.keys(this.options.headers).reduce(function(headers, headerName) {
            headers[headerName.toLowerCase()] = options.headers[headerName];

            return headers;
        }, {}) :
        {};

    if (this.options.url) {
        if (this.options.url.indexOf('http://') !== 0) {
            this.options.url = 'http://' + this.options.url;
        }

        parsedUrl = url.parse(this.options.url, true);
        this.options.host = parsedUrl.hostname;
        this.options.port = parseInt(parsedUrl.port, 10);
        this.options.path = parsedUrl.path;

        if ( ! this.options.port) {
            this.options.port = Request.DEFAULT_OPTIONS.port;
        }
    }

    // rebuild path with query params
    // `query` hash properties has higher priority than the specified in the `path` string does
    if (this.options.query) {
        parsedPath = url.parse(this.options.path, true);

        // `search` prop has higher priority than the `query` then remove it
        delete parsedPath.search;
        extend(parsedPath.query, this.options.query);

        this.options.path = url.format(parsedPath);
    }

    // build request body for some methods
    if (typeof this.options.body !== 'undefined') {
        this.options.body = (typeof this.options.body === 'object') ?
            JSON.stringify(this.options.body) :
            String(this.options.body);

        // @todo does we must support encodings rather than 'utf8'?
        if ( ! this.options.headers['content-length']) {
            this.options.headers['content-length'] = Buffer.byteLength(this.options.body, 'utf8');
        }
    } else {
        delete this.options.body;
    }

    if (this.options.allowGzip) {
        acceptEncoding = this.options.headers['accept-encoding'] || '*';

        if (acceptEncoding.indexOf('gzip') === -1) {
            this.options.headers['accept-encoding'] = 'gzip, ' + acceptEncoding;
        }
    }

    this.agent = Request.getAgent(this);
}

Request.Error = AskerError;

// default Request options
Request.DEFAULT_OPTIONS = {
    host : 'localhost',
    port : 80,
    path : '/',
    method : 'GET',
    maxRetries : 0,
    timeout : 500,
    allowGzip : true,
    requestId : ''
};

// if not defined queueTimeout = timeout + QUEUE_TIMEOUT_DELTA
Request.prototype.QUEUE_TIMEOUT_DELTA = 50;

// pool of Agents. Yes, it's pool of pools
Request.agentsPool = {};

http.globalAgent.maxSockets = 1024;
http.Agent.defaultMaxSockets = 1024;

/**
 * @param {AgentOptions} options
 * @returns {AdvancedAgent}
 */
Request.createAgent = function(options) {
    var agentsPool = this.agentsPool,
        agent = new AdvancedAgent(extend({ persistent : true }, options));

    agentsPool[agent.options.name] = agent;

    // Setup `removeSocket` event listener for non-persistnt agents.
    // Destroy agent then requests queue and sockets pool became empty.
    if ( ! agent.options.persistent) {
        agent.on(AdvancedAgent.EVENTS.SOCKET_REMOVED, function() {
            if (( ! this.requests.keys || this.requests.keys().length === 0) &&
                ( ! this.sockets.keys || this.sockets.keys().length === 0)) {
                delete agentsPool[agent.options.name];
            }
        });
    }

    return agent;
};

/**
 * Returns existing agent from agents pool or creates new if no one exists
 * @param {Request} request instance
 * @returns {Agent|AdvancedAgent}
 */
Request.getAgent = function(request) {
    var options = request.options.agent,
        agent = false;

    // returns `http.globalAgent` if agent name is undefined or equals 'globalAgent'
    if ( ! options || ! options.name || options.name === 'globalAgent') {
        agent = http.globalAgent;
    } else if (Object.prototype.hasOwnProperty.call(this.agentsPool, options.name)) {
        agent = this.agentsPool[options.name];
    }

    return agent || this.createAgent(options);
};

/**
 * Accept or decline status code, allows or decline retries
 * @param {Number} code
 * @returns {{accept : Boolean, isRetryAllowed : Boolean}}
 */
Request.prototype.statusCodeFilter = function(code) {
    return {
        accept : code === 200 || code === 201,
        isRetryAllowed : 400 > code || code > 499
    };
};

/**
 * @returns {{network: (undefined|number), total: (undefined|number)}}
 */
Request.prototype.getTimers = function() {
    var now = Date.now();

    return {
        network : this._timeNetworkStart && ((this._timeNetworkEnd || now) - this._timeNetworkStart),
        total : this._timeExecuteStart && (now - this._timeExecuteStart)
    };
};

/**
 * timestamp for error messages in the '[in XX~YY ms]' format
 * XX – time between socket was assigned for request and response or error retrieved
 * YY – time about `execute` method call and response or error retrieved
 * @returns {String}
 */
Request.prototype.formatTimestamp = function() {
    var timers = this.getTimers();

    return ['in ', timers.network || '0', '~', timers.total, ' ms'].join('');
};

/**
 * @returns {{time:{network:number,total:number},retries:{used:number,limit:number}}}
 */
Request.prototype.getResponseMetaBase = function() {
    return {
        time : this.getTimers(),
        options : this.options,
        retries : {
            used : this.retries,
            limit : this.options.maxRetries
        }
    };
};

/**
 * @memberOf Request.prototype
 * @field {boolean} isRunning
 */
Object.defineProperty(Request.prototype, 'isRunning', {
    get : function() {
        return !! this._isRunning;
    },
    enumerable : true
});

/**
 * @param {Error} err
 * @param {Object} [data]
 * @returns {*}
 */
Request.prototype.done = function(err, data) {
    this._isRunning = false;

    return this._callback(err, data);
};

/**
 * Successfully resolve request
 * @param {Number} code http status code
 * @param {*} response data
 * @param {Object} [meta]
 * @param {Object} headers http response headers
 */
Request.prototype.resolve = function(code, response, meta, headers) {
    this.done(null, {
        data : response,
        statusCode : code,
        headers : headers,
        meta : meta ? extend(this.getResponseMetaBase(), meta) : this.getResponseMetaBase()
    });
};

/**
 * Handle request retries. Throw an error if retries limit exceeded.
 * @param {Object} requestOptions same as for _tryHttpRequest
 * @param {AskerError} retryReason error which is a reason for retry
 * @private
 */
Request.prototype._retryHttpRequest = function(requestOptions, retryReason) {
    var errorData = {
            requestId : this.options.requestId,
            timings : this.formatTimestamp(),
            maxRetries : this.options.maxRetries,
            url : [
                requestOptions.host,
                requestOptions.port ? ':' + requestOptions.port : '',
                requestOptions.path
            ].join('')
        };

    retryReason.bind(errorData);

    if (this.retries >= this.options.maxRetries) {
        // retries limit exceeded
        // throw an RETRIES_LIMIT_EXCEEDED if retries allowed for request
        // or retry reason error in another case
        if (this.options.maxRetries > 0) {
            // @todo throw following error with `reason` prop which contains retryReason
            // so user code can determine limits exceeded errors
            retryReason = AskerError
                .createError(AskerError.CODES.RETRIES_LIMIT_EXCEEDED, retryReason)
                .bind(errorData);
        }
        this.done(retryReason);
    } else {
        this.retries++;
        this._onretry && this._onretry(retryReason, this.retries);
        this._tryHttpRequest(requestOptions);
    }
};

/**
 * run the request
 * @param options Параметры запроса, аналогичны параметрам http.request
 * @private
 */
Request.prototype._tryHttpRequest = function(options) {
    var self = this,
        url = options.host + ':' + options.port + options.path,
        retryRequest = this._retryHttpRequest.bind(this, options),
        httpRequest,
        statusFilterResult;

    httpRequest = http.request(options, function(res) {
        var contentLen = parseInt(res.headers['content-length'], 10),
            isContentLength = ! isNaN(contentLen) && contentLen > 0,
            offset = 0,
            error,
            body;

        if (isContentLength) {
            body = new Buffer(contentLen);
            body.fill(0);
        }

        statusFilterResult = self.statusCodeFilter(res.statusCode);

        if ( ! statusFilterResult.accept) {
            httpRequest.break(true);
            error = AskerError.createError(
                AskerError.CODES.UNEXPECTED_STATUS_CODE, { statusCode : res.statusCode, url : url });
            return statusFilterResult.isRetryAllowed ? retryRequest(error) : self.done(error);
        }

        res.on('data', function(chunk) {
            var isBuffer = Buffer.isBuffer(chunk),
                chunkBuffer;

            httpRequest.clearTimeouts();

            if (isContentLength) {
                offset += isBuffer ? chunk.copy(body, offset) : body.write(chunk, offset);
            } else {
                chunkBuffer = isBuffer ? chunk : new Buffer(chunk);
                body = typeof body !== 'undefined' ? Buffer.concat([body, chunkBuffer]) : chunkBuffer;
            }
        });

        res.on('end', function() {
            var encoding = res.headers['content-encoding'];

            // for sure ;)
            httpRequest.clearTimeouts();

            if (encoding && encoding.toLowerCase().indexOf('gzip') > -1) {
                zlib.gunzip(body, function(err, deflatedBody) {
                    error ?
                        self.done(AskerError.createError(AskerError.CODES.GUNZIP_ERROR, error)) :
                        self.resolve(res.statusCode, deflatedBody.toString(), { gzip : true }, res.headers);
                });
            } else {
                self.resolve(res.statusCode, typeof body !== 'undefined' ? body.toString() : null, {}, res.headers);
            }
        });
    });

    httpRequest.clearTimeouts = function() {
        httpRequest.socketTimeout && clearTimeout(httpRequest.socketTimeout);
        httpRequest.queueTimeout && clearTimeout(httpRequest.queueTimeout);
    };

    httpRequest.break = function(abort, errorCode, errorData) {
        httpRequest.clearTimeouts();

        if (abort) {
            httpRequest.rejected = true;
            httpRequest.abort();
        }

        httpRequest.emit('removeSocket');
        self._timeNetworkEnd = Date.now();

        errorCode && retryRequest(AskerError.createError(errorCode, errorData));
    };

    // setup queue timeout
    httpRequest.queueTimeout = setTimeout(function() {
        httpRequest.break(true, AskerError.CODES.QUEUE_TIMEOUT);
    }, isNaN(options.queueTimeout) ? options.timeout + self.QUEUE_TIMEOUT_DELTA : options.queueTimeout);

    httpRequest.on('socket', function() {
        self._timeNetworkStart = Date.now();

        httpRequest.socketTimeout = setTimeout(function() {
            httpRequest.break(true, AskerError.CODES.SOCKET_TIMEOUT);
        }, options.timeout);
    });

    httpRequest.on('error', function(error) {
        // request manually aborted
        ! httpRequest.rejected && httpRequest.break(false, AskerError.CODES.HTTP_CLIENT_REQUEST_ERROR, error);
    });

    // send request body
    this.options.body && httpRequest.write(this.options.body);

    httpRequest.end();
};

/**
 * Execute request
 * @returns {boolean} `false` if request already running
 */
Request.prototype.execute = function() {
    if (this.isRunning) {
        return false;
    }

    this._isRunning = true;

    this.retries = 0;
    this._timeExecuteStart = Date.now();
    this._tryHttpRequest(extend({}, this.options, { agent : this.agent }));

    return true;
};

module.exports = Request;
<|endoftext|>