<commit_before>/**
 * @file Google 统计分析
 * @author littlewin(littlewin.wang@gmail.com)
 */

import Service from '~/plugins/axios'
import Cookies from 'js-cookie'
const uuidv4 = require('uuid/v4')

/* eslint-disable */
// 只在生产模式加载
// if (process.env.NODE_ENV === 'production') {
if (1) {
  // Generate Client information
  let clientInfo = (window, document, navigator, location, route) => {
    let screen = window.screen;
    let encode = encodeURIComponent;
    let cid = Cookies.get('_cid')

    if (!cid) {
      cid = uuidv4()
      Cookies.set('_cid', cid)
    }

    let data = [
      'v=1',
      't=pageview',
      'cid=' + cid,
      'tid=' + 'UA-108992108-1',
      'dt=' + encode(route.fullPath),
      'dr=' + encode(document.referrer),
      'dp=' + encode(route.fullPath),
      'ul=' + (navigator.language || navigator.browserLanguage),
      'sd=' + screen.colorDepth + '-bit',
      'sr=' + screen.width + 'x' + screen.height,
      'z=' + (+new Date)
    ]

    return '?' + data.join('&')
  }

  // 应用挂载后
  window.onNuxtReady((app) => {
    Service.post('/ga', {
      query: clientInfo(window, document, navigator, location, app.$route)
    })
    // 页面路由切换
    app.$nuxt.$on('routeChanged', (to, from) => {
      // 告诉后端统计分析服务 增加新的页面访问统计
      Service.post('/ga', {
        query: clientInfo(window, document, navigator, location, to)
      })
    })
  })
}
<commit_msg>update<commit_after>/**
 * @file Google 统计分析
 * @author littlewin(littlewin.wang@gmail.com)
 */

import Service from '~/plugins/axios'
import Cookies from 'js-cookie'
const uuidv4 = require('uuid/v4')

/* eslint-disable */
// 只在生产模式加载
if (process.env.NODE_ENV === 'production') {
  // Generate Client information
  let clientInfo = (window, document, navigator, location, route) => {
    let screen = window.screen;
    let encode = encodeURIComponent;

    // 从cookie中取_cid, 没有就新建并保存
    let cid = Cookies.get('_cid')

    if (!cid) {
      cid = uuidv4()
      Cookies.set('_cid', cid)
    }

    // 组装query
    let data = [
      'v=1',
      't=pageview',
      'cid=' + cid,
      'tid=' + 'UA-108992108-1',
      'dt=' + encode(route.fullPath),
      'dr=' + encode(document.referrer),
      'dp=' + encode(route.fullPath),
      'ul=' + (navigator.language || navigator.browserLanguage),
      'sd=' + screen.colorDepth + '-bit',
      'sr=' + screen.width + 'x' + screen.height,
      'z=' + (+new Date)
    ]

    return '?' + data.join('&')
  }

  // 应用挂载后
  window.onNuxtReady((app) => {
    // 告诉后端统计分析服务 增加新的页面访问统计
    Service.post('/ga', {
      query: clientInfo(window, document, navigator, location, app.$route)
    })
    // 页面路由切换
    app.$nuxt.$on('routeChanged', (to, from) => {
      // 告诉后端统计分析服务 增加新的页面访问统计
      Service.post('/ga', {
        query: clientInfo(window, document, navigator, location, to)
      })
    })
  })
}
<|endoftext|>


<commit_before>var WebSocketServer = require('websocket').server;
var fs = require('fs');
var url = require('url');
var qs = require('querystring');
var express = require('express');
var iniparser = require('iniparser');
var crypto = require('crypto');

var EventEmitter = require('events').EventEmitter;

console.log('Loading config');
var config = iniparser.parseSync('./config.ini');

// HTTP Server
var http = require(config.http.protocol);
var app = express();
var httpServer = http.createServer(app);
app.use(express.static(__dirname + config.filesystem.public_files));
app.set('views', __dirname + config.filesystem.view_files);
app.engine('html', require('ejs').renderFile);

// App Variables
var currentUsers = {};
var carddisplay = {
    cards: {},
    show: false
};
var currentUserstory = '';

// WebSocket Server
console.log('Creating WebSocket Server');
wsServer = new WebSocketServer({
    httpServer: httpServer,
    // You should not use autoAcceptConnections for production
    // applications, as it defeats all standard cross-origin protection
    // facilities built into the protocol and the browser.  You should
    // *always* verify the connection's origin and decide whether or not
    // to accept it.
    autoAcceptConnections: false
});

var pokerLoginListener = function(messageData) {
    var user,
        sha1sum,
        sendData;

    // For callbacks
    connection = this.connection;

    user = messageData.user;
    if (typeof user.id !== 'undefined') {
        currentUsers[user.id] = user;
        sendData = {
            type: 'login',
            user: user
        };
        connection.sendUTF(JSON.stringify(sendData));
        broadcastUsers();
    } else {
        // Create random id for user
        sha1sum = crypto.createHash('sha1');
        crypto.randomBytes(256, function(ex, buf) {
            if (ex) throw ex;
            sha1sum.update(buf);
            user.id = sha1sum.digest('hex');
            currentUsers[user.id] = user;
            sendData = {
                type: 'login',
                user: user
            };
            connection.sendUTF(JSON.stringify(sendData));
            broadcastUsers();
        });
    }
    this.user = user;
};

var getInitialDataListener = function(messageData) {
    this.connection.sendUTF(JSON.stringify(getUserUpdateList()));
    this.connection.sendUTF(JSON.stringify(getCardUpdateList()));
    this.connection.sendUTF(JSON.stringify(getUserstoryUpdate()));
};

var playCardListener = function(messageData) {
    // Allow only if cards are not already shown
    if (!carddisplay.show) {
        carddisplay.cards[messageData.userId] = messageData.cardValue
        broadcastCards();
    }
};

var showCardsListener = function(messageData) {
    var pushData = {
        type: 'show-cards'
    };
    carddisplay.show = true;
    wsServer.broadcastUTF(JSON.stringify(pushData));
};

var resetCardsListener = function(messageData) {
    carddisplay = {
        cards: {},
        show: false
    };
    broadcastCards();
};

var postUserstoryListener = function(messageData) {
    currentUserstory  = messageData.userstory;
    broadcastUserstory();
};

var postChatMessageListener = function(messageData) {
    chatMessage = {
        type: 'new-chat-message',
        text: messageData.text,
        user: this.user
    };
    wsServer.broadcastUTF(JSON.stringify(chatMessage));
};

var resetRoomListener = function(messageData) {
    currentUserstory = '';
    carddisplay = {
        cards: {},
        show: false
    };
    broadcastCards();
    broadcastUserstory();

    var pushData = {
        type: 'reset-room'
    };
    wsServer.broadcastUTF(JSON.stringify(pushData));
}

var PokerConnectionHandler = function() {};
PokerConnectionHandler.prototype = new EventEmitter();

PokerConnectionHandler.prototype.connection = null;

PokerConnectionHandler.prototype.setConnection = function(connection) {
    var me;
    me = this;
    this.connection = connection;
    this.connection.on('message', function(message) {
        me.onmessage.call(me, message);
    });

    connection.on('close', function(reasonCode, description) {
        me.onclose.call(me, reasonCode, description);
    });
};

PokerConnectionHandler.prototype.onclose = function(reasonCode, description) {
    console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
    if (typeof this.user != 'undefined') {
        currentUsers[this.user.id] = null;
        delete currentUsers[this.user.id];

        carddisplay[this.user.id] = null;
        delete carddisplay.cards[this.user.id];

        broadcastUsers();
        broadcastCards();
    }
};

PokerConnectionHandler.prototype.onmessage = function(message) {
    if (message.type === 'utf8') {
        //console.log('Received Message: ' + message.utf8Data);
        var messageData = JSON.parse(message.utf8Data);
        // Emit message type as event
        this.emit(messageData.type, messageData, this);
    }
    else if (message.type === 'binary') {
        console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
    }
};

wsServer.on('request', function(request) {
    var connectionHandler = new PokerConnectionHandler();
    connectionHandler.on('login', pokerLoginListener);
    connectionHandler.on('get-initial-data', getInitialDataListener);
    connectionHandler.on('play-card', playCardListener);
    connectionHandler.on('show-cards', showCardsListener);
    connectionHandler.on('reset-cards', resetCardsListener);
    connectionHandler.on('post-userstory', postUserstoryListener);
    connectionHandler.on('post-chat-message', postChatMessageListener);
    connectionHandler.on('reset-room', resetRoomListener);

	connectionHandler.setConnection(request.accept());

    console.log((new Date()) + ' Connection accepted.');

});

app.get('/', function(req, res) {
    console.log(config.http);
	res.render(
        'index.html',
        {
            web: config.http,
            cards: config.cards
        }
    );
	res.end();
});

getUserUpdateList = function () {
	return {
    	type: 'userlist',
    	data: currentUsers
    };
};

broadcastUsers = function() {
	var pushData = getUserUpdateList();
    wsServer.broadcastUTF(JSON.stringify(pushData));
}

getCardUpdateList = function () {
	return {
    	type: 'carddisplay',
    	data: carddisplay
    };
};

broadcastCards = function() {
	var pushData = getCardUpdateList();
    wsServer.broadcastUTF(JSON.stringify(pushData));
};

getUserstoryUpdate = function() {
	return {
		type: 'userstory',
		userstory: currentUserstory
	};
}

broadcastUserstory = function() {
	var pushData = getUserstoryUpdate();
	wsServer.broadcastUTF(JSON.stringify(pushData));
};

httpServer.listen(config.http.port, function() {
	console.log('HTTP Server running with config:');
    console.log(config.http);
});<commit_msg>require i18n module<commit_after>var WebSocketServer = require('websocket').server,
    fs = require('fs'),
    url = require('url'),
    qs = require('querystring'),
    express = require('express'),
    iniparser = require('iniparser'),
    crypto = require('crypto'),
    i18n = require('i18n');

var EventEmitter = require('events').EventEmitter;

console.log('Loading config');
var config = iniparser.parseSync('./config.ini');

// HTTP Server
var http = require(config.http.protocol);
var app = express();
var httpServer = http.createServer(app);
app.use(express.static(__dirname + config.filesystem.public_files));
app.set('views', __dirname + config.filesystem.view_files);
app.engine('html', require('ejs').renderFile);

// App Variables
var currentUsers = {};
var carddisplay = {
    cards: {},
    show: false
};
var currentUserstory = '';

// WebSocket Server
console.log('Creating WebSocket Server');
wsServer = new WebSocketServer({
    httpServer: httpServer,
    // You should not use autoAcceptConnections for production
    // applications, as it defeats all standard cross-origin protection
    // facilities built into the protocol and the browser.  You should
    // *always* verify the connection's origin and decide whether or not
    // to accept it.
    autoAcceptConnections: false
});

var pokerLoginListener = function(messageData) {
    var user,
        sha1sum,
        sendData;

    // For callbacks
    connection = this.connection;

    user = messageData.user;
    if (typeof user.id !== 'undefined') {
        currentUsers[user.id] = user;
        sendData = {
            type: 'login',
            user: user
        };
        connection.sendUTF(JSON.stringify(sendData));
        broadcastUsers();
    } else {
        // Create random id for user
        sha1sum = crypto.createHash('sha1');
        crypto.randomBytes(256, function(ex, buf) {
            if (ex) throw ex;
            sha1sum.update(buf);
            user.id = sha1sum.digest('hex');
            currentUsers[user.id] = user;
            sendData = {
                type: 'login',
                user: user
            };
            connection.sendUTF(JSON.stringify(sendData));
            broadcastUsers();
        });
    }
    this.user = user;
};

var getInitialDataListener = function(messageData) {
    this.connection.sendUTF(JSON.stringify(getUserUpdateList()));
    this.connection.sendUTF(JSON.stringify(getCardUpdateList()));
    this.connection.sendUTF(JSON.stringify(getUserstoryUpdate()));
};

var playCardListener = function(messageData) {
    // Allow only if cards are not already shown
    if (!carddisplay.show) {
        carddisplay.cards[messageData.userId] = messageData.cardValue
        broadcastCards();
    }
};

var showCardsListener = function(messageData) {
    var pushData = {
        type: 'show-cards'
    };
    carddisplay.show = true;
    wsServer.broadcastUTF(JSON.stringify(pushData));
};

var resetCardsListener = function(messageData) {
    carddisplay = {
        cards: {},
        show: false
    };
    broadcastCards();
};

var postUserstoryListener = function(messageData) {
    currentUserstory  = messageData.userstory;
    broadcastUserstory();
};

var postChatMessageListener = function(messageData) {
    chatMessage = {
        type: 'new-chat-message',
        text: messageData.text,
        user: this.user
    };
    wsServer.broadcastUTF(JSON.stringify(chatMessage));
};

var resetRoomListener = function(messageData) {
    currentUserstory = '';
    carddisplay = {
        cards: {},
        show: false
    };
    broadcastCards();
    broadcastUserstory();

    var pushData = {
        type: 'reset-room'
    };
    wsServer.broadcastUTF(JSON.stringify(pushData));
}

var PokerConnectionHandler = function() {};
PokerConnectionHandler.prototype = new EventEmitter();

PokerConnectionHandler.prototype.connection = null;

PokerConnectionHandler.prototype.setConnection = function(connection) {
    var me;
    me = this;
    this.connection = connection;
    this.connection.on('message', function(message) {
        me.onmessage.call(me, message);
    });

    connection.on('close', function(reasonCode, description) {
        me.onclose.call(me, reasonCode, description);
    });
};

PokerConnectionHandler.prototype.onclose = function(reasonCode, description) {
    console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
    if (typeof this.user != 'undefined') {
        currentUsers[this.user.id] = null;
        delete currentUsers[this.user.id];

        carddisplay[this.user.id] = null;
        delete carddisplay.cards[this.user.id];

        broadcastUsers();
        broadcastCards();
    }
};

PokerConnectionHandler.prototype.onmessage = function(message) {
    if (message.type === 'utf8') {
        //console.log('Received Message: ' + message.utf8Data);
        var messageData = JSON.parse(message.utf8Data);
        // Emit message type as event
        this.emit(messageData.type, messageData, this);
    }
    else if (message.type === 'binary') {
        console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
    }
};

wsServer.on('request', function(request) {
    var connectionHandler = new PokerConnectionHandler();
    connectionHandler.on('login', pokerLoginListener);
    connectionHandler.on('get-initial-data', getInitialDataListener);
    connectionHandler.on('play-card', playCardListener);
    connectionHandler.on('show-cards', showCardsListener);
    connectionHandler.on('reset-cards', resetCardsListener);
    connectionHandler.on('post-userstory', postUserstoryListener);
    connectionHandler.on('post-chat-message', postChatMessageListener);
    connectionHandler.on('reset-room', resetRoomListener);

	connectionHandler.setConnection(request.accept());

    console.log((new Date()) + ' Connection accepted.');

});

app.get('/', function(req, res) {
    console.log(config.http);
	res.render(
        'index.html',
        {
            web: config.http,
            cards: config.cards
        }
    );
	res.end();
});

getUserUpdateList = function () {
	return {
    	type: 'userlist',
    	data: currentUsers
    };
};

broadcastUsers = function() {
	var pushData = getUserUpdateList();
    wsServer.broadcastUTF(JSON.stringify(pushData));
}

getCardUpdateList = function () {
	return {
    	type: 'carddisplay',
    	data: carddisplay
    };
};

broadcastCards = function() {
	var pushData = getCardUpdateList();
    wsServer.broadcastUTF(JSON.stringify(pushData));
};

getUserstoryUpdate = function() {
	return {
		type: 'userstory',
		userstory: currentUserstory
	};
}

broadcastUserstory = function() {
	var pushData = getUserstoryUpdate();
	wsServer.broadcastUTF(JSON.stringify(pushData));
};

httpServer.listen(config.http.port, function() {
	console.log('HTTP Server running with config:');
    console.log(config.http);
});<|endoftext|>


<commit_before>var fs = require('fs');
var os = require('os');
var spawn = require('child_process').spawn;
var path = require('path');
var request = require('request');

var PLATFORM = os.platform();
var ROOT = path.resolve(__dirname, '..');
var ARCH = os.arch();
var URL = 'http://cide.cc:8080/webrtc/';
var NODEVER = process.version.split('.');

NODEVER[2] = 'x';
NODEVER = NODEVER.join('.');

URL += 'webrtc-' + PLATFORM + '-' + ARCH + '-' + NODEVER + '.node';

function build() {
  console.log('Building module...');

  var nodegyp = path.resolve(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js');

  if (!fs.existsSync(nodegyp)) {
    nodegyp = path.resolve(ROOT, '..', 'node-gyp', 'bin', 'node-gyp.js');

    if (!fs.existsSync(nodegyp)) {
      throw new Error('Build Failed. Please follow instructions from https://github.com/vmolsa/webrtc-native#build-from-source');
    }
  }

  var res = spawn('node', [ nodegyp, 'rebuild' ], {
    cwd: ROOT,
    env: process.env,
    stdio: 'inherit',
  });

  res.on('error', function(error) {
    var res = spawn('iojs', [ nodegyp, 'rebuild' ], {
      cwd: ROOT,
      env: process.env,
      stdio: 'inherit',
    });
  });
}

function test() {
  console.log('Loading module...');

  try {
    var WebRTC = require(path.resolve(ROOT, 'build', 'Release', 'webrtc.node'));

    setTimeout(function() {
      console.log('Done! :)');
    }, 200);
  } catch (ignored) {
    if (PLATFORM == 'win32') {
      throw new Error('prebuilt module not working. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
    } else {
      throw new Error('prebuilt module not working. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
    }

    console.log('Install Failed! :(');
  }
}

if (process.env['BUILD_WEBRTC'] == 'true') {
  build();
} else {
  console.log('Downloading module...');

  if (!fs.existsSync(path.resolve(ROOT, 'build', 'Release'))) {
    if (!fs.existsSync(path.resolve(ROOT, 'build'))) {
      fs.mkdirSync(path.resolve(ROOT, 'build'));
    }

    fs.mkdirSync(path.resolve(ROOT, 'build', 'Release'));
  }

  request.get(URL, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      setTimeout(test, 200);
    } else {
      if (os.platform() == 'win32') {
        throw new Error('prebuilt module not found. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
      } else {
        throw new Error('prebuilt module not found. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
      }

      process.exit(1);
    }
  }).pipe(fs.createWriteStream(path.resolve(ROOT, 'build', 'Release', 'webrtc.node')));
}
<commit_msg>Add package version check<commit_after>var fs = require('fs');
var os = require('os');
var spawn = require('child_process').spawn;
var path = require('path');
var request = require('request');

var PLATFORM = os.platform();
var ROOT = path.resolve(__dirname, '..');
var ARCH = os.arch();
var URL = 'http://cide.cc:8080/webrtc/';
var NODEVER = process.version.split('.');
var PACKAGE = require(path.resolve(ROOT, 'package.json'));

NODEVER[2] = 'x';
NODEVER = NODEVER.join('.');

URL += 'webrtc-' + PACKAGE.version + '-' + PLATFORM + '-' + ARCH + '-' + NODEVER + '.node';

function build() {
  console.log('Building module...');

  var nodegyp = path.resolve(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js');

  if (!fs.existsSync(nodegyp)) {
    nodegyp = path.resolve(ROOT, '..', 'node-gyp', 'bin', 'node-gyp.js');

    if (!fs.existsSync(nodegyp)) {
      throw new Error('Build Failed. Please follow instructions from https://github.com/vmolsa/webrtc-native#build-from-source');
    }
  }

  var res = spawn('node', [ nodegyp, 'rebuild' ], {
    cwd: ROOT,
    env: process.env,
    stdio: 'inherit',
  });

  res.on('error', function(error) {
    var res = spawn('iojs', [ nodegyp, 'rebuild' ], {
      cwd: ROOT,
      env: process.env,
      stdio: 'inherit',
    });
  });
}

function test() {
  console.log('Loading module...');

  try {
    var WebRTC = require(path.resolve(ROOT, 'build', 'Release', 'webrtc.node'));

    setTimeout(function() {
      console.log('Done! :)');
    }, 200);
  } catch (ignored) {
    if (PLATFORM == 'win32') {
      throw new Error('prebuilt module not working. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
    } else {
      throw new Error('prebuilt module not working. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
    }
  }
}

if (process.env['BUILD_WEBRTC'] == 'true') {
  build();
} else {
  console.log('Downloading module...');

  if (!fs.existsSync(path.resolve(ROOT, 'build', 'Release'))) {
    if (!fs.existsSync(path.resolve(ROOT, 'build'))) {
      fs.mkdirSync(path.resolve(ROOT, 'build'));
    }

    fs.mkdirSync(path.resolve(ROOT, 'build', 'Release'));
  }

  request.get(URL, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      setTimeout(test, 200);
    } else {
      if (os.platform() == 'win32') {
        throw new Error('prebuilt module not found. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
      } else {
        throw new Error('prebuilt module not found. See the instructions from https://github.com/vmolsa/webrtc-native#build-from-source for building module from source.');
      }
    }
  }).pipe(fs.createWriteStream(path.resolve(ROOT, 'build', 'Release', 'webrtc.node')));
}
<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * A general type for keyboard dragging. Objects can be dragged in two dimensions with the arrow keys and with the WASD
 * keys. This can be added to a node through addAccessibleInputListener for accessibility, which is mixed into Nodes with
 * the Accessibility trait.
 *
 * JavaScript does not natively handle multiple 'keydown' events at once, so we have a custom implementation that
 * tracks which keys are down and for how long in a step() function. To work, this drag handler requires a view step.
 *
 * @author Jesse Greenberg
 * @author Michael Barlow
 */

define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Input = require( 'SCENERY/input/Input' );
  var scenery = require( 'SCENERY/scenery' );
  var Vector2 = require( 'DOT/Vector2' );

  /**
   * @constructor
   * @param {Object} options
   */
  function KeyboardDragListener( options ) {
    var self = this;

    options = _.extend( {

      // {number} - while direction key is down, this will be the 1D delta for movement in view coordinates
      positionDelta: 5,

      // {number} - if shift key down while pressing direction key, this will be the 1D delta for movement in view
      // coordinates
      shiftPositionDelta: 2,

      // {Property.<Vector2>|null} - if provided, it will be synchronized with the drag location in the model
      // frame, applying provided transforms as needed
      locationProperty: null,

      // {Bounds2|null} - if provided, the model location will be constrained to be inside these bounds
      dragBounds: null,

      // {Transform3|null} - if provided, this will be the conversion between the view and model coordinate frames,
      // Usually most useful when paired with the locationProperty
      transform: null,

      // {Function|null} - Called as start( event: {DOMEvent} ) when keyboard drag is started
      start: null,

      // {Function|null} - Called as drag( event: {DOMEvent}, viewDelta: {Vector2} ) during drag
      drag: null,

      // {Function|null} - Called as end( event: {DOMEvent}, viewDelta: {Vector2} ) when keyboard drag ends
      end: null // called at the end of the dragging interaction
    }, options );

    // @private, see options for more information
    this._start = options.start;
    this._drag = options.drag;
    this._end = options.end;
    this._dragBounds = options.dragBounds;
    this._transform = options.transform;
    this._locationProperty = options.locationProperty;
    this._positionDelta = options.positionDelta;
    this._shiftPositionDelta = options.shiftPositionDelta;

    // @private { [].{ isDown: {boolean}, timeDown: [boolean] } - tracks the state of the keyboard. JavaScript doesn't
    // handle multiple key presses, so we track which keys are currently down and update based on state of this
    // collection of objects
    this.keyState = [];

    // @private { [].{ keys: <Array.number>, callback: <Function> } } - groups of keys that have some behavior when
    // pressed in order. See this.addHotkeyGroup() for more information
    this.hotkeyGroups = [];

    // @private { keyCode: <number>, timeDown: <number> } - a key in a hot key group that is currently down. When all
    // keys in a group have been pressed in order, we will call the associated listener
    this.hotKeyDown = {};

    // @private {{keys: <Array.number>, callback: <Function>}|null} - the hotkey group that is currently down
    this.keyGroupDown = null;

    // @private {boolean} - when a hotkey group is pressed down, dragging will be disabled until
    // all keys are up again
    this.draggingDisabled = false;

    /**
     * Implements keyboard dragging when listener is attached to the node, public so listener is attached
     * with addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.keydown = function( event ) {

      // required to work with Safari and VoiceOver, otherwise arrow keys will move virtual cursor
      if ( Input.isArrowKey( event.keyCode ) ) {
        event.preventDefault();
      }

      // if the key is already down, don't do anything else (we don't want to create a new keystate object
      // for a key that is already being tracked and down, nor call startDrag every keydown event)
      if ( self.keyInListDown( [ event.keyCode ] ) ) { return; }

      // update the key state
      self.keyState.push( {
        keyDown: true,
        keyCode: event.keyCode,
        timeDown: 0 // in ms
      } );

      if ( self._start ) {
        if ( self.movementKeysDown ) {
         self._start( event );
        }
      }
    };

    /**
     * Behavior for keyboard 'up', public so it can be attached with addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.keyup = function( event ) {
      var moveKeysDown = self.movementKeysDown;

      for ( var i = 0; i < self.keyState.length; i++ ) {
        if ( event.keyCode === self.keyState[ i ].keyCode ) {
          self.keyState.splice( i, 1 );
        }
      }

      var moveKeysStillDown = self.movementKeysDown;
      if ( self._end ) {

        // if movement keys are no longer down after keyup, call the optional end drag function
        if ( !moveKeysStillDown && moveKeysDown !== moveKeysStillDown ) {
          self._end( event );
        }
      }
    };

    /**
     * Behavior on blur, reset the listener when this happens, public for addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.blur = function( event) {
      self.reset();
    };
  }

  scenery.register( 'KeyboardDragListener', KeyboardDragListener );

  return inherit( Object, KeyboardDragListener, {

    /**
     * Step function for the drag handler. JavaScript does not natively handle multiple keydown events at once,
     * so we need to track the state of the keyboard in an Object and manage dragging in this function.
     * In order for the drag handler to work.
     *
     * @public
     */
    step: function( dt ) {

      // no-op unless a key is down
      if ( this.keyState.length > 0 ) {

        // for each key that is still down, increment the tracked time that has been down
        for ( var i = 0; i < this.keyState.length; i++ ) {
          if ( this.keyState[ i ].keyDown ) {
            this.keyState[ i ].timeDown += dt;
          }
        }

        // check to see if any hotkey combinations are down
        for ( var j = 0; j < this.hotkeyGroups.length; j++ ) {
          var hotkeysDownList = [];
          var keys = this.hotkeyGroups[ j ].keys;

          for ( var k = 0; k < keys.length; k++ ) {
            for ( var l = 0; l < this.keyState.length; l++ ) {
              if ( this.keyState[ l ].keyCode === keys[ k ] ) {
                hotkeysDownList.push( this.keyState[ l ] );
              }
            }
          }

          // the hotkeysDownList array order should match the order of the key group, so now we just need to make
          // sure that the key down times are in the right order
          var keysInOrder = false;
          for ( var m = 0; m < hotkeysDownList.length - 1; m++ ) {
            if ( hotkeysDownList[ m + 1 ] && hotkeysDownList[ m ].timeDown > hotkeysDownList[ m + 1 ].timeDown ) {
              keysInOrder = true;
            }
          }

          // if keys are in order, call the callback associated with the group, and disable dragging until
          // all hotkeys associated with that group are up again
          if ( keysInOrder ) {
            this.keyGroupDown = this.hotkeyGroups[ j ];
            this.hotkeyGroups[ j ].callback();
          }
        }

        // if a key group is down, check to see if any of those keys are still down - if so, we will disable dragging
        // until all of them are up
        if ( this.keyGroupDown ) {
          if ( this.keyInListDown( this.keyGroupDown.keys ) ) {
            this.draggingDisabled = true;
          }
          else {
            this.draggingDisabled = false;

            // keys are no longer down, clear the group
            this.keyGroupDown = null;
          }
        }

        if ( !this.draggingDisabled ) {

          // handle the change in position
          var deltaX = 0;
          var deltaY = 0;
          var positionDelta = this.shiftKeyDown() ? this._shiftPositionDelta : this._positionDelta;

          if ( this.leftMovementKeysDown() ) {
            deltaX = -positionDelta;
          }
          if ( this.rightMovementKeysDown() ) {
            deltaX = positionDelta;
          }
          if ( this.upMovementKeysDown() ) {
            deltaY = -positionDelta;
          }
          if ( this.downMovementKeysDown() ) {
            deltaY = positionDelta;
          }

          // only initiate move if there was some attempted keyboard drag
          var vectorDelta = new Vector2( deltaX, deltaY );
          if ( !vectorDelta.equals( Vector2.ZERO ) ) {

            // to model coordinates
            if ( this._transform ) {
              vectorDelta = this._transform.viewToModelDelta( vectorDelta );
            }

            // synchronize with model location
            if ( this._locationProperty ) {
              var newPosition = this._locationProperty.get().plus( vectorDelta );

              // constrain to bounds in model coordinates
              if ( this._dragBounds ) {
                newPosition = this._dragBounds.closestPointTo( newPosition );
              }

              // update the position if it is different
              if ( !newPosition.equals( this._locationProperty.get() ) ) {
                this._locationProperty.set( newPosition );
              }
            }

            // call our drag function
            if ( this._drag ) {
              this._drag( event, vectorDelta );
            }
          }
        }
      }
    },

    /**
     * Returns true if any of the keys in the list are currently down.
     *
     * @param  {Array.<number>} keys
     * @return {boolean}
     */
    keyInListDown: function( keys ) {
      var keyIsDown = false;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          for ( var j = 0; j < keys.length; j++ ) {
            if ( keys[ j ] === this.keyState[ i ].keyCode ) {
              keyIsDown = true;
              break;
            }
          }
        }
        if ( keyIsDown ) {
          // no need to keep looking
          break;
        }
      }

      return keyIsDown;
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    leftMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_A, Input.KEY_LEFT_ARROW ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    rightMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_RIGHT_ARROW, Input.KEY_D ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    upMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_UP_ARROW, Input.KEY_W ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    downMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_DOWN_ARROW, Input.KEY_S ] );
    },

    /**
     * Returns true if any of the movement keys are down (arrow keys or WASD keys).
     *
     * @return {boolean}
     */
    getMovementKeysDown: function() {
      return this.rightMovementKeysDown() || this.leftMovementKeysDown() ||
             this.upMovementKeysDown() || this.downMovementKeysDown();
    },
    get movementKeysDown() { return this.getMovementKeysDown(); },

    /**
     * Returns true if the enter key is currently pressed down.
     *
     * @return {boolean}
     */
    enterKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_ENTER ] );
    },

    /**
     * Returns true if the keystate indicates that the shift key is currently down.
     *
     * @return {boolean}
     */
    shiftKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_SHIFT ] );
    },

    /**
     * Add a set of hotkeys that behave such that the desired callback will be called when
     * all keys listed in the array are pressed down in order.
     *
     * @param {Object} hotKeyGroup - { keys: [].<number>, callback: function }
     */
    addHotkeyGroup: function( hotKeyGroup ) {
      this.hotkeyGroups.push( hotKeyGroup );
    },

    /**
     * Add mutliple sets of hotkey groups that behave such hat the desired callback will be called
     * when all keys listed in the array are pressed down in order.  Behaves much like addHotkeyGroup,
     * but allows you to add multiple groups at one time.
     *
     * @param {[].<string>} hotKeyGroups
     */
    addHotkeyGroups: function( hotKeyGroups ) {
      for ( var i = 0; i < hotKeyGroups.length; i++ ) {
        this.addHotkeyGroup( hotKeyGroups[ i ] );
      }
    },

    /**
     * Reset the keystate Object tracking which keys are currently pressed down.
     *
     * @public
     */
    reset: function() {
      this.keyState = [];
    }
  }, {

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    isLeftMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_A || keyCode === Input.KEY_LEFT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    isRightMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_D || keyCode === Input.KEY_RIGHT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    isUpMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_W || keyCode === Input.KEY_UP_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object down.
     *
     * @public
     * @return {boolean}
     */
    isDownMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_S || keyCode === Input.KEY_DOWN_ARROW;
    },
  } );
} );
<commit_msg>add shift to keystate if it is down when we focus element, see #53<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * A general type for keyboard dragging. Objects can be dragged in two dimensions with the arrow keys and with the WASD
 * keys. This can be added to a node through addAccessibleInputListener for accessibility, which is mixed into Nodes with
 * the Accessibility trait.
 *
 * JavaScript does not natively handle multiple 'keydown' events at once, so we have a custom implementation that
 * tracks which keys are down and for how long in a step() function. To work, this drag handler requires a view step.
 *
 * @author Jesse Greenberg
 * @author Michael Barlow
 */

define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Input = require( 'SCENERY/input/Input' );
  var scenery = require( 'SCENERY/scenery' );
  var Vector2 = require( 'DOT/Vector2' );

  /**
   * @constructor
   * @param {Object} options
   */
  function KeyboardDragListener( options ) {
    var self = this;

    options = _.extend( {

      // {number} - while direction key is down, this will be the 1D delta for movement in view coordinates
      positionDelta: 5,

      // {number} - if shift key down while pressing direction key, this will be the 1D delta for movement in view
      // coordinates
      shiftPositionDelta: 2,

      // {Property.<Vector2>|null} - if provided, it will be synchronized with the drag location in the model
      // frame, applying provided transforms as needed
      locationProperty: null,

      // {Bounds2|null} - if provided, the model location will be constrained to be inside these bounds
      dragBounds: null,

      // {Transform3|null} - if provided, this will be the conversion between the view and model coordinate frames,
      // Usually most useful when paired with the locationProperty
      transform: null,

      // {Function|null} - Called as start( event: {DOMEvent} ) when keyboard drag is started
      start: null,

      // {Function|null} - Called as drag( event: {DOMEvent}, viewDelta: {Vector2} ) during drag
      drag: null,

      // {Function|null} - Called as end( event: {DOMEvent}, viewDelta: {Vector2} ) when keyboard drag ends
      end: null // called at the end of the dragging interaction
    }, options );

    // @private, see options for more information
    this._start = options.start;
    this._drag = options.drag;
    this._end = options.end;
    this._dragBounds = options.dragBounds;
    this._transform = options.transform;
    this._locationProperty = options.locationProperty;
    this._positionDelta = options.positionDelta;
    this._shiftPositionDelta = options.shiftPositionDelta;

    // @private { [].{ isDown: {boolean}, timeDown: [boolean] } - tracks the state of the keyboard. JavaScript doesn't
    // handle multiple key presses, so we track which keys are currently down and update based on state of this
    // collection of objects
    // TODO: Consider a global state object for this that persists across listeners so the state of the keyboard will
    // be accurate when focus changes from one element to another, see https://github.com/phetsims/friction/issues/53
    this.keyState = [];

    // @private { [].{ keys: <Array.number>, callback: <Function> } } - groups of keys that have some behavior when
    // pressed in order. See this.addHotkeyGroup() for more information
    this.hotkeyGroups = [];

    // @private { keyCode: <number>, timeDown: <number> } - a key in a hot key group that is currently down. When all
    // keys in a group have been pressed in order, we will call the associated listener
    this.hotKeyDown = {};

    // @private {{keys: <Array.number>, callback: <Function>}|null} - the hotkey group that is currently down
    this.keyGroupDown = null;

    // @private {boolean} - when a hotkey group is pressed down, dragging will be disabled until
    // all keys are up again
    this.draggingDisabled = false;

    /**
     * Implements keyboard dragging when listener is attached to the node, public so listener is attached
     * with addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.keydown = function( event ) {

      // required to work with Safari and VoiceOver, otherwise arrow keys will move virtual cursor
      if ( Input.isArrowKey( event.keyCode ) ) {
        event.preventDefault();
      }

      // if the key is already down, don't do anything else (we don't want to create a new keystate object
      // for a key that is already being tracked and down, nor call startDrag every keydown event)
      if ( self.keyInListDown( [ event.keyCode ] ) ) { return; }

      // update the key state
      self.keyState.push( {
        keyDown: true,
        keyCode: event.keyCode,
        timeDown: 0 // in ms
      } );

      if ( self._start ) {
        if ( self.movementKeysDown ) {
         self._start( event );
        }
      }
    };

    /**
     * Behavior for keyboard 'up', public so it can be attached with addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.keyup = function( event ) {
      var moveKeysDown = self.movementKeysDown;

      // if the shift key is down when we navigate to the object, add it to the keystate because it won't be added until
      // the next keydown event
      if ( event.keyCode === Input.KEY_TAB ) {
        if ( event.shiftKey ) {

          // add 'shift' to the keystate until it is released again
          self.keyState.push( {
            keyDown: true,
            keyCode: Input.KEY_SHIFT,
            timeDown: 0 // in ms
          } );
        }
      }

      for ( var i = 0; i < self.keyState.length; i++ ) {
        if ( event.keyCode === self.keyState[ i ].keyCode ) {
          self.keyState.splice( i, 1 );
        }
      }

      var moveKeysStillDown = self.movementKeysDown;
      if ( self._end ) {

        // if movement keys are no longer down after keyup, call the optional end drag function
        if ( !moveKeysStillDown && moveKeysDown !== moveKeysStillDown ) {
          self._end( event );
        }
      }
    };

    /**
     * Behavior on blur, reset the listener when this happens, public for addAccessibleInputListener()
     * @public (scenery-internal)
     *
     * @param {DOMEvent} event
     */
    this.blur = function( event) {
      self.reset();
    };
  }

  scenery.register( 'KeyboardDragListener', KeyboardDragListener );

  return inherit( Object, KeyboardDragListener, {

    /**
     * Step function for the drag handler. JavaScript does not natively handle multiple keydown events at once,
     * so we need to track the state of the keyboard in an Object and manage dragging in this function.
     * In order for the drag handler to work.
     *
     * @public
     */
    step: function( dt ) {

      // no-op unless a key is down
      if ( this.keyState.length > 0 ) {

        // for each key that is still down, increment the tracked time that has been down
        for ( var i = 0; i < this.keyState.length; i++ ) {
          if ( this.keyState[ i ].keyDown ) {
            this.keyState[ i ].timeDown += dt;
          }
        }

        // check to see if any hotkey combinations are down
        for ( var j = 0; j < this.hotkeyGroups.length; j++ ) {
          var hotkeysDownList = [];
          var keys = this.hotkeyGroups[ j ].keys;

          for ( var k = 0; k < keys.length; k++ ) {
            for ( var l = 0; l < this.keyState.length; l++ ) {
              if ( this.keyState[ l ].keyCode === keys[ k ] ) {
                hotkeysDownList.push( this.keyState[ l ] );
              }
            }
          }

          // the hotkeysDownList array order should match the order of the key group, so now we just need to make
          // sure that the key down times are in the right order
          var keysInOrder = false;
          for ( var m = 0; m < hotkeysDownList.length - 1; m++ ) {
            if ( hotkeysDownList[ m + 1 ] && hotkeysDownList[ m ].timeDown > hotkeysDownList[ m + 1 ].timeDown ) {
              keysInOrder = true;
            }
          }

          // if keys are in order, call the callback associated with the group, and disable dragging until
          // all hotkeys associated with that group are up again
          if ( keysInOrder ) {
            this.keyGroupDown = this.hotkeyGroups[ j ];
            this.hotkeyGroups[ j ].callback();
          }
        }

        // if a key group is down, check to see if any of those keys are still down - if so, we will disable dragging
        // until all of them are up
        if ( this.keyGroupDown ) {
          if ( this.keyInListDown( this.keyGroupDown.keys ) ) {
            this.draggingDisabled = true;
          }
          else {
            this.draggingDisabled = false;

            // keys are no longer down, clear the group
            this.keyGroupDown = null;
          }
        }

        if ( !this.draggingDisabled ) {

          // handle the change in position
          var deltaX = 0;
          var deltaY = 0;
          var positionDelta = this.shiftKeyDown() ? this._shiftPositionDelta : this._positionDelta;

          if ( this.leftMovementKeysDown() ) {
            deltaX = -positionDelta;
          }
          if ( this.rightMovementKeysDown() ) {
            deltaX = positionDelta;
          }
          if ( this.upMovementKeysDown() ) {
            deltaY = -positionDelta;
          }
          if ( this.downMovementKeysDown() ) {
            deltaY = positionDelta;
          }

          // only initiate move if there was some attempted keyboard drag
          var vectorDelta = new Vector2( deltaX, deltaY );
          if ( !vectorDelta.equals( Vector2.ZERO ) ) {

            // to model coordinates
            if ( this._transform ) {
              vectorDelta = this._transform.viewToModelDelta( vectorDelta );
            }

            // synchronize with model location
            if ( this._locationProperty ) {
              var newPosition = this._locationProperty.get().plus( vectorDelta );

              // constrain to bounds in model coordinates
              if ( this._dragBounds ) {
                newPosition = this._dragBounds.closestPointTo( newPosition );
              }

              // update the position if it is different
              if ( !newPosition.equals( this._locationProperty.get() ) ) {
                this._locationProperty.set( newPosition );
              }
            }

            // call our drag function
            if ( this._drag ) {
              this._drag( event, vectorDelta );
            }
          }
        }
      }
    },

    /**
     * Returns true if any of the keys in the list are currently down.
     *
     * @param  {Array.<number>} keys
     * @return {boolean}
     */
    keyInListDown: function( keys ) {
      var keyIsDown = false;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          for ( var j = 0; j < keys.length; j++ ) {
            if ( keys[ j ] === this.keyState[ i ].keyCode ) {
              keyIsDown = true;
              break;
            }
          }
        }
        if ( keyIsDown ) {
          // no need to keep looking
          break;
        }
      }

      return keyIsDown;
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    leftMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_A, Input.KEY_LEFT_ARROW ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    rightMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_RIGHT_ARROW, Input.KEY_D ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    upMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_UP_ARROW, Input.KEY_W ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    downMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_DOWN_ARROW, Input.KEY_S ] );
    },

    /**
     * Returns true if any of the movement keys are down (arrow keys or WASD keys).
     *
     * @return {boolean}
     */
    getMovementKeysDown: function() {
      return this.rightMovementKeysDown() || this.leftMovementKeysDown() ||
             this.upMovementKeysDown() || this.downMovementKeysDown();
    },
    get movementKeysDown() { return this.getMovementKeysDown(); },

    /**
     * Returns true if the enter key is currently pressed down.
     *
     * @return {boolean}
     */
    enterKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_ENTER ] );
    },

    /**
     * Returns true if the keystate indicates that the shift key is currently down.
     *
     * @return {boolean}
     */
    shiftKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_SHIFT ] );
    },

    /**
     * Add a set of hotkeys that behave such that the desired callback will be called when
     * all keys listed in the array are pressed down in order.
     *
     * @param {Object} hotKeyGroup - { keys: [].<number>, callback: function }
     */
    addHotkeyGroup: function( hotKeyGroup ) {
      this.hotkeyGroups.push( hotKeyGroup );
    },

    /**
     * Add mutliple sets of hotkey groups that behave such hat the desired callback will be called
     * when all keys listed in the array are pressed down in order.  Behaves much like addHotkeyGroup,
     * but allows you to add multiple groups at one time.
     *
     * @param {[].<string>} hotKeyGroups
     */
    addHotkeyGroups: function( hotKeyGroups ) {
      for ( var i = 0; i < hotKeyGroups.length; i++ ) {
        this.addHotkeyGroup( hotKeyGroups[ i ] );
      }
    },

    /**
     * Reset the keystate Object tracking which keys are currently pressed down.
     *
     * @public
     */
    reset: function() {
      this.keyState = [];
    }
  }, {

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    isLeftMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_A || keyCode === Input.KEY_LEFT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    isRightMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_D || keyCode === Input.KEY_RIGHT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    isUpMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_W || keyCode === Input.KEY_UP_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object down.
     *
     * @public
     * @return {boolean}
     */
    isDownMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_S || keyCode === Input.KEY_DOWN_ARROW;
    },
  } );
} );
<|endoftext|>


<commit_before>define(['react', 'reactDom', 'reactAddons'], function(React, ReactDOM, Perf) {
  "use strict";

var CommonMixins = {
columns: 31,
rows: 51,
rgba: 255,
  create2DArray: function(num) {
    var obj = [];
    for (var i = 0; i < num; i++) {
      obj[i] = [];
    }
    return obj;
  },
  getRandomNumber: function(num) {
    return Math.floor((Math.random() * num) + 0);
  }
};

var Cell = React.createClass({
  mixins: [CommonMixins],
  getInitialState: function() {
    return {
      rndX: 0,
      rndY: 0,
      rgba1: this.create2DArray(this.columns),
      rgba2: this.create2DArray(this.columns),
      rgba3: this.create2DArray(this.columns),
      cells: this.create2DArray(this.columns)
    }
  },
  render: function() {
    var x = this.props.x;
    var y = this.props.y;
    var rndX = this.getRandomNumber(this.columns);
    var rndY = this.getRandomNumber(this.rows);

    this.state.cells[rndX][rndY] = this.props.values.rndNum;
    this.state.rgba1[rndX][rndY] = this.props.values.rgba1Val;
    this.state.rgba2[rndX][rndY] = this.props.values.rgba2Val;
    this.state.rgba3[rndX][rndY] = this.props.values.rgba3Val;

    var style = {
      backgroundColor: "rgba(" + this.state.rgba1[x][y] + "," + this.state.rgba2[x][y] + "," + this.state.rgba3[x][y] + "," + 1 + ")"
    };

    return (

{this.state.cells[x][y]}

    )
  }
});

var Column = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return (this.props.columns !== nextProps.columns || this.props.values !== nextProps.values);
  },
  render: function() {
    var column = [];
    for (var i = 0; i < this.props.columns; i++) {
      column.push(<Cell x={i} y={this.props.id} key={i} values={this.props.values} />);
    }
    return (
{column}
)
  }
});

var Row = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return (this.props.columns !== nextProps.columns || this.props.values !== nextProps.values);
  },
  render: function() {
    var row = [];
    for (var i = 0; i < this.props.rows; i++) {
      row.push(<Column id={i} key={i} columns={this.props.columns} values={this.props.values} />);
    }
    return (
{row}
)
  }
});

var Grid = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return (this.props.rows !== nextProps.rows || this.props.columns !== nextProps.columns || this.props.values !== nextProps.values);
  },
  render: function() {
    return (
<Row rows={this.props.rows} columns={this.props.columns} values={this.props.values} />
)
  }
});

var Buttons = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return (this.props.isInterval !== nextProps.isInterval || this.props.start !== nextProps.start || this.props.stop !== nextProps.stop);
  },
  render: function() {
  console.log(this.props.isInterval);
    return (

          {!this.props.isInterval
          ? <button className="btn btn-primary" onClick={this.props.start}>Start</button>
          : <button className="btn btn-danger" onClick={this.props.stop}>Stop</button>
          }

    )
  }
});

var GridMixins = {

  getInitialState: function() {
    return {
      interval: {},
        isInterval: false,
    values: {}
    }
  },
  randomizeCells: function() {
    this.setState({
    isInterval: true,
    values: {
    rndNum: this.getRandomNumber(this.rgba),
    rgba1Val: this.getRandomNumber(this.rgba),
    rgba2Val: this.getRandomNumber(this.rgba),
    rgba3Val: this.getRandomNumber(this.rgba)
    }
    });
  },
  startTimer: function() {
    console.log("Starting timer...");
    this.state.interval = setInterval(this.randomizeCells, 0);
  },
  stopTimer: function() {
    console.log("Stopping timer...");
    clearInterval(this.state.interval);
    this.setState({
      isInterval: false
    });
  },
  shouldComponentUpdate: function(nextProps, nextState) {
    var values = (this.state.values.rndNum !== nextState.values.rndNum ||
      this.state.values.rndNum !== nextState.values.rndNum ||
      this.state.values.rgba1Val !== nextState.values.rgba1Val ||
      this.state.values.rgba2Val !== nextState.values.rgba2Val ||
      this.state.values.rgba3Val !== nextState.values.rgba3Val ||
      this.state.isInterval !== nextState.isInterval);
    return values;
  }
};

var GridWrapper1 = React.createClass({
  mixins: [CommonMixins, GridMixins],
  render: function() {
    return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

    )
  }
});

  var GridWrapper2 = React.createClass({
    mixins: [CommonMixins, GridMixins],
    render: function() {
      return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

      )
    }
  });

  var Directive = function(log, stateParams) {
    return {
      restrict: "E",
      link: function() {

        // var perf = Perf.addons.Perf;
        // window.perf = perf;
        // perf.start();
        // perf.stop();
        // var measurements = perf.getLastMeasurements();
        // console.table(perf.getLastMeasurements());
        // console.table(perf.printWasted());
        // perf.printInclusive(measurements);
        // perf.printExclusive(measurements);
        // perf.printWasted(measurements);
        // perf.printDOM(measurements);

        if (stateParams.version === "1") {
          ReactDOM.render(
            <GridWrapper1 />,
              document.getElementById('react')
          );
        } else {
          ReactDOM.render(
            <GridWrapper2 />,
              document.getElementById('react')
          );
        }

      }

    }
  };

  Directive.$inject = ["$log", "$stateParams"];

  return Directive;

});

<commit_msg>updating react_direct<commit_after>define(['react', 'reactDom', 'reactAddons'], function(React, ReactDOM, ReactAddons) {
  "use strict";

var CommonMixins = {
columns: 31,
rows: 51,
rgba: 255,
  create2DArray: function(num) {
    var obj = [];
    for (var i = 0; i < num; i++) {
      obj[i] = [];
    }
    return obj;
  },
  getRandomNumber: function(num) {
    return Math.floor((Math.random() * num) + 0);
  }
};

var Cell = React.createClass({
  mixins: [CommonMixins, ReactAddons.addons.PureRenderMixin],
  getInitialState: function() {
    return {
      rndX: 0,
      rndY: 0,
      rgba1: this.create2DArray(this.columns),
      rgba2: this.create2DArray(this.columns),
      rgba3: this.create2DArray(this.columns),
      cells: this.create2DArray(this.columns)
    }
  },
  render: function() {
    var x = this.props.x;
    var y = this.props.y;
    var rndX = this.getRandomNumber(this.columns);
    var rndY = this.getRandomNumber(this.rows);

    this.state.cells[rndX][rndY] = this.props.values.rndNum;
    this.state.rgba1[rndX][rndY] = this.props.values.rgba1Val;
    this.state.rgba2[rndX][rndY] = this.props.values.rgba2Val;
    this.state.rgba3[rndX][rndY] = this.props.values.rgba3Val;

    var style = {
      backgroundColor: "rgba(" + this.state.rgba1[x][y] + "," + this.state.rgba2[x][y] + "," + this.state.rgba3[x][y] + "," + 1 + ")"
    };

    return (

{this.state.cells[x][y]}

    )
  }
});

var Column = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var column = [];
    for (var i = 0; i < this.props.columns; i++) {
      column.push(<Cell x={i} y={this.props.id} key={i} values={this.props.values} />);
    }
    return (
{column}
)
  }
});

var Row = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var row = [];
    for (var i = 0; i < this.props.rows; i++) {
      row.push(<Column id={i} key={i} columns={this.props.columns} values={this.props.values} />);
    }
    return (
{row}
)
  }
});

var Grid = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (
<Row rows={this.props.rows} columns={this.props.columns} values={this.props.values} />
)
  }
});

var Buttons = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
  console.log(this.props.isInterval);
    return (

          {!this.props.isInterval
          ? <button className="btn btn-primary" onClick={this.props.start}>Start</button>
          : <button className="btn btn-danger" onClick={this.props.stop}>Stop</button>
          }

    )
  }
});

var GridMixins = {

  getInitialState: function() {
    return {
      interval: {},
        isInterval: false,
    values: {}
    }
  },
  randomizeCells: function() {
    this.setState({
    isInterval: true,
    values: {
    rndNum: this.getRandomNumber(this.rgba),
    rgba1Val: this.getRandomNumber(this.rgba),
    rgba2Val: this.getRandomNumber(this.rgba),
    rgba3Val: this.getRandomNumber(this.rgba)
    }
    });
  },
  startTimer: function() {
    console.log("Starting timer...");
    this.state.interval = setInterval(this.randomizeCells, 0);
  },
  stopTimer: function() {
    console.log("Stopping timer...");
    clearInterval(this.state.interval);
    this.setState({
      isInterval: false
    });
  }
};

var GridWrapper1 = React.createClass({
  mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

    )
  }
});

  var GridWrapper2 = React.createClass({
    mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
    render: function() {
      return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

      )
    }
  });

  var Directive = function(log, stateParams) {
    return {
      restrict: "E",
      link: function() {

        // var perf = Perf.addons.Perf;
        // window.perf = perf;
        // perf.start();
        // perf.stop();
        // var measurements = perf.getLastMeasurements();
        // console.table(perf.getLastMeasurements());
        // console.table(perf.printWasted());
        // perf.printInclusive(measurements);
        // perf.printExclusive(measurements);
        // perf.printWasted(measurements);
        // perf.printDOM(measurements);

        if (stateParams.version === "1") {
          ReactDOM.render(
            <GridWrapper1 />,
              document.getElementById('react')
          );
        } else {
          ReactDOM.render(
            <GridWrapper2 />,
              document.getElementById('react')
          );
        }

      }

    }
  };

  Directive.$inject = ["$log", "$stateParams"];

  return Directive;

});

<|endoftext|>


<commit_before>/*!
 * node-sass: scripts/install.js
 */

var fs = require('fs'),
  eol = require('os').EOL,
  mkdir = require('mkdirp'),
  path = require('path'),
  sass = require('../lib/extensions'),
  request = require('request'),
  pkg = require('../package.json');

/**
 * Download file, if succeeds save, if not delete
 *
 * @param {String} url
 * @param {String} dest
 * @param {Function} cb
 * @api private
 */

function download(url, dest, cb) {
  var reportError = function(err) {
    cb(['Cannot download "', url, '": ', eol, eol,
      typeof err.message === 'string' ? err.message : err, eol, eol,
      'Hint: If github.com is not accessible in your location', eol,
      '      try setting a proxy via HTTP_PROXY, e.g. ', eol, eol,
      '      export HTTP_PROXY=http://example.com:1234',eol, eol,
      'or configure npm proxy via', eol, eol,
      '      npm config set proxy http://example.com:8080'].join(''));
  };

  var successful = function(response) {
    return response.statusCode >= 200 && response.statusCode < 300;
  };

  var options = {
    rejectUnauthorized: false,
    proxy: getProxy(),
    headers: {
      'User-Agent': getUserAgent(),
    }
  };

  try {
    request(url, options, function(err, response) {
      if (err) {
        reportError(err);
      } else if (!successful(response)) {
        reportError(['HTTP error', response.statusCode, response.statusMessage].join(' '));
      } else {
        cb();
      }
    })
    .on('response', function(response) {
      if (successful(response)) {
        response.pipe(fs.createWriteStream(dest));
      }
    });
  } catch (err) {
    cb(err);
  }
}

/**
 * A custom user agent use for binary downloads.
 *
 * @api private
 */
function getUserAgent() {
  return [
    'node/', process.version, ' ',
    'node-sass-installer/', pkg.version
  ].join('');
}

/**
 * Determine local proxy settings
 *
 * @param {Object} options
 * @param {Function} cb
 * @api private
 */

function getProxy() {
  return process.env.npm_config_https_proxy ||
         process.env.npm_config_proxy ||
         process.env.npm_config_http_proxy ||
         process.env.HTTPS_PROXY ||
         process.env.https_proxy ||
         process.env.HTTP_PROXY ||
         process.env.http_proxy;
}

/**
 * Check and download binary
 *
 * @api private
 */

function checkAndDownloadBinary() {
  if (sass.hasBinary(sass.getBinaryPath())) {
    return;
  }

  mkdir(path.dirname(sass.getBinaryPath()), function(err) {
    if (err) {
      console.error(err);
      return;
    }

    download(sass.getBinaryUrl(), sass.getBinaryPath(), function(err) {
      if (err) {
        console.error(err);
        return;
      }

      console.log('Binary downloaded and installed at', sass.getBinaryPath());
    });
  });
}

/**
 * Skip if CI
 */

if (process.env.SKIP_SASS_BINARY_DOWNLOAD_FOR_CI) {
  console.log('Skipping downloading binaries on CI builds');
  return;
}

/**
 * If binary does not exist, download it
 */

checkAndDownloadBinary();
<commit_msg>Add a timeout to the binary fetch request<commit_after>/*!
 * node-sass: scripts/install.js
 */

var fs = require('fs'),
  eol = require('os').EOL,
  mkdir = require('mkdirp'),
  path = require('path'),
  sass = require('../lib/extensions'),
  request = require('request'),
  pkg = require('../package.json');

/**
 * Download file, if succeeds save, if not delete
 *
 * @param {String} url
 * @param {String} dest
 * @param {Function} cb
 * @api private
 */

function download(url, dest, cb) {
  var reportError = function(err) {
    var timeoutMessge;

    if (err.code === 'ETIMEDOUT') {
      if (err.connect === true) {
        // timeout is hit while your client is attempting to establish a connection to a remote machine
        timeoutMessge = 'Timed out attemping to establish a remote connection';
      } else {
        timeoutMessge = 'Timed out whilst downloading the prebuilt binary';
        // occurs any time the server is too slow to send back a part of the response
      }

    }
    cb(['Cannot download "', url, '": ', eol, eol,
      typeof err.message === 'string' ? err.message : err, eol, eol,
      timeoutMessge ? timeoutMessge + eol + eol : timeoutMessge,
      'Hint: If github.com is not accessible in your location', eol,
      '      try setting a proxy via HTTP_PROXY, e.g. ', eol, eol,
      '      export HTTP_PROXY=http://example.com:1234',eol, eol,
      'or configure npm proxy via', eol, eol,
      '      npm config set proxy http://example.com:8080'].join(''));
  };

  var successful = function(response) {
    return response.statusCode >= 200 && response.statusCode < 300;
  };

  var options = {
    rejectUnauthorized: false,
    proxy: getProxy(),
    timeout: 1000,
    headers: {
      'User-Agent': getUserAgent(),
    }
  };

  try {
    request(url, options, function(err, response) {
      if (err) {
        reportError(err);
      } else if (!successful(response)) {
        reportError(['HTTP error', response.statusCode, response.statusMessage].join(' '));
      } else {
        cb();
      }
    })
    .on('response', function(response) {
      if (successful(response)) {
        response.pipe(fs.createWriteStream(dest));
      }
    });
  } catch (err) {
    cb(err);
  }
}

/**
 * A custom user agent use for binary downloads.
 *
 * @api private
 */
function getUserAgent() {
  return [
    'node/', process.version, ' ',
    'node-sass-installer/', pkg.version
  ].join('');
}

/**
 * Determine local proxy settings
 *
 * @param {Object} options
 * @param {Function} cb
 * @api private
 */

function getProxy() {
  return process.env.npm_config_https_proxy ||
         process.env.npm_config_proxy ||
         process.env.npm_config_http_proxy ||
         process.env.HTTPS_PROXY ||
         process.env.https_proxy ||
         process.env.HTTP_PROXY ||
         process.env.http_proxy;
}

/**
 * Check and download binary
 *
 * @api private
 */

function checkAndDownloadBinary() {
  if (sass.hasBinary(sass.getBinaryPath())) {
    return;
  }

  mkdir(path.dirname(sass.getBinaryPath()), function(err) {
    if (err) {
      console.error(err);
      return;
    }

    download(sass.getBinaryUrl(), sass.getBinaryPath(), function(err) {
      if (err) {
        console.error(err);
        return;
      }

      console.log('Binary downloaded and installed at', sass.getBinaryPath());
    });
  });
}

/**
 * Skip if CI
 */

if (process.env.SKIP_SASS_BINARY_DOWNLOAD_FOR_CI) {
  console.log('Skipping downloading binaries on CI builds');
  return;
}

/**
 * If binary does not exist, download it
 */

checkAndDownloadBinary();
<|endoftext|>


<commit_before>/* eslint-env node */
/* eslint-disable object-shorthand */
'use strict';

module.exports = function(grunt) {

    // Find all of the task which start with `grunt-` and load them, rather than explicitly declaring them all
    require('matchdep').filterDev(['grunt-*', '!grunt-cli']).forEach(grunt.loadNpmTasks);

    grunt.initConfig({
        clean: {
            built: {
                src: [
                    'dist/**'
                ]
            },
            dependencies: {
                src: [
                    'bower_components/**',
                    'node_modules/**'
                ]
            },
            tmp: {
                src: ['tmp/**']
            }
        },

        watch: {
            csscomb: {
                files: ['app/styles/**/*.css'],
                tasks: ['shell:csscombfix']
            }
        },

        shell: {
            'npm-install': {
                command: 'npm install'
            },

            'bower-install': {
                command: 'bower install'
            },

            ember: {
                command: function (mode) {
                    let liveReloadBaseUrl = grunt.option('live-reload-base-url') || '/ghost/';

                    switch (mode) {
                    case 'prod':
                        return 'npm run build -- --environment=production --silent';
                    case 'dev':
                        return 'npm run build';
                    case 'watch':
                        return `npm run start -- --live-reload-base-url=${liveReloadBaseUrl} --live-reload-port=4201`;
                    }
                }
            },

            csscombfix: {
                command: 'csscomb -c app/styles/csscomb.json -v app/styles'
            },

            csscomblint: {
                command: 'csscomb -c app/styles/csscomb.json -lv app/styles'
            },

            test: {
                command: 'npm test'
            },

            options: {
                preferLocal: true
            }
        }
    });

    grunt.registerTask('init', 'Install the client dependencies',
        ['shell:npm-install', 'shell:bower-install']
    );
};
<commit_msg>🛠 switch to using yarn in our Grunt tasks (#613)<commit_after>/* eslint-env node */
/* eslint-disable object-shorthand */
'use strict';

module.exports = function(grunt) {

    // Find all of the task which start with `grunt-` and load them, rather than explicitly declaring them all
    require('matchdep').filterDev(['grunt-*', '!grunt-cli']).forEach(grunt.loadNpmTasks);

    grunt.initConfig({
        clean: {
            built: {
                src: [
                    'dist/**'
                ]
            },
            dependencies: {
                src: [
                    'bower_components/**',
                    'node_modules/**'
                ]
            },
            tmp: {
                src: ['tmp/**']
            }
        },

        watch: {
            csscomb: {
                files: ['app/styles/**/*.css'],
                tasks: ['shell:csscombfix']
            }
        },

        shell: {
            'npm-install': {
                command: 'yarn install'
            },

            'bower-install': {
                command: 'bower install'
            },

            ember: {
                command: function (mode) {
                    let liveReloadBaseUrl = grunt.option('live-reload-base-url') || '/ghost/';

                    switch (mode) {
                    case 'prod':
                        return 'npm run build -- --environment=production --silent';
                    case 'dev':
                        return 'npm run build';
                    case 'watch':
                        return `npm run start -- --live-reload-base-url=${liveReloadBaseUrl} --live-reload-port=4201`;
                    }
                }
            },

            csscombfix: {
                command: 'csscomb -c app/styles/csscomb.json -v app/styles'
            },

            csscomblint: {
                command: 'csscomb -c app/styles/csscomb.json -lv app/styles'
            },

            test: {
                command: 'npm test'
            },

            options: {
                preferLocal: true
            }
        }
    });

    grunt.registerTask('init', 'Install the client dependencies',
        ['shell:npm-install', 'shell:bower-install']
    );
};
<|endoftext|>


<commit_before>var
    Promise = require('bluebird'),
    fs = require('fs'),
    http = require('http'),
    https = require('https'),
	path = require('path'),
    express = require('express'),
    session = require('express-session'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    cookieParser = require('cookie-parser'),
    lessMiddleware = require('less-middleware'),
    request = require('request'),
    console = require('clim')();

http.globalAgent.maxSockets = 1024;

(function() {
    'use strict';

    var HttpHandler = function(controller, port, securePort, creds) {
        this.controller = controller;
        this.port = port;
        this.securePort = securePort;
        this.creds = creds;
        this.config = this.controller.config;
        this.httpConfig = this.config.http || { };
    }

    HttpHandler.prototype.start = function(creds) {
        var app = express();

        app.use(morgan('dev'));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: true }));
        app.use(cookieParser());

        var self = this;
        app.use(function(req, res, next) {
            if (self.controller.config.auth) {
                res.header(
                    'Access-Control-Allow-Headers',
                    'Content-Type, Authorization');
                res.header(
                    'Access-Control-Allow-Methods',
                    'GET, OPTIONS');
            }

            Object.keys(self.httpConfig.headers).map(function(key) {
                res.header(key, self.httpConfig.headers[key]);
            });
            res.header('X-powered-by', 'Hobu, Inc.');
            next();
        });

        if (this.httpConfig.enableStaticServe) this.registerStatic(app);
        this.registerCommands(app);

        if (this.port) {
            http.createServer(app).listen(this.port);
            console.log('HTTP server running on port', this.port);
        }

        if (this.securePort) {
            https.createServer(this.creds, app).listen(this.securePort);
            console.log('HTTPS server running on port', this.securePort);
        }
    }

    HttpHandler.prototype.registerStatic = function(app) {
        app.set('views', __dirname + '/static/views');
        app.set('view engine', 'jade');

        var publicDir = '/static/public';
        app.use(lessMiddleware(path.join(__dirname, publicDir)));
        app.use(express.static(__dirname + publicDir));

        app.get('/ws/:resourceId', function(req, res) {
            res.render('wsView');
        });

        app.get('/http/:resourceId', function(req, res) {
            res.render('httpView');
        });
    };

    HttpHandler.prototype.registerCommands = function(app) {
        var controller = this.controller;

        if (this.config.auth) {
            console.log('Proxying auth requests to', this.config.auth.path);
            var auths = { };

            app.options('*', function(req, res) {
                res.status(200).end();
            });

            var self = this;
            app.use('/resource/:resource(*)/:call(info|read|hierarchy)',
                    function(req, res, next)
            {
                var id = req.cookies[self.config.auth.cookieName];
                if (!id) return res.status(401).send();

                var resource = req.params.resource;
                if (!auths[id]) auths[id] = { };

                if (!auths[id][resource]) {
                    console.log('Authing', id);
                    var start = new Date();

                    auths[id][resource] = new Promise((resolve, reject) => {
                        var jar = request.jar();
                        var cookie = request.cookie(
                            self.config.auth.cookieName + '=' + id);
                        jar.setCookie(cookie, self.config.auth.path + resource);

                        var options = {
                            url: self.config.auth.path + resource,
                            rejectUnauthorized: false,
                            jar: jar
                        };

                        request(options, (err, authResponse) => {
                            if (err) {
                                console.log('Auth proxy err:', err);
                                return reject(500);
                            }

                            var code = authResponse.statusCode;
                            var ok = Math.floor(code / 100) == 2;

                            var time = (new Date() - start) / 1000;

                            console.log('Authed', id, 'in', time, 's:', ok);

                            var timeoutMinutes = ok ?
                                self.config.auth.cacheMinutes.good :
                                self.config.auth.cacheMinutes.bad;

                            setTimeout(() => {
                                delete auths[id][resource];
                                if (!Object.keys(auths[id]).length) {
                                    delete auths[id];
                                }
                            }, timeoutMinutes * 60000);

                            if (ok) resolve();
                            else reject(code);
                        });
                    });
                }

                auths[id][resource].then(() => next())
                .catch((statusCode) => res.status(statusCode).send());
            });
        }

        app.get('/resource/:resource(*)/info', function(req, res) {
            controller.info(req.params.resource, function(err, data) {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });

        app.get('/resource/:resource(*)/read', function(req, res) {
            // Terminate query on socket hangup.
            var keepGoing = true;
            req.on('close', () => keepGoing = false);

            controller.read(
                req.params.resource,
                req.query,
                function(err) {
                    if (err) return res.json(err.code || 500, err.message);
                    res.header('Content-Type', 'application/octet-stream');
                },
                function(err, data, done) {
                    if (err) {
                        console.error('Encountered data error');
                        return res.status(err.code || 500).json(err.message);
                    }

                    res.write(data);
                    if (done) res.end();

                    return keepGoing;
                }
            );
        });

        app.get('/resource/:resource(*)/hierarchy', function(req, res) {
            var resource = req.params.resource;
            var query = req.query;

            controller.hierarchy(resource, query, (err, data) => {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });
    }

    module.exports.HttpHandler = HttpHandler
})();

<commit_msg>Allow non-logged-in users to check for public resource auth.<commit_after>var
    Promise = require('bluebird'),
    fs = require('fs'),
    http = require('http'),
    https = require('https'),
	path = require('path'),
    express = require('express'),
    session = require('express-session'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    cookieParser = require('cookie-parser'),
    lessMiddleware = require('less-middleware'),
    request = require('request'),
    console = require('clim')();

http.globalAgent.maxSockets = 1024;

(function() {
    'use strict';

    var HttpHandler = function(controller, port, securePort, creds) {
        this.controller = controller;
        this.port = port;
        this.securePort = securePort;
        this.creds = creds;
        this.config = this.controller.config;
        this.httpConfig = this.config.http || { };
        this.auths = { };
    }

    HttpHandler.prototype.start = function(creds) {
        var app = express();

        app.use(morgan('dev'));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: true }));
        app.use(cookieParser());

        var self = this;
        app.use(function(req, res, next) {
            if (self.controller.config.auth) {
                res.header(
                    'Access-Control-Allow-Headers',
                    'Content-Type, Authorization');
                res.header(
                    'Access-Control-Allow-Methods',
                    'GET, OPTIONS');
            }

            Object.keys(self.httpConfig.headers).map(function(key) {
                res.header(key, self.httpConfig.headers[key]);
            });
            res.header('X-powered-by', 'Hobu, Inc.');
            next();
        });

        if (this.httpConfig.enableStaticServe) this.registerStatic(app);
        this.registerCommands(app);

        if (this.port) {
            http.createServer(app).listen(this.port);
            console.log('HTTP server running on port', this.port);
        }

        if (this.securePort) {
            https.createServer(this.creds, app).listen(this.securePort);
            console.log('HTTPS server running on port', this.securePort);
        }
    }

    HttpHandler.prototype.registerStatic = function(app) {
        app.set('views', __dirname + '/static/views');
        app.set('view engine', 'jade');

        var publicDir = '/static/public';
        app.use(lessMiddleware(path.join(__dirname, publicDir)));
        app.use(express.static(__dirname + publicDir));

        app.get('/ws/:resourceId', function(req, res) {
            res.render('wsView');
        });

        app.get('/http/:resourceId', function(req, res) {
            res.render('httpView');
        });
    };

    HttpHandler.prototype.registerCommands = function(app) {
        var controller = this.controller;

        if (this.config.auth) {
            console.log('Proxying auth requests to', this.config.auth.path);

            app.options('*', function(req, res) {
                res.status(200).end();
            });

            var self = this;
            app.use('/resource/:resource(*)/:call(info|read|hierarchy)',
                    function(req, res, next)
            {
                var id = req.cookies[self.config.auth.cookieName] || 'anon';

                var resource = req.params.resource;
                if (!self.auths[id]) self.auths[id] = { };

                if (!self.auths[id][resource]) {
                    console.log('Authing', id);
                    var start = new Date();

                    self.auths[id][resource] = new Promise((resolve, reject) =>
                    {
                        var jar = request.jar();
                        var cookie = request.cookie(
                            self.config.auth.cookieName + '=' + id);
                        jar.setCookie(cookie, self.config.auth.path + resource);

                        var options = {
                            url: self.config.auth.path + resource,
                            rejectUnauthorized: false,
                            jar: jar
                        };

                        request(options, (err, authResponse) => {
                            if (err) {
                                console.log('Auth proxy err:', err);
                                return reject(500);
                            }

                            var code = authResponse.statusCode;
                            var ok = Math.floor(code / 100) == 2;

                            var time = (new Date() - start) / 1000;

                            console.log('Authed', id, 'in', time, 's:', ok);

                            var timeoutMinutes = ok ?
                                self.config.auth.cacheMinutes.good :
                                self.config.auth.cacheMinutes.bad;

                            setTimeout(() => {
                                delete self.auths[id][resource];
                                if (!Object.keys(self.auths[id]).length) {
                                    delete self.auths[id];
                                }
                            }, timeoutMinutes * 60000);

                            if (ok) resolve();
                            else reject(code);
                        });
                    });
                }

                self.auths[id][resource].then(() => next())
                .catch((statusCode) => res.status(statusCode).send());
            });
        }

        app.get('/resource/:resource(*)/info', function(req, res) {
            controller.info(req.params.resource, function(err, data) {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });

        app.get('/resource/:resource(*)/read', function(req, res) {
            // Terminate query on socket hangup.
            var keepGoing = true;
            req.on('close', () => keepGoing = false);

            controller.read(
                req.params.resource,
                req.query,
                function(err) {
                    if (err) return res.json(err.code || 500, err.message);
                    res.header('Content-Type', 'application/octet-stream');
                },
                function(err, data, done) {
                    if (err) {
                        console.error('Encountered data error');
                        return res.status(err.code || 500).json(err.message);
                    }

                    res.write(data);
                    if (done) res.end();

                    return keepGoing;
                }
            );
        });

        app.get('/resource/:resource(*)/hierarchy', function(req, res) {
            var resource = req.params.resource;
            var query = req.query;

            controller.hierarchy(resource, query, (err, data) => {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });
    }

    module.exports.HttpHandler = HttpHandler
})();

<|endoftext|>