<commit_before>
var debug   = require('debug')('qmotion'),
    events  = require('events'),
    net     = require('net'),
    storage = require('node-persist');

const TCP_PORT = 9760,
      UDP_PORT = 9720;

var supportedPosition = [0, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];

function QMotion(ip) {
    events.EventEmitter.call(this);

    if (ip != undefined) {
        this.ip = ip;
        this.queue = [];
        this.tcpClient = null;
        this._readDevice();
    }
}
require('util').inherits(QMotion, events.EventEmitter);

QMotion.PositionState = {
    DECREASING: 0,
    INCREASING: 1,
    STOPPED:    2
}

QMotion.search = function() {
    var dgram = require("dgram");
    var server = dgram.createSocket("udp4");

    server.on("error", function (err) {
        debug("UDP error:\n" + err.stack);

        server.close();
        server.emit("timeout");
    });

    server.on("listening", function () {
        var address = server.address();
        debug("UDP listening " + address.address + ":" + address.port);

        var message = new Buffer("00", "hex");

        server.send(message, 0, message.length, UDP_PORT, "255.255.255.255", function(err) {
            if (err == null) {
                return;
            }

            debug("UDP error:\n" + err.stack);

            server.close();
            server.emit("timeout");
        });
    });

    server.on("message", function (msg, rinfo) {
        if (msg == undefined || msg.toString("hex") == "00") {
            return;
        }

        debug("UDP response: " + msg.toString("hex") + " from " + rinfo.address + ":" + rinfo.port);

        var device = new QMotion(rinfo.address);
        server.emit("found", device);

        device.on('initialized', function() {
            server.close();
        });
    });

    server.bind(UDP_PORT, "0.0.0.0", function() {
        server.setBroadcast(true);
    });

    return server;
}

QMotion.prototype.move = function(blind, position, callback) {
    callback = callback || function() {};

    debug("Move to "+ position + "%");

    var code = this._getCode(position);

    if (code == null) {
        callback(null);
        return;
    }

    var cmd = "1b0500";

    this._addToQueue(cmd, blind, code, callback, position);
}

QMotion.prototype._addToQueue = function(cmd, blind, code, callback, retVal) {
    this.queue.push([{"cmd": "1b00"}, {"cmd": "1b0100"}, {"cmd": cmd + blind.addr + code, "callback": callback, "blind": blind, "retVal": retVal}]);
    this._createClient();
}

QMotion.prototype._createClient = function() {
    if (this.tcpClient != null) {
        return
    }

    this.tcpClient = new net.Socket().unref();

    this.tcpClient.on('data', function(data) {
        debug("Recv: " + data.toString("hex"));

        var timeout = 10;

        if (this.item.callback !== undefined) {
            if (this.item.blind !== undefined) {
                this.item.blind._updatePositionState();
            }

            this.item.callback(this.item.retVal);
            timeout = 250;
        }

        this.item = null;

        setTimeout(function(self) {
            self._processQueue();
        }, timeout, this);
    }.bind(this));

    this.tcpClient.connect(TCP_PORT, this.ip, function() {
        this._processQueue();
    }.bind(this));
}

QMotion.prototype._getCode = function(value) {
    var code = null;

    switch(value) {
        case 0:
            code = '02';
            break;
        case 12.5:
            code = '0e';
            break;
        case 25:
            code = '0c';
            break;
        case 37.5:
            code = '0b';
            break;
        case 50:
            code = '08';
            break;
        case 62.5:
            code = '09';
            break;
        case 75:
            code = '07';
            break;
        case 87.5:
            code = '06';
            break;
        case 100:
            code = '01';
            break;
    }

    return code;
}

QMotion.prototype._persistPath = function() {
    var path = require('path');

    var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
    return path.join(home, ".qmotion", "persist");
}

QMotion.prototype._processQueue = function() {
    if (this.queue.length == 0) {
        if (this.tcpClient != null) {
            this.tcpClient.removeAllListeners();
            this.tcpClient.destroy();
            this.tcpClient = null;
        }

        return;
    }

    this.item = this.queue.shift();

    if (this.item.constructor === Array) {
        if (this.item.length > 1) {
            this.queue.unshift(this.item.slice(1));
        }

        this.item = this.item[0];
    }

    debug("Send: " + this.item.cmd);
    this.tcpClient.write(Buffer(this.item.cmd, "hex"));
}

QMotion.prototype._readDevice = function() {
    var client = new net.Socket();
    var blinds = {};

    storage.initSync({dir: this._persistPath()});

    var reMsg = /^1604/;
    var reGroup = /^162c/;
    var reScene = /^163b/;

    const lenMsg = 12;
    const lenGroup = 92;
    const lenScene = 122;

    client.on('data', function(data) {
        var hexString = data.toString("hex");

        debug("TCP response: " + hexString);

        if (reMsg.test(hexString) && hexString.length > lenMsg) {
            hexString = hexString.substr(lenMsg);
        }

        oldHex = hexString

        do {
            var index;

            if (reGroup.test(oldHex)) {
                index = lenGroup;
            }
            else if (reScene.test(oldHex)) {
                index = lenScene;
            }

            hexString = oldHex.substr(0, index);
            oldHex = oldHex.substr(index);

            if (reScene.test(hexString) || reMsg.test(hexString)) {
                continue;
            }

            var item = new QMotionBlind(this, hexString);
            this.emit('blind', item);
            blinds[item.addr] = item;
        }
        while(oldHex.length >= index);

        client.end();
    }.bind(this));

    client.connect(TCP_PORT, this.ip, function() {
        client.write(Buffer("16020000", "hex"));
    });

    client.on('end', function() {
        this.emit('initialized', blinds);
    }.bind(this));

    client.on('error', function(err) {
        debug(err);
    });
}

var defaults = {currentPosition: 0, positionState: QMotion.PositionState.STOPPED, targetPosition: 0, targetActualPosition: 0};

function QMotionBlind(device, hexString) {
    events.EventEmitter.call(this);

    this.name = Buffer(hexString.substr(52), "hex").toString().replace(/\u0000+/, "");
    this.addr = hexString.substr(10,2) + hexString.substr(8,2) + hexString.substr(6,2);
    this.buffer = hexString;
    this.device = device;

    this.state = storage.getItemSync(this.addr);

    if (this.state === undefined) {
        this.state = {};
    }

    for (key in defaults) {
        if (this.state[key] === undefined) {
            this.state[key] = defaults[key];
        }
    }

    storage.setItemSync(this.addr, this.state);

    this._timer = null;
}
require('util').inherits(QMotionBlind, events.EventEmitter);

QMotionBlind.prototype.identify = function(callback) {
	callback = callback || function() {};

    var oldPos = this.state.currentPosition;
    var index = supportedPosition.indexOf(oldPos);
    var newPos = index < 6 ? supportedPosition[index + 1] : supportedPosition[index - 1];

    this.move(newPos, function() {
        setTimeout(function(self) {
            self.move(oldPos, function() {
                callback();
            });
        }, 30 * 1000 / supportedPosition.length, this);

        setTimeout(function(self) {
            self.move(oldPos);
        }, (30 * 1000 / supportedPosition.length) + 1000, this);
    }.bind(this));
}

QMotionBlind.prototype.move = function(position, callback) {
	callback = callback || function() {};

	position = this._validatePosition(position);

	if (position == NaN) {
		callback(null);
	}

	if (this.state.targetPosition !== position) {
		this.state.targetPosition = position;
		debug('Emit targetPosition %s%', position);
		this.emit('targetPosition', position);
	}

    this.device.move(this, position, callback);
}

QMotionBlind.prototype._setTimer = function() {
    if (this._timer !== null) {
        return;
    }

    this._timer = setInterval(
        function(self) {
            var index = supportedPosition.indexOf(self.state.currentPosition);
            index = self.state.positionState == QMotion.PositionState.INCREASING ? index + 1 : index - 1;

            if (index < 0) {
                index = 0;
                clearInterval(self._timer);
            }
            else if (index > supportedPosition.length - 1) {
                index = supportedPosition.length - 1;
                clearInterval(self._timer);
            }

            self.state.currentPosition = supportedPosition[index];
            debug('Emit currentPosition %s%', self.state.currentPosition);
            self.emit("currentPosition", self.state.currentPosition);

            if (self.state.currentPosition == self.state.targetPosition) {
                clearInterval(self._timer);
                self._timer = null;
                self.state.positionState = QMotion.PositionState.STOPPED;
                self.emit("positionState", self.state.positionState);
            }

            storage.setItemSync(self.addr, self.state);
        },
        30 * 1000 / supportedPosition.length,
        this
    );
}

QMotionBlind.prototype._updatePositionState = function() {
    if (this.state.targetPosition > this.state.currentPosition) {
        this.state.positionState = QMotion.PositionState.INCREASING;
        this._setTimer();
    }
    else if (this.state.targetPosition < this.state.currentPosition) {
        this.state.positionState = QMotion.PositionState.DECREASING;
        this._setTimer();
    }
    else {
        this.state.positionState = QMotion.PositionState.STOPPED;
    }

    this.emit("positionState", this.state.positionState);
    storage.setItemSync(this.addr, this.state);
}

QMotionBlind.prototype._validatePosition = function(position) {
	var value = parseFloat(position);

    if (value == NaN) {
        return value;
    }

	if (supportedPosition.indexOf(value) == -1) {
        if (value < supportedPosition[0]) {
            value = supportedPosition[0];
        }
        else if (value > supportedPosition[supportedPosition.length - 1]) {
            value = supportedPosition[supportedPosition.length - 1];
        }
        else {
            for (i = 0; i < supportedPosition.length - 1 ; i++) {
                if (value < supportedPosition[i + 1]) {
                    value = (value > (supportedPosition[i] + 6.25)) ? supportedPosition[i + 1] : supportedPosition[i];
                    break;
                }
            }
        }
    }

    return value;
}

module.exports = QMotion;
<commit_msg>fix potential race condition; whitespace cleanup<commit_after>
var debug   = require('debug')('qmotion'),
    events  = require('events'),
    net     = require('net'),
    storage = require('node-persist');

const TCP_PORT = 9760,
      UDP_PORT = 9720;

var supportedPosition = [0, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];

function QMotion(ip) {
    events.EventEmitter.call(this);

    if (ip != undefined) {
        this.ip = ip;
        this.queue = [];
        this.tcpClient = null;
        this._readDevice();
    }
}
require('util').inherits(QMotion, events.EventEmitter);

QMotion.PositionState = {
    DECREASING: 0,
    INCREASING: 1,
    STOPPED:    2
}

QMotion.search = function() {
    var dgram = require("dgram");
    var server = dgram.createSocket("udp4");

    server.on("error", function (err) {
        debug("UDP error:\n" + err.stack);

        server.close();
        server.emit("timeout");
    });

    server.on("listening", function () {
        var address = server.address();
        debug("UDP listening " + address.address + ":" + address.port);

        var message = new Buffer("00", "hex");

        server.send(message, 0, message.length, UDP_PORT, "255.255.255.255", function(err) {
            if (err == null) {
                return;
            }

            debug("UDP error:\n" + err.stack);

            server.close();
            server.emit("timeout");
        });
    });

    server.on("message", function (msg, rinfo) {
        if (msg == undefined || msg.toString("hex") == "00") {
            return;
        }

        debug("UDP response: " + msg.toString("hex") + " from " + rinfo.address + ":" + rinfo.port);

        var device = new QMotion(rinfo.address);
        server.emit("found", device);

        device.on('initialized', function() {
            server.close();
        });
    });

    server.bind(UDP_PORT, "0.0.0.0", function() {
        server.setBroadcast(true);
    });

    return server;
}

QMotion.prototype.move = function(blind, position, callback) {
    callback = callback || function() {};

    debug("Move to "+ position + "%");

    var code = this._getCode(position);

    if (code == null) {
        callback(null);
        return;
    }

    var cmd = "1b0500";

    this._addToQueue(cmd, blind, code, callback, position);
}

QMotion.prototype._addToQueue = function(cmd, blind, code, callback, retVal) {
    this.queue.push([{"cmd": "1b00"}, {"cmd": "1b0100"}, {"cmd": cmd + blind.addr + code, "callback": callback, "blind": blind, "retVal": retVal}]);
    this._createClient();
}

QMotion.prototype._createClient = function() {
    if (this.tcpClient != null) {
        return
    }

    this.tcpClient = new net.Socket().unref();

    this.tcpClient.on('data', function(data) {
        debug("Recv: " + data.toString("hex"));

        var timeout = 10;

        if (this.item.callback !== undefined) {
            if (this.item.blind !== undefined) {
                this.item.blind._updatePositionState();
            }

            this.item.callback(this.item.retVal);
            timeout = 250;
        }

        this.item = null;

        setTimeout(function(self) {
            self._processQueue();
        }, timeout, this);
    }.bind(this));

    this.tcpClient.connect(TCP_PORT, this.ip, function() {
        this._processQueue();
    }.bind(this));
}

QMotion.prototype._getCode = function(value) {
    var code = null;

    switch(value) {
        case 0:
            code = '02';
            break;
        case 12.5:
            code = '0e';
            break;
        case 25:
            code = '0c';
            break;
        case 37.5:
            code = '0b';
            break;
        case 50:
            code = '08';
            break;
        case 62.5:
            code = '09';
            break;
        case 75:
            code = '07';
            break;
        case 87.5:
            code = '06';
            break;
        case 100:
            code = '01';
            break;
    }

    return code;
}

QMotion.prototype._persistPath = function() {
    var path = require('path');

    var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
    return path.join(home, ".qmotion", "persist");
}

QMotion.prototype._processQueue = function() {
    if (this.queue.length == 0) {
        if (this.tcpClient != null) {
            this.tcpClient.removeAllListeners();
            this.tcpClient.destroy();
            this.tcpClient = null;
        }

        return;
    }

    this.item = this.queue.shift();

    if (this.item.constructor === Array) {
        if (this.item.length > 1) {
            this.queue.unshift(this.item.slice(1));
        }

        this.item = this.item[0];
    }

    debug("Send: " + this.item.cmd);
    this.tcpClient.write(Buffer(this.item.cmd, "hex"));
}

QMotion.prototype._readDevice = function() {
    var client = new net.Socket();
    var blinds = {};

    storage.initSync({dir: this._persistPath()});

    var reMsg = /^1604/;
    var reGroup = /^162c/;
    var reScene = /^163b/;

    const lenMsg = 12;
    const lenGroup = 92;
    const lenScene = 122;

    client.on('data', function(data) {
        var hexString = data.toString("hex");

        debug("TCP response: " + hexString);

        if (reMsg.test(hexString) && hexString.length > lenMsg) {
            hexString = hexString.substr(lenMsg);
        }

        oldHex = hexString

        do {
            var index;

            if (reGroup.test(oldHex)) {
                index = lenGroup;
            }
            else if (reScene.test(oldHex)) {
                index = lenScene;
            }

            hexString = oldHex.substr(0, index);
            oldHex = oldHex.substr(index);

            if (reScene.test(hexString) || reMsg.test(hexString)) {
                continue;
            }

            var item = new QMotionBlind(this, hexString);
            this.emit('blind', item);
            blinds[item.addr] = item;
        }
        while(oldHex.length >= index);

        client.end();
    }.bind(this));

    client.connect(TCP_PORT, this.ip, function() {
        client.write(Buffer("16020000", "hex"));
    });

    client.on('end', function() {
        this.emit('initialized', blinds);
    }.bind(this));

    client.on('error', function(err) {
        debug(err);
    });
}

var defaults = {currentPosition: 0, positionState: QMotion.PositionState.STOPPED, targetPosition: 0, targetActualPosition: 0};

function QMotionBlind(device, hexString) {
    events.EventEmitter.call(this);

    this.name = Buffer(hexString.substr(52), "hex").toString().replace(/\u0000+/, "");
    this.addr = hexString.substr(10,2) + hexString.substr(8,2) + hexString.substr(6,2);
    this.buffer = hexString;
    this.device = device;

    this.state = storage.getItemSync(this.addr);

    if (this.state === undefined) {
        this.state = {};
    }

    for (key in defaults) {
        if (this.state[key] === undefined) {
            this.state[key] = defaults[key];
        }
    }

    storage.setItemSync(this.addr, this.state);

    this._timer = null;
}
require('util').inherits(QMotionBlind, events.EventEmitter);

QMotionBlind.prototype.identify = function(callback) {
    callback = callback || function() {};

    var oldPos = this.state.currentPosition;
    var index = supportedPosition.indexOf(oldPos);
    var newPos = index < 6 ? supportedPosition[index + 1] : supportedPosition[index - 1];

    this.move(newPos, function() {
        setTimeout(function(self) {
            self.move(oldPos, function() {
                callback();
            });
        }, 30 * 1000 / supportedPosition.length, this);

        setTimeout(function(self) {
            if (self.state.targetPosition == oldPos) {
                self.move(oldPos);
            }
        }, (30 * 1000 / supportedPosition.length) + 1000, this);
    }.bind(this));
}

QMotionBlind.prototype.move = function(position, callback) {
    callback = callback || function() {};

    position = this._validatePosition(position);

    if (position == NaN) {
        callback(null);
    }

    if (this.state.targetPosition !== position) {
        this.state.targetPosition = position;
        debug('Emit targetPosition %s%', position);
        this.emit('targetPosition', position);
    }

    this.device.move(this, position, callback);
}

QMotionBlind.prototype._setTimer = function() {
    if (this._timer !== null) {
        return;
    }

    this._timer = setInterval(
        function(self) {
            var index = supportedPosition.indexOf(self.state.currentPosition);
            index = self.state.positionState == QMotion.PositionState.INCREASING ? index + 1 : index - 1;

            if (index < 0) {
                index = 0;
                clearInterval(self._timer);
            }
            else if (index > supportedPosition.length - 1) {
                index = supportedPosition.length - 1;
                clearInterval(self._timer);
            }

            self.state.currentPosition = supportedPosition[index];
            debug('Emit currentPosition %s%', self.state.currentPosition);
            self.emit("currentPosition", self.state.currentPosition);

            if (self.state.currentPosition == self.state.targetPosition) {
                clearInterval(self._timer);
                self._timer = null;
                self.state.positionState = QMotion.PositionState.STOPPED;
                self.emit("positionState", self.state.positionState);
            }

            storage.setItemSync(self.addr, self.state);
        },
        30 * 1000 / supportedPosition.length,
        this
    );
}

QMotionBlind.prototype._updatePositionState = function() {
    if (this.state.targetPosition > this.state.currentPosition) {
        this.state.positionState = QMotion.PositionState.INCREASING;
        this._setTimer();
    }
    else if (this.state.targetPosition < this.state.currentPosition) {
        this.state.positionState = QMotion.PositionState.DECREASING;
        this._setTimer();
    }
    else {
        this.state.positionState = QMotion.PositionState.STOPPED;
    }

    this.emit("positionState", this.state.positionState);
    storage.setItemSync(this.addr, this.state);
}

QMotionBlind.prototype._validatePosition = function(position) {
    var value = parseFloat(position);

    if (value == NaN) {
        return value;
    }

    if (supportedPosition.indexOf(value) == -1) {
        if (value < supportedPosition[0]) {
            value = supportedPosition[0];
        }
        else if (value > supportedPosition[supportedPosition.length - 1]) {
            value = supportedPosition[supportedPosition.length - 1];
        }
        else {
            for (i = 0; i < supportedPosition.length - 1 ; i++) {
                if (value < supportedPosition[i + 1]) {
                    value = (value > (supportedPosition[i] + 6.25)) ? supportedPosition[i + 1] : supportedPosition[i];
                    break;
                }
            }
        }
    }

    return value;
}

module.exports = QMotion;
<|endoftext|>


<commit_before>function human() {
  this.name = makeName("weapon") + " " + makeName("weapon")
  this.health = 100 + Math.round(p5.random(-2,10))
  this.damage = Math.round(p5.random(2,4)*2)
  this.di =  Math.round(p5.random(1,3))
  this.regenSpeed = 5
  this.weapon = p5.random(weapons)

  this.levelUp = function () {
    this.maxStam += Math.round(p5.random(1,2+(level/5)))
    this.maxHealth += Math.round(p5.random(1,1+(level/7)))
    this.health += 20
    this.stamina += 10
  }

  this.regen = function () {
    if (this.stamina < this.maxStam) {
      this.stamina = +(this.stamina + (this.regenSpeed+(level+(this.maxStam/10)/50))/15).toFixed(3)
    }else if (this.stamina > this.maxStam) {
      this.stamina = this.maxStam
    }

    if (this.health < this.maxHealth) {
      this.health = +(this.health + (this.regenSpeed+(level/5)+(this.maxHealth/40))/50).toFixed(2)
    }else if (this.health > this.maxHealth) {
      this.health = this.maxHealth
    }

  }
  this.attack = function () {
    var damage = 0
    if (p5.random(this.weapon.accuracy,100)>30) {
      this.stamina = +(this.stamina-((this.weapon.damage*this.weapon.di)/20)).toFixed(0);
      for (var i = 0; i < this.weapon.di; i++) {
        damage += Math.round(p5.random(1,this.weapon.damage))
      }
    }
      return damage;
    }
  this.stamina = 50 + Math.round(p5.random(-5,5))
  this.maxHealth = this.health
  this.maxStam = this.stamina
}

// enemy
function Enemy() {
  this.legendary = false

  this.name = makeName("weapon") + " " + makeName("weapon")
  this.health = 100 + Math.round(p5.random(-1+(level*2),5+(level*5)))
  this.damage = Math.round(p5.random(2,4+(level/2))*2)
  this.di =  Math.round(p5.random(1,2+(level/10)))
  this.regenSpeed = 1
  this.weapon = p5.random(weapons)

  this.regen = function () {
    this.regenSpeed = 1+(level/1.5)
    if (this.stamina < this.maxStam) {
      this.stamina = +(this.stamina + this.regenSpeed/10).toFixed(3)
    }else if (this.stamina > this.maxStam) {
      this.stamina = this.maxStam
    }

    if (this.health < this.maxHealth) {
      this.health = +(this.health + this.regenSpeed/100).toFixed(3)
    }else if (this.health > this.maxHealth) {
      this.health = this.maxHealth
    }
  }

  this.attack = function () {
    var damage = 0
    if (p5.random(this.weapon.accuracy,100)>50) {
      for (var i = 0; i < this.weapon.di; i++) {
        damage += Math.round(p5.random(1,this.weapon.damage))
      }
    }
      return damage;
    }
  this.stamina = 50 + Math.round(p5.random(-5,5))
  this.maxStam = this.stamina
  this.maxHealth = this.health
  if (p5.random(0,400-level*2)<=10) {
    this.legendary = true
    this.maxHealth *= 1.5
    this.health = this.maxHealth
    this.name = "The Legendary " + this.name
    this.weapon.damage += Math.round(p5.random(1,7+(level/2)))
    this.weapon.di += Math.round(p5.random(0,1+(level/20)))
    this.weapon.accuracy += Math.round(p5.random(0,1+(level/20)))
    this.weapon.speed -= 300
    this.weapon.speed = +(this.weapon.speed).toFixed(2)
    if (this.weapon.speed <=0) {
      this.weapon.speed = 1
    }
  }

}

// weapon
function Weapon() {
  this.level = level+1
  this.speed = p5.random(100,1000)
  this.damage = Math.round(p5.random(2,4 + (level/4))*2)
  this.type = "'s " + p5.random(wTypes)
  this.name = makeName()
  this.di =  Math.round(p5.random(1,3 + (level/10)))
  this.legendary = false
  this.accuracy = +(p5.random(0,50 + (level)).toFixed(2))
  if (p5.random(0,100)<=10) {
    this.legendary = true
    this.name = this.name + "'s Legendary "
    this.type = this.type.slice(3)
    this.damage += Math.round(p5.random(1,7+(level/2)))
    this.di += Math.round(p5.random(1,1+(level/40)))
    this.accuracy += Math.round(p5.random(1,1+(level/10)))
    this.speed += p5.random(10,400)
    if (this.speed >= 1000) {
      this.speed = p5.random(800,999)
    }

  }
  this.name = this.name + this.type
  this.speed = +(this.speed).toFixed(2)
}
// armor
function Armor() {
  this.level = level+ Math.round(p5.random(-5,1))
  if (this.level <0) {
    this.level = 0
  }

  this.type = p5.random(Atypes)
  this.name = makeName()
  this.legendary = false
  if (p5.random(0,100)<=10) {
    this.legendary = true
    this.name = this.name + "'s Legendary "
  }
  this.name = this.name + this.type
}
<commit_msg>Beauty update<commit_after>function human() {
  this.name = makeName("weapon") + " " + makeName("weapon")
  this.health = 100 + Math.round(p5.random(-2,10))
  this.damage = Math.round(p5.random(2,4)*2)
  this.di =  Math.round(p5.random(1,3))
  this.regenSpeed = 5
  this.weapon = p5.random(weapons)

  this.levelUp = function () {
    this.maxStam += Math.round(p5.random(1 , 2+(level/5)))
    this.maxHealth += Math.round(p5.random(1 , 1+(level/7)))
    this.health += 20
    this.stamina += 10
  }

  this.regen = function () {
    if (this.stamina < this.maxStam) {
      this.stamina = +(this.stamina + (this.regenSpeed+(level+(this.maxStam/10)/50))/15).toFixed(3)
    }else if (this.stamina > this.maxStam) {
      this.stamina = this.maxStam
    }

    if (this.health < this.maxHealth) {
      this.health = +(this.health + (this.regenSpeed+(level/5)+(this.maxHealth/40))/50).toFixed(2)
    }else if (this.health > this.maxHealth) {
      this.health = this.maxHealth
    }

  }
  this.attack = function () {
    var damage = 0
    if (p5.random(this.weapon.accuracy,100)>30) {
      this.stamina = +(this.stamina-((this.weapon.damage*this.weapon.di)/20)).toFixed(0);
      for (var i = 0; i < this.weapon.di; i++) {
        damage += Math.round(p5.random(1,this.weapon.damage))
      }
    }
      return damage;
    }
  this.stamina = 50 + Math.round(p5.random(-5,5))
  this.maxHealth = this.health
  this.maxStam = this.stamina
}

// enemy
function Enemy() {
  this.legendary = false

  this.name = makeName("weapon") + " " + makeName("weapon")
  this.health = 100 + Math.round(p5.random(-1+(level*2),5+(level*5)))
  this.damage = Math.round(p5.random(2,4+(level/2))*2)
  this.di =  Math.round(p5.random(1,2+(level/10)))
  this.regenSpeed = 1
  this.weapon = p5.random(weapons)

  this.regen = function () {
    this.regenSpeed = 1+(level/1.5)
    if (this.stamina < this.maxStam) {
      this.stamina = +(this.stamina + this.regenSpeed/10).toFixed(3)
    }else if (this.stamina > this.maxStam) {
      this.stamina = this.maxStam
    }

    if (this.health < this.maxHealth) {
      this.health = +(this.health + this.regenSpeed/100).toFixed(3)
    }else if (this.health > this.maxHealth) {
      this.health = this.maxHealth
    }
  }

  this.attack = function () {
    var damage = 0
    if (p5.random(this.weapon.accuracy,100)>50) {
      for (var i = 0; i < this.weapon.di; i++) {
        damage += Math.round(p5.random(1,this.weapon.damage))
      }
    }
      return damage;
    }
  this.stamina = 50 + Math.round(p5.random(-5,5))
  this.maxStam = this.stamina
  this.maxHealth = this.health
  if (p5.random(0,400-level*2)<=10) {
    this.legendary = true
    this.maxHealth *= 1.5
    this.health = this.maxHealth
    this.name = "The Legendary " + this.name
    this.weapon.damage += Math.round(p5.random(1,7+(level/2)))
    this.weapon.di += Math.round(p5.random(0,1+(level/20)))
    this.weapon.accuracy += Math.round(p5.random(0,1+(level/20)))
    this.weapon.speed -= 300
    this.weapon.speed = +(this.weapon.speed).toFixed(2)
    if (this.weapon.speed <=0) {
      this.weapon.speed = 1
    }
  }

}

// weapon
function Weapon() {
  this.level = level+1
  this.speed = p5.random(100,1000)
  this.damage = Math.round(p5.random(2,4 + (level/4))*2)
  this.type = "'s " + p5.random(wTypes)
  this.name = makeName()
  this.di =  Math.round(p5.random(1,3 + (level/10)))
  this.legendary = false
  this.accuracy = +(p5.random(0,50 + (level)).toFixed(2))
  if (p5.random(0,100)<=10) {
    this.legendary = true
    this.name = this.name + "'s Legendary "
    this.type = this.type.slice(3)
    this.damage += Math.round(p5.random(1,7+(level/2)))
    this.di += Math.round(p5.random(1,1+(level/40)))
    this.accuracy += Math.round(p5.random(1,1+(level/10)))
    this.speed += p5.random(10,400)
    if (this.speed >= 1000) {
      this.speed = p5.random(800,999)
    }

  }
  this.name = this.name + this.type
  this.speed = +(this.speed).toFixed(2)
}
// armor
function Armor() {
  this.level = level+ Math.round(p5.random(-5,1))
  if (this.level <0) {
    this.level = 0
  }

  this.type = p5.random(Atypes)
  this.name = makeName()
  this.legendary = false
  if (p5.random(0,100)<=10) {
    this.legendary = true
    this.name = this.name + "'s Legendary "
  }
  this.name = this.name + this.type
}
<|endoftext|>


<commit_before>/**
 * debounce
 * @param {integer} milliseconds This param indicates the number of milliseconds
 *     to wait after the last call before calling the original function .
 * @return {function} This returns a function that when called will wait the
 *     indicated number of milliseconds after the last call before
 *     calling the original function.
 */
Function.prototype.debounce = function (milliseconds) {
  var baseFunction = this,
    timer = null,
    wait = milliseconds;

  return function () {
    var self = this,
      args = arguments;

    function complete() {
      baseFunction.apply(self, args);
      timer = null;
    }

    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(complete, wait);
  };
};

/**
 * throttle
 * @param {integer} milliseconds This param indicates the number of milliseconds
 *     to wait between calls before calling the original function.
 * @return {function} This returns a function that when called will wait the
 *     indicated number of milliseconds between calls before
 *     calling the original function.
 */
Function.prototype.throttle = function (milliseconds) {
  var baseFunction = this,
    lastEventTimestamp = null,
    limit = milliseconds;

  return function () {
    var self = this,
      args = arguments,
      now = Date.now();

    if (!lastEventTimestamp || now - lastEventTimestamp >= limit) {
      lastEventTimestamp = now;
      baseFunction.apply(self, args);
    }
  };
};<commit_msg>cleaned up formatting<commit_after>/**
 * debounce
 * @param {integer} milliseconds This param indicates the number of milliseconds
 *     to wait after the last call before calling the original function .
 * @return {function} This returns a function that when called will wait the
 *     indicated number of milliseconds after the last call before
 *     calling the original function.
 */
Function.prototype.debounce = function (milliseconds) {
    var baseFunction = this,
        timer = null,
        wait = milliseconds;

    return function () {
        var self = this,
            args = arguments;

        function complete() {
            baseFunction.apply(self, args);
            timer = null;
        }

        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(complete, wait);
    };
};

/**
* throttle
* @param {integer} milliseconds This param indicates the number of milliseconds
*     to wait between calls before calling the original function.
* @return {function} This returns a function that when called will wait the
*     indicated number of milliseconds between calls before
*     calling the original function.
*/
Function.prototype.throttle = function (milliseconds) {
    var baseFunction = this,
        lastEventTimestamp = null,
        lastimit = milliseconds;

    return function () {
        var self = this,
            args = arguments,
            now = Date.now();

        if (!lastEventTimestamp || now - lastEventTimestamp >= limit) {
            lastEventTimestamp = now;
            baseFunction.apply(self, args);
        }
    };
};<|endoftext|>


<commit_before>(function() {
	/**
	 * Load the list of files asynchronously. This uses recursion in
	 * order to create a queue of files that will be loaded serially.
	 * Each layer, starting at the bottom, loads a file and then loads
	 * the layer on top of it. The very top file on the stack will have
	 * zero files to load, so instead it will be the one to call the
	 * callback to notify the caller that all the content is loaded.
	 *
	 * @param {Object} context function to call this method in the context of
	 * @param {Array.<string>} paths array of strings containing relative paths for required locale data files
	 * @param {Array} results empty array in which to place the resulting json when it is loaded from a file
	 * @param {Object} params An object full of parameters that the caller is passing to this function to help load the files
	 * @param {function(Array.<Object>)} callback callback to call when this function is finished attempting
	 * to load all the files that exist and can be loaded
	 */
	function loadFiles(context, paths, results, params, callback) {
		if (paths.length > 0) {
			var path = paths.shift();
			var ajax = new enyo.Ajax({url: enyo.path.rewrite("$lib/enyo-ilib/ilib/locale/" + path)});
			//console.log("moondemo2: browser/async: attempting to load lib/enyo-ilib/ilib/locale/" + path);
			var resultFunc = function(inSender, json) {
				//console.log("moondemo2: " + (json ? "success" : "failed"));
				results.push((typeof(json) === 'object') ? json : undefined);
				if (paths.length > 0) {
					loadFiles(context, paths, results, params, callback);
				} else {
					// only the bottom item on the stack will call
					// the callback
					callback.call(context, results);
				}
			};
			ajax.response(this, resultFunc);
			ajax.error(this, function(inSender, json) {
				//console.log("moondemo2: browser/async: attempting to load resources/" + path);
				// not there? Try the standard place instead
				var file = "resources/" + path;
				var ajax2 = new enyo.Ajax({url: file});

				ajax2.response(this, resultFunc);
				ajax2.error(this, resultFunc);
				ajax2.go();
			});
			ajax.go();
		}
	}

	ilib.setLoaderCallback(enyo.bind(this, function(paths, sync, params, callback) {
		if (sync) {
			var ret = [];
			// synchronous
			paths.forEach(function (path) {
				// console.log("browser/sync: attempting to load lib/enyo-ilib/ilib/locale/" + path);
				var ajax = new enyo.Ajax({
					url: enyo.path.rewrite("$lib/enyo-ilib/ilib/locale/" + path),
					sync: true
				});

				var handler = function(inSender, json) {
					// console.log((json ? "success" : "failed"));
					ret.push((typeof(json) === 'object') ? json : undefined);
				};
				ajax.response(this, handler);
				ajax.error(this, function(inSender, json) {
					// console.log("browser/sync: Now attempting to load resources/" + path);
					var ajax2 = new enyo.Ajax({
						url: "resources/" + path,
						sync: true
					});
					ajax2.response(this, handler);
					ajax2.error(this, handler);
					ajax2.go();
				});
				ajax.go();
			});

			if (typeof(callback) === 'function') {
				callback.call(this, ret);
			}
			return ret;
		}

		// asynchronous
		var results = [];
		loadFiles(this, paths, results, params, callback);
	}));

	if (typeof(window.UILocale) !== 'undefined') {
		// this is a hack until GF-1581 is fixed
		ilib.setLocale(window.UILocale);
	}

	// This is temporary special code for webOS to be able to test apps with a font that works
	// in other locales.
	var li = new ilib.LocaleInfo(); // for the current locale
	var locale = li.getLocale();
	enyo.ready(function () {
		var base = " enyo-locale-";
		if (li.getScript() !== "Latn" || locale.getLanguage() === "vi") {
			// allow enyo to define other fonts for non-Latin languages, or Vietnamese which
			// is Latin-based, but the characters with multiple accents don't appear in the
			// regular fonts, creating a strange "ransom note" look with a mix of fonts in the
			// same word. So, treat it like a non-Latin language in order to get all the characters
			// to display with the same font.
			enyo.dom.getFirstElementByTagName("body").className += base + "non-latin";
		}

		// allow enyo to apply right-to-left styles to the app and widgets if necessary
		var script = new ilib.ScriptInfo(li.getDefaultScript());
		if (script.getScriptDirection() === "rtl") {
			enyo.dom.getFirstElementByTagName("body").className += base + "right-to-left";
		}

		// allow enyo or the apps to give CSS classes that are specific to the language, country, or script
		if (locale.getLanguage()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage();
			if (locale.getScript()) {
				enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getScript();
				if (locale.getRegion()) {
					enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getScript() + "-" + locale.getRegion();
				}
			} else if (locale.getRegion()) {
				enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getRegion();
			}
		}
		if (locale.getScript()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getScript();
		}
		if (locale.getRegion()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getRegion();
		}
	});
})();

/*
 * Reset the $L function to use ilib instead of the dummy function that enyo
 * comes with by default.
 */
$L = (function() {
	var lfunc = function (string) {
		var str = $L.rb.getString(string);
		return str.toString();
	};
	var locale = new ilib.Locale();
	lfunc.rb = new ilib.ResBundle({
		type: "html",
		name: "strings",
		missing: locale.getLanguage() === "en" ? "source" : "pseudo",
		lengthen: true		// if pseudo-localizing, this tells it to lengthen strings
	});
	return lfunc;
})();

/**
 * Set the locale for the strings that $L loads. This may reload the
 * string resources if necessary.
 */
$L.setLocale = function (spec) {
	if (spec !== $L.rb.getLocale().getSpec()) {
		$L.rb = new ilib.ResBundle({
			locale: spec,
			type: "html",
			name: "strings",
			missing: locale.getLanguage() === "en" ? "source" : "pseudo",
			lengthen: true		// if pseudo-localizing, this tells it to lengthen strings
		});
	}
};
<commit_msg>GF-14590: adds rtl property on enyo.Control<commit_after>(function() {
	/**
	 * Load the list of files asynchronously. This uses recursion in
	 * order to create a queue of files that will be loaded serially.
	 * Each layer, starting at the bottom, loads a file and then loads
	 * the layer on top of it. The very top file on the stack will have
	 * zero files to load, so instead it will be the one to call the
	 * callback to notify the caller that all the content is loaded.
	 *
	 * @param {Object} context function to call this method in the context of
	 * @param {Array.<string>} paths array of strings containing relative paths for required locale data files
	 * @param {Array} results empty array in which to place the resulting json when it is loaded from a file
	 * @param {Object} params An object full of parameters that the caller is passing to this function to help load the files
	 * @param {function(Array.<Object>)} callback callback to call when this function is finished attempting
	 * to load all the files that exist and can be loaded
	 */
	function loadFiles(context, paths, results, params, callback) {
		if (paths.length > 0) {
			var path = paths.shift();
			var ajax = new enyo.Ajax({url: enyo.path.rewrite("$lib/enyo-ilib/ilib/locale/" + path)});
			//console.log("moondemo2: browser/async: attempting to load lib/enyo-ilib/ilib/locale/" + path);
			var resultFunc = function(inSender, json) {
				//console.log("moondemo2: " + (json ? "success" : "failed"));
				results.push((typeof(json) === 'object') ? json : undefined);
				if (paths.length > 0) {
					loadFiles(context, paths, results, params, callback);
				} else {
					// only the bottom item on the stack will call
					// the callback
					callback.call(context, results);
				}
			};
			ajax.response(this, resultFunc);
			ajax.error(this, function(inSender, json) {
				//console.log("moondemo2: browser/async: attempting to load resources/" + path);
				// not there? Try the standard place instead
				var file = "resources/" + path;
				var ajax2 = new enyo.Ajax({url: file});

				ajax2.response(this, resultFunc);
				ajax2.error(this, resultFunc);
				ajax2.go();
			});
			ajax.go();
		}
	}

	ilib.setLoaderCallback(enyo.bind(this, function(paths, sync, params, callback) {
		if (sync) {
			var ret = [];
			// synchronous
			paths.forEach(function (path) {
				// console.log("browser/sync: attempting to load lib/enyo-ilib/ilib/locale/" + path);
				var ajax = new enyo.Ajax({
					url: enyo.path.rewrite("$lib/enyo-ilib/ilib/locale/" + path),
					sync: true
				});

				var handler = function(inSender, json) {
					// console.log((json ? "success" : "failed"));
					ret.push((typeof(json) === 'object') ? json : undefined);
				};
				ajax.response(this, handler);
				ajax.error(this, function(inSender, json) {
					// console.log("browser/sync: Now attempting to load resources/" + path);
					var ajax2 = new enyo.Ajax({
						url: "resources/" + path,
						sync: true
					});
					ajax2.response(this, handler);
					ajax2.error(this, handler);
					ajax2.go();
				});
				ajax.go();
			});

			if (typeof(callback) === 'function') {
				callback.call(this, ret);
			}
			return ret;
		}

		// asynchronous
		var results = [];
		loadFiles(this, paths, results, params, callback);
	}));

	if (typeof(window.UILocale) !== 'undefined') {
		// this is a hack until GF-1581 is fixed
		ilib.setLocale(window.UILocale);
	}

	// This is temporary special code for webOS to be able to test apps with a font that works
	// in other locales.
	var li = new ilib.LocaleInfo(); // for the current locale
	var locale = li.getLocale();
	enyo.ready(function () {
		var base = " enyo-locale-";
		if (li.getScript() !== "Latn" || locale.getLanguage() === "vi") {
			// allow enyo to define other fonts for non-Latin languages, or Vietnamese which
			// is Latin-based, but the characters with multiple accents don't appear in the
			// regular fonts, creating a strange "ransom note" look with a mix of fonts in the
			// same word. So, treat it like a non-Latin language in order to get all the characters
			// to display with the same font.
			enyo.dom.getFirstElementByTagName("body").className += base + "non-latin";
		}

		// allow enyo to apply right-to-left styles to the app and widgets if necessary
		var script = new ilib.ScriptInfo(li.getDefaultScript());
		if (script.getScriptDirection() === "rtl") {
			enyo.dom.getFirstElementByTagName("body").className += base + "right-to-left";
			if (enyo.Control) {
				enyo.Control.prototype.rtl = true;
			}
		}

		// allow enyo or the apps to give CSS classes that are specific to the language, country, or script
		if (locale.getLanguage()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage();
			if (locale.getScript()) {
				enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getScript();
				if (locale.getRegion()) {
					enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getScript() + "-" + locale.getRegion();
				}
			} else if (locale.getRegion()) {
				enyo.dom.getFirstElementByTagName("body").className += base + locale.getLanguage() + "-" + locale.getRegion();
			}
		}
		if (locale.getScript()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getScript();
		}
		if (locale.getRegion()) {
			enyo.dom.getFirstElementByTagName("body").className += base + locale.getRegion();
		}
	});
})();

/*
 * Reset the $L function to use ilib instead of the dummy function that enyo
 * comes with by default.
 */
$L = (function() {
	var lfunc = function (string) {
		var str = $L.rb.getString(string);
		return str.toString();
	};
	var locale = new ilib.Locale();
	lfunc.rb = new ilib.ResBundle({
		type: "html",
		name: "strings",
		missing: locale.getLanguage() === "en" ? "source" : "pseudo",
		lengthen: true		// if pseudo-localizing, this tells it to lengthen strings
	});
	return lfunc;
})();

/**
 * Set the locale for the strings that $L loads. This may reload the
 * string resources if necessary.
 */
$L.setLocale = function (spec) {
	if (spec !== $L.rb.getLocale().getSpec()) {
		$L.rb = new ilib.ResBundle({
			locale: spec,
			type: "html",
			name: "strings",
			missing: locale.getLanguage() === "en" ? "source" : "pseudo",
			lengthen: true		// if pseudo-localizing, this tells it to lengthen strings
		});
	}
};
<|endoftext|>


<commit_before>/** global: updateScreenUrl */

/**
 * Screen class constructor
 * @param {string} updateScreenUrl global screen update checks url
 */
function Screen(updateScreenUrl) {
  this.fields = [];
  this.url = updateScreenUrl;
  this.lastChanges = null;
  this.endAt = null;
  this.nextUrl = null;
  this.stopping = false;
  this.cache = {};
}

/**
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
 */
Screen.prototype.checkUpdates = function() {
  var s = this;
  $.get(this.url, function(j) {
    if (j.success) {
      if (s.lastChanges == null) {
        s.lastChanges = j.data.lastChanges;
      } else if (s.lastChanges != j.data.lastChanges) {
        s.reload();
        s.nextUrl = null;
        return;
      }

      if (j.data.duration > 0) {
        // Setup next screen
        s.reload(j.data.duration * 1000);
        s.nextUrl = j.data.nextScreenUrl;
      }
    }
  });
}

/**
 * Start Screen reload procedure, checking for every field timeout
 */
Screen.prototype.reload = function(minDuration) {
  var endAt = Date.now() + (minDuration ? minDuration : 0);
  if (this.stopping && this.endAt < endAt) {
    return;
  }

  this.endAt = minDuration ? Date.now() + minDuration : 0;
  this.stopping = true;
  for (var i in this.fields) {
    if (!this.fields.hasOwnProperty(i)) {
      continue;
    }
    var f = this.fields[i];
    if (f.timeout && f.endAt > this.endAt) {
      this.endAt = f.endAt;
    }
  }

  if (this.endAt === 0) {
    this.doReload();
  }
}

/**
 * Actual Screen reload action
 */
Screen.prototype.doReload = function() {
  if (this.nextUrl) {
    window.location = this.nextUrl;
  } else {
    window.location.reload();
  }
}

/**
 * Check every field for content
 * @param  {Content} data
 * @return {boolean} content is displayed
 */
Screen.prototype.displaysData = function(data) {
  return this.fields.filter(function(field) {
    return field.current && field.current.data == data;
  }).length > 0;
}

/**
 * Content class constructor
 * @param {array} c content attributes
 */
function Content(c) {
  this.id = c.id;
  this.data = c.data;
  this.duration = c.duration * 1000;
  this.type = c.type;
  this.src = null;

  if (this.shouldPreload()) {
    this.preload();
  }
}

/**
 * Check if content should be ajax preloaded
 * @return {boolean}
 */
Content.prototype.shouldPreload = function() {
  return this.canPreload() && !this.isPreloading() && !this.isPreloaded();
}

/**
 * Check if content has pre-loadable material
 * @return {boolean}
 */
Content.prototype.canPreload = function() {
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
}

/**
 * Extract url from contant data
 * @return {string} resource url
 */
Content.prototype.getResource = function() {
  if (this.src) {
    return this.src;
  }
  var srcMatch = this.data.match(/src="([^"]+)"/);
  if (!srcMatch) {
    return false;
  }
  var src = srcMatch[1];
  if (src.indexOf('/') === 0) {
    src = window.location.origin + src;
  }
  if (src.indexOf('http') !== 0) {
    return false;
  }

  this.src = src;
  return src;
}

/**
 * Check cache for preload status of content
 * @return {Boolean}
 */
Content.prototype.isPreloaded = function() {
  if (!this.canPreload()) {
    return true;
  }

  var cache = screen.cache[this.getResource()]
  switch (cache) {
    case undefined: // unset
    case false: // preloading
      return false;
    case true: // preloaded without expire
      return true;
    default: // check expire
      return (new Date()).valueOf() < cache;
  }
}

/**
 * Set content cache status
 * @param {string} expires header
 */
Content.prototype.setPreloaded = function(expires) {
  switch (expires) {
    case null:
    case undefined:
      // No Expires header, don't bother with caching
    case true:
      // Force content cache
      screen.cache[this.getResource()] = true;
      break;
    case false:
      // Discard content cache and disallow display
      delete screen.cache[this.getResource()];
      break;
    default:
      var exp = new Date(expires).valueOf();
      var diff = exp - (new Date()).valueOf();
      // Don't check short Expires and add 5 sec to valid Expires
      screen.cache[this.getResource()] = diff < 10000 ? true : exp + 5000;
  }
}

/**
 * Check cache for in progress preloading
 * @return {Boolean}
 */
Content.prototype.isPreloading = function() {
  return screen.cache[this.getResource()] === false;
}

/**
 * Set content preloading status
 * @param {boolean} state is preloading
 */
Content.prototype.setPreloading = function(state) {
  if (state) {
    screen.cache[this.getResource()] = false;
  } else if (this.isPreloading()) {
    delete screen.cache[this.getResource()];
  }
}

/**
 * Ajax call to preload content
 * @return {[type]} [description]
 */
Content.prototype.preload = function() {
  var src = this.getResource();
  if (!src) {
    this.setPreloaded(true);
    return;
  }
  this.setPreloading(true);

  var c = this;
  $.ajax(src).done(function(data, textStatus, jqXHR) {
    c.setPreloaded(jqXHR.getResponseHeader('Expires'));
  }).fail(function() {
    c.setPreloaded(false); // Discard until next Content init
  });
}

/**
 * Field class constructor
 * @param {jQuery.Object} $f field object
 */
function Field($f) {
  this.$field = $f;
  this.id = $f.attr('data-id');
  this.url = $f.attr('data-url');
  this.types = $f.attr('data-types').split(' ');
  this.canUpdate = this.url != null;
  this.contents = [];
  this.previous = null;
  this.current = null;
  this.next = null;
  this.timeout = null;
  this.endAt = null;
}

/**
 * Retrieves contents from backend for this field
 */
Field.prototype.getContents = function() {
  if (!this.canUpdate) {
    return;
  }

  var f = this;
  $.get(this.url, function(j) {
    if (j.success) {
      f.contents = j.next.map(function(c) {
        return new Content(c);
      });
      if (!f.timeout && f.contents.length) {
        f.pickNext();
      }
    } else {
      f.setError(j.message || 'Error');
    }
  });
}

/**
 * Display error in field text
 */
Field.prototype.setError = function(err) {
  this.$field.text(err);
}

/**
 * Randomize order
 */
Field.prototype.randomizeSortContents = function() {
  this.contents = this.contents.sort(function(a, b) {
    return Math.random() - 0.5;
  });
}

/**
 * Loop through field contents to pick next displayable content
 */
Field.prototype.pickNext = function() {
  if (screen.stopping && screen.endAt < Date.now()) { // Stoping screen
    screen.doReload();
    return;
  }

  this.previous = this.current;
  this.current = null;
  var pData = this.previous && this.previous.data;
  // Avoid repeat & other field same content
  this.randomizeSortContents();
  for (var i = 0; i < this.contents.length; i++) {
    var c = this.contents[i];
    // Skip too long or not preloaded content
    if ((screen.endAt != null && c.duration + Date.now() > screen.endAt) || !c.isPreloaded()) {
      continue;
    }

    if (c.data == pData) {
      // Will repeat, avoid if enough content
      if (this.contents.length < 2) {
        this.next = c;
        break;
      }
      continue;
    }

    if (screen.displaysData(c.data)) {
      // Same content already displayed on other field, avoid if enough content
      if (this.contents.length < 3) {
        this.next = c;
        break;
      }
      continue;
    }

    this.next = c;
  }

  this.display();
}

/**
 * Display next content in field html
 */
Field.prototype.display = function() {
  var f = this;
  if (this.next && this.next.duration > 0) {
    this.current = this.next
    this.next = null;
    this.$field.html(this.current.data);
    this.$field.show();
    if (this.$field.text() != '') {
      this.$field.textfill({
        maxFontPixels: 0,
      });
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, this.current.duration);
    this.endAt = this.current.duration + Date.now()
  } else {
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, 2000);
  }
}

/**
 * jQuery.load event
 * Initialize Screen and Fields
 * Setup updates interval timeouts
 */
var screen = null;

function onLoad() {
  screen = new Screen(updateScreenUrl);
  // Init
  $('.field').each(function() {
    var f = new Field($(this));
    f.getContents();
    screen.fields.push(f);
  });

  // Setup content updates loop
  setInterval(function() {
    for (var f in screen.fields) {
      if (screen.fields.hasOwnProperty(f)) {
        screen.fields[f].getContents();
      }
    }
    screen.checkUpdates();
  }, 60000);
  screen.checkUpdates();
}

// Run
$(onLoad);
<commit_msg>Remove unused params in content randomizer<commit_after>/** global: updateScreenUrl */

/**
 * Screen class constructor
 * @param {string} updateScreenUrl global screen update checks url
 */
function Screen(updateScreenUrl) {
  this.fields = [];
  this.url = updateScreenUrl;
  this.lastChanges = null;
  this.endAt = null;
  this.nextUrl = null;
  this.stopping = false;
  this.cache = {};
}

/**
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
 */
Screen.prototype.checkUpdates = function() {
  var s = this;
  $.get(this.url, function(j) {
    if (j.success) {
      if (s.lastChanges == null) {
        s.lastChanges = j.data.lastChanges;
      } else if (s.lastChanges != j.data.lastChanges) {
        s.reload();
        s.nextUrl = null;
        return;
      }

      if (j.data.duration > 0) {
        // Setup next screen
        s.reload(j.data.duration * 1000);
        s.nextUrl = j.data.nextScreenUrl;
      }
    }
  });
}

/**
 * Start Screen reload procedure, checking for every field timeout
 */
Screen.prototype.reload = function(minDuration) {
  var endAt = Date.now() + (minDuration ? minDuration : 0);
  if (this.stopping && this.endAt < endAt) {
    return;
  }

  this.endAt = minDuration ? Date.now() + minDuration : 0;
  this.stopping = true;
  for (var i in this.fields) {
    if (!this.fields.hasOwnProperty(i)) {
      continue;
    }
    var f = this.fields[i];
    if (f.timeout && f.endAt > this.endAt) {
      this.endAt = f.endAt;
    }
  }

  if (this.endAt === 0) {
    this.doReload();
  }
}

/**
 * Actual Screen reload action
 */
Screen.prototype.doReload = function() {
  if (this.nextUrl) {
    window.location = this.nextUrl;
  } else {
    window.location.reload();
  }
}

/**
 * Check every field for content
 * @param  {Content} data
 * @return {boolean} content is displayed
 */
Screen.prototype.displaysData = function(data) {
  return this.fields.filter(function(field) {
    return field.current && field.current.data == data;
  }).length > 0;
}

/**
 * Content class constructor
 * @param {array} c content attributes
 */
function Content(c) {
  this.id = c.id;
  this.data = c.data;
  this.duration = c.duration * 1000;
  this.type = c.type;
  this.src = null;

  if (this.shouldPreload()) {
    this.preload();
  }
}

/**
 * Check if content should be ajax preloaded
 * @return {boolean}
 */
Content.prototype.shouldPreload = function() {
  return this.canPreload() && !this.isPreloading() && !this.isPreloaded();
}

/**
 * Check if content has pre-loadable material
 * @return {boolean}
 */
Content.prototype.canPreload = function() {
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
}

/**
 * Extract url from contant data
 * @return {string} resource url
 */
Content.prototype.getResource = function() {
  if (this.src) {
    return this.src;
  }
  var srcMatch = this.data.match(/src="([^"]+)"/);
  if (!srcMatch) {
    return false;
  }
  var src = srcMatch[1];
  if (src.indexOf('/') === 0) {
    src = window.location.origin + src;
  }
  if (src.indexOf('http') !== 0) {
    return false;
  }

  this.src = src;
  return src;
}

/**
 * Check cache for preload status of content
 * @return {Boolean}
 */
Content.prototype.isPreloaded = function() {
  if (!this.canPreload()) {
    return true;
  }

  var cache = screen.cache[this.getResource()]
  switch (cache) {
    case undefined: // unset
    case false: // preloading
      return false;
    case true: // preloaded without expire
      return true;
    default: // check expire
      return (new Date()).valueOf() < cache;
  }
}

/**
 * Set content cache status
 * @param {string} expires header
 */
Content.prototype.setPreloaded = function(expires) {
  switch (expires) {
    case null:
    case undefined:
      // No Expires header, don't bother with caching
    case true:
      // Force content cache
      screen.cache[this.getResource()] = true;
      break;
    case false:
      // Discard content cache and disallow display
      delete screen.cache[this.getResource()];
      break;
    default:
      var exp = new Date(expires).valueOf();
      var diff = exp - (new Date()).valueOf();
      // Don't check short Expires and add 5 sec to valid Expires
      screen.cache[this.getResource()] = diff < 10000 ? true : exp + 5000;
  }
}

/**
 * Check cache for in progress preloading
 * @return {Boolean}
 */
Content.prototype.isPreloading = function() {
  return screen.cache[this.getResource()] === false;
}

/**
 * Set content preloading status
 * @param {boolean} state is preloading
 */
Content.prototype.setPreloading = function(state) {
  if (state) {
    screen.cache[this.getResource()] = false;
  } else if (this.isPreloading()) {
    delete screen.cache[this.getResource()];
  }
}

/**
 * Ajax call to preload content
 * @return {[type]} [description]
 */
Content.prototype.preload = function() {
  var src = this.getResource();
  if (!src) {
    this.setPreloaded(true);
    return;
  }
  this.setPreloading(true);

  var c = this;
  $.ajax(src).done(function(data, textStatus, jqXHR) {
    c.setPreloaded(jqXHR.getResponseHeader('Expires'));
  }).fail(function() {
    c.setPreloaded(false); // Discard until next Content init
  });
}

/**
 * Field class constructor
 * @param {jQuery.Object} $f field object
 */
function Field($f) {
  this.$field = $f;
  this.id = $f.attr('data-id');
  this.url = $f.attr('data-url');
  this.types = $f.attr('data-types').split(' ');
  this.canUpdate = this.url != null;
  this.contents = [];
  this.previous = null;
  this.current = null;
  this.next = null;
  this.timeout = null;
  this.endAt = null;
}

/**
 * Retrieves contents from backend for this field
 */
Field.prototype.getContents = function() {
  if (!this.canUpdate) {
    return;
  }

  var f = this;
  $.get(this.url, function(j) {
    if (j.success) {
      f.contents = j.next.map(function(c) {
        return new Content(c);
      });
      if (!f.timeout && f.contents.length) {
        f.pickNext();
      }
    } else {
      f.setError(j.message || 'Error');
    }
  });
}

/**
 * Display error in field text
 */
Field.prototype.setError = function(err) {
  this.$field.text(err);
}

/**
 * Randomize order
 */
Field.prototype.randomizeSortContents = function() {
  this.contents = this.contents.sort(function() {
    return Math.random() - 0.5;
  });
}

/**
 * Loop through field contents to pick next displayable content
 */
Field.prototype.pickNext = function() {
  if (screen.stopping && screen.endAt < Date.now()) { // Stoping screen
    screen.doReload();
    return;
  }

  this.previous = this.current;
  this.current = null;
  var pData = this.previous && this.previous.data;
  // Avoid repeat & other field same content
  this.randomizeSortContents();
  for (var i = 0; i < this.contents.length; i++) {
    var c = this.contents[i];
    // Skip too long or not preloaded content
    if ((screen.endAt != null && c.duration + Date.now() > screen.endAt) || !c.isPreloaded()) {
      continue;
    }

    if (c.data == pData) {
      // Will repeat, avoid if enough content
      if (this.contents.length < 2) {
        this.next = c;
        break;
      }
      continue;
    }

    if (screen.displaysData(c.data)) {
      // Same content already displayed on other field, avoid if enough content
      if (this.contents.length < 3) {
        this.next = c;
        break;
      }
      continue;
    }

    this.next = c;
  }

  this.display();
}

/**
 * Display next content in field html
 */
Field.prototype.display = function() {
  var f = this;
  if (this.next && this.next.duration > 0) {
    this.current = this.next
    this.next = null;
    this.$field.html(this.current.data);
    this.$field.show();
    if (this.$field.text() != '') {
      this.$field.textfill({
        maxFontPixels: 0,
      });
    }
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, this.current.duration);
    this.endAt = this.current.duration + Date.now()
  } else {
    this.timeout = setTimeout(function() {
      f.pickNext();
    }, 2000);
  }
}

/**
 * jQuery.load event
 * Initialize Screen and Fields
 * Setup updates interval timeouts
 */
var screen = null;

function onLoad() {
  screen = new Screen(updateScreenUrl);
  // Init
  $('.field').each(function() {
    var f = new Field($(this));
    f.getContents();
    screen.fields.push(f);
  });

  // Setup content updates loop
  setInterval(function() {
    for (var f in screen.fields) {
      if (screen.fields.hasOwnProperty(f)) {
        screen.fields[f].getContents();
      }
    }
    screen.checkUpdates();
  }, 60000);
  screen.checkUpdates();
}

// Run
$(onLoad);
<|endoftext|>


<commit_before>(function(a,b,c,d,e,f){function k(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=j&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=j&f+1],c=c*d+h[j&(h[f]=h[g=j&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function l(a,b){var e,c=[],d=(typeof a)[0];if(b&&"o"==d)for(e in a)try{c.push(l(a[e],b-1))}catch(f){}return c.length?c:"s"==d?a:a+"\0"}function m(a,b){for(var d,c=a+"",e=0;c.length>e;)b[j&e]=j&(d^=19*b[j&e])+c.charCodeAt(e++);return o(b)}function n(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),o(c)}catch(e){return[+new Date,a,a.navigator.plugins,a.screen,o(b)]}}function o(a){return String.fromCharCode.apply(0,a)}var g=c.pow(d,e),h=c.pow(2,f),i=2*h,j=d-1;c.seedrandom=function(a,f){var j=[],p=m(l(f?[a,o(b)]:0 in arguments?a:n(),3),j),q=new k(j);return m(o(q.S),b),c.random=function(){for(var a=q.g(e),b=g,c=0;h>a;)a=(a+c)*d,b*=d,c=q.g(1);for(;a>=i;)a/=2,b/=2,c>>>=1;return(a+c)/b},p},m(c.random(),b)})(this,[],Math,256,6,52);
<commit_msg>Remove the old seedrandom function<commit_after><|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const http = require('http');
const inquirer = require('inquirer');
const urlParser = require('url');
const querystring = require('querystring');
const request = require('request');

const OAUTH_PORT = 3333;
const DEFAULT_REDIRECT_URI = 'http://localhost:' + OAUTH_PORT;
const CALLBACK_HTML_FILE = path.join(__dirname, '..', 'www', 'oauth_callback.html');

const datafire = require('../index');
const logger = require('../lib/logger');
const locations = require('../lib/locations');

const QUESTION_SETS = {
  alias: [
    {name: 'alias', message: "Choose an alias for this account:"}
  ],
  basic: [
    {name: 'username', message: "username:"},
    {name: 'password', message: "password:", type: 'password'},
  ],
  apiKey: [
    {name: 'api_key', message: "api_key:"},
  ],
  oauth_client: [
    {name: 'client_id', message: "client_id:"},
    {name: 'client_secret', message: "client_secret:"},
    {name: 'redirect_uri', message: "redirect_uri:", default: DEFAULT_REDIRECT_URI},
  ],
  oauth_tokens: [
    {name: 'access_token', message: "access_token:"},
    {name: 'refresh_token', message: "refresh_token (optional):"},
  ],
  scopes: [
    {name: 'scopes', type: 'checkbox', message: 'Choose at least one scope to authorize'},
  ],
  choose_definition: [
    {name: 'definition', message: "This API has multiple authentication flows. Which do you want to use?", type: 'list'}
  ]
}

QUESTION_SETS.oauth2 = QUESTION_SETS.oauth_tokens.concat(QUESTION_SETS.oauth_client);

let getQuestions = (secDef, allDefs) => {
  let qs = JSON.parse(JSON.stringify(QUESTION_SETS[secDef.type]));
  if (secDef.type === 'apiKey') {
    let allApiKeys = Object.keys(allDefs).map(k => allDefs[k]).filter(d => d.type === 'apiKey');
    qs = allApiKeys.map(def => {
      return {
        name: def.name,
        message: def.name + ':',
      }
    })
  }
  return qs;
}

let getChooseDefQuestion = (secOptions) => {
  let qs = JSON.parse(JSON.stringify(QUESTION_SETS.choose_definition));
  qs[0].choices = secOptions.map(o => {
    let description = '(' + o.name;
    if (o.def.description) description += ' - ' + o.def.description;
    description += ')';
    return {
      name: o.def.type + ' ' + description,
      value: o,
    }
  });
  return qs;
}

let setDefaults = (questions, defaults) => {
  return questions.map(q => {
    return {
      name: q.name,
      message: q.message,
      type: q.type,
      default: defaults[q.name],
    }
  });
}

let getAccounts = (integration) => {
  let credFile = path.join(locations.credentials[0], integration + '.json');
  return fs.existsSync(credFile) ? require(credFile) : {};
}

module.exports = (args) => {
  try {
    fs.mkdirSync(locations.credentials[0]);
  } catch (e) {}

  let integration = datafire.Integration.new(args.integration);
  integration.initialize(err => {
    if (err) throw err;
    let secDefs = integration.spec.securityDefinitions;
    if (!secDefs || !Object.keys(secDefs).length) {
      logger.logError("No security definitions found for " + args.integration);
      return;
    }
    let secOptions = Object.keys(secDefs).map(name => {
      return {
        name: name,
        def: secDefs[name],
      }
    });
    let accounts = getAccounts(integration.name);
    let accountToEdit = null;
    let secOption = null;
    if (args.as) {
      accountToEdit = accounts[args.as];
      if (!accountToEdit) throw new Error("Account " + args.as + " not found");
      secOption = secOptions.filter(o => o.name === accountToEdit.securityDefinition)[0];
      if (!secOption) throw new Error("Security definition " + accountToEdit.securityDefinition + " not found");
    } else if (secOptions.length === 1) {
      secOption = secOptions[0];
    }
    let questions = secOption ? [] : getChooseDefQuestion(secOptions);
    inquirer.prompt(questions).then(answers => {
      if (answers.definition) secOption = answers.definition;
      if (args.set_default) {
        accounts.default = args.set_default;
        saveAccounts(integration, accounts);
      } else if (args.generate_token) {
        let clientAccount = accountToEdit;
        if (args.client) {
          clientAccount = accounts[args.client];
        }
        generateToken(integration, secOption, accounts, accountToEdit, clientAccount);
      } else {
        authenticate(integration, secOption, accounts, accountToEdit);
      }
    })
  })
}

let authenticate = (integration, secOption, accounts, accountToEdit) => {
  let questions = getQuestions(secOption.def, integration.spec.securityDefinitions);
  if (accountToEdit) questions = setDefaults(questions, accountToEdit);
  inquirer.prompt(questions).then(answers => {
    for (let k in answers) {
      if (!answers[k]) delete answers[k];
    }
    answers.securityDefinition = secOption.name;
    if (accountToEdit) {
      for (let k in answers) accountToEdit[k] = answers[k];
      saveAccounts(integration, accounts);
      return
    } else {
      inquirer.prompt(QUESTION_SETS.alias).then(aliasAnswer => {
        accounts[aliasAnswer.alias] = answers;
        saveAccounts(integration, accounts);
      })
    }
  })
}

let generateToken = (integration, secOption, accounts, accountToEdit, clientAccount) => {
  let questions = [];
  if (!accountToEdit) questions = questions.concat(QUESTION_SETS.alias);
  if (!clientAccount) questions = questions.concat(QUESTION_SETS.oauth_client);
  inquirer.prompt(questions).then(answers => {
    if (answers.alias) accountToEdit = accounts[answers.alias] = {};
    if (answers.client_id) accountToEdit.client_id = answers.client_id;
    if (answers.client_secret) accountToEdit.client_secret = answers.client_secret;
    if (answers.redirect_uri) accountToEdit.redirect_uri = answers.redirect_uri;
    if (!clientAccount) clientAccount = accountToEdit;
    accountToEdit.securityDefinition = secOption.name;
    startOAuthServer(integration, secOption.def, accounts, accountToEdit, clientAccount)
  })
}
let saveAccounts = (integration, accounts) => {
  let oldCreds = getAccounts(integration.name);
  let credFile = path.join(locations.credentials[0], integration.name + '.json');
  logger.log('Saving credentials to ' + credFile.replace(process.cwd(), '.'));
  fs.writeFileSync(credFile, JSON.stringify(accounts, null, 2));
}

let getOAuthURL = (integration, secDef, clientAccount, scopes) => {
  var flow = secDef.flow;
  var url = secDef.authorizationUrl;
  var state = Math.random();
  url += '?response_type=' + (flow === 'implicit' ? 'token' : 'code');
  url += '&redirect_uri=' + clientAccount.redirect_uri || DEFAULT_REDIRECT_URI;
  url += '&client_id=' + encodeURIComponent(clientAccount.client_id);
  if (flow === 'accessCode') url += '&access_type=offline';
  if (scopes.length > 0) {
    url += '&scope=' + encodeURIComponent(scopes.join(' '));
  }
  url += '&state=' + encodeURIComponent(state);
  return url;
}

let startOAuthServer = (integration, secDef, accounts, accountToEdit, clientAccount) => {
  let server = http.createServer((req, res) => {
    let urlObj = urlParser.parse(req.url);
    if (urlObj.pathname !== '/') {
      res.writeHead(404);
      res.end();
      return;
    }
    let search = urlParser.parse(req.url).search || '?';
    search = search.substring(1);
    search = querystring.parse(search);
    if (search.code) {
      request.post({
        url: secDef.tokenUrl,
        form: {
          code: search.code,
          client_id: clientAccount.client_id,
          client_secret: clientAccount.client_secret,
          redirect_uri: clientAccount.redirect_uri || DEFAULT_REDIRECT_URI,
          grant_type: 'authorization_code',
        },
        json: true,
      }, (err, resp, body) => {
        if (err) throw err;
        let newURL = '/?saved=true#access_token=' + encodeURIComponent(body.access_token);
        newURL += '&refresh_token=' + encodeURIComponent(body.refresh_token);
        newURL += '&saved=true';
        res.writeHead(302, {
          'Location': newURL,
        });
        res.end();
        accountToEdit.access_token = body.access_token;
        accountToEdit.refresh_token = body.refresh_token;
        accountToEdit.client_id = clientAccount.client_id;
        accountToEdit.client_secret = clientAccount.client_secret;
        saveAccounts(integration, accounts);
      })
    } else {
      fs.readFile(CALLBACK_HTML_FILE, 'utf8', (err, data) => {
        if (err) throw err;
        res.end(data);
        if (!search.saved) {
          inquirer.prompt(QUESTION_SETS.oauth_tokens).then(answers => {
            if (answers.access_token) accountToEdit.access_token = answers.access_token;
            if (answers.refresh_token) accountToEdit.refresh_token = answers.refresh_token;
            accountToEdit.client_id = clientAccount.client_id;
            accountToEdit.client_secret = clientAccount.client_secret;
            saveAccounts(integration, accounts);
            server.close();
            process.exit(0);
          })
        } else {
          server.close();
          process.exit(0);
        }
      })
    }
  }).listen(OAUTH_PORT, (err) => {
    if (err) throw err;
    QUESTION_SETS.scopes[0].choices = Object.keys(secDef.scopes).map(s => {
      return {value: s, name: s + ' (' + secDef.scopes[s] + ')'}
    })
    inquirer.prompt(QUESTION_SETS.scopes).then(answers => {
      let url = getOAuthURL(integration, secDef, clientAccount, answers.scopes);
      logger.log("Visit this url to retrieve your access and refresh tokens:")
      logger.logURL(url);
    })
  });
}
<commit_msg>when only apiKey sec defs are available, allow filling all of them<commit_after>const fs = require('fs');
const path = require('path');
const http = require('http');
const inquirer = require('inquirer');
const urlParser = require('url');
const querystring = require('querystring');
const request = require('request');

const OAUTH_PORT = 3333;
const DEFAULT_REDIRECT_URI = 'http://localhost:' + OAUTH_PORT;
const CALLBACK_HTML_FILE = path.join(__dirname, '..', 'www', 'oauth_callback.html');

const datafire = require('../index');
const logger = require('../lib/logger');
const locations = require('../lib/locations');

const QUESTION_SETS = {
  alias: [
    {name: 'alias', message: "Choose an alias for this account:"}
  ],
  basic: [
    {name: 'username', message: "username:"},
    {name: 'password', message: "password:", type: 'password'},
  ],
  apiKey: [
    {name: 'api_key', message: "api_key:"},
  ],
  oauth_client: [
    {name: 'client_id', message: "client_id:"},
    {name: 'client_secret', message: "client_secret:"},
    {name: 'redirect_uri', message: "redirect_uri:", default: DEFAULT_REDIRECT_URI},
  ],
  oauth_tokens: [
    {name: 'access_token', message: "access_token:"},
    {name: 'refresh_token', message: "refresh_token (optional):"},
  ],
  scopes: [
    {name: 'scopes', type: 'checkbox', message: 'Choose at least one scope to authorize'},
  ],
  choose_definition: [
    {name: 'definition', message: "This API has multiple authentication flows. Which do you want to use?", type: 'list'}
  ]
}

QUESTION_SETS.oauth2 = QUESTION_SETS.oauth_tokens.concat(QUESTION_SETS.oauth_client);

let getQuestions = (secDef, allDefs) => {
  let qs = JSON.parse(JSON.stringify(QUESTION_SETS[secDef.type]));
  if (secDef.type === 'apiKey') {
    let allApiKeys = Object.keys(allDefs)
          .map(k => ({name: k, def: allDefs[k]}))
          .filter(d => d.def.type === 'apiKey');
    qs = allApiKeys.map(def => {
      return {
        name: def.name,
        message: def.name + ':',
      }
    })
  }
  return qs;
}

let getChooseDefQuestion = (secOptions) => {
  let qs = JSON.parse(JSON.stringify(QUESTION_SETS.choose_definition));
  if (secOptions.filter(o => o.def.type === 'apiKey').length === secOptions.length) {
    return [{
      name: qs[0].name,
      type: 'list',
      choices: [{name: '(press enter to continue)', value: secOptions[0]}],
      message: "You can specify one or more apiKeys for this API"
    }]
  }
  qs[0].choices = secOptions.map(o => {
    let description = '(' + o.name;
    if (o.def.description) description += ' - ' + o.def.description;
    description += ')';
    return {
      name: o.def.type + ' ' + description,
      value: o,
    }
  });
  return qs;
}

let setDefaults = (questions, defaults) => {
  return questions.map(q => {
    return {
      name: q.name,
      message: q.message,
      type: q.type,
      default: defaults[q.name],
    }
  });
}

let getAccounts = (integration) => {
  let credFile = path.join(locations.credentials[0], integration + '.json');
  return fs.existsSync(credFile) ? require(credFile) : {};
}

module.exports = (args) => {
  try {
    fs.mkdirSync(locations.credentials[0]);
  } catch (e) {}

  let integration = datafire.Integration.new(args.integration);
  integration.initialize(err => {
    if (err) throw err;
    let secDefs = integration.spec.securityDefinitions;
    if (!secDefs || !Object.keys(secDefs).length) {
      logger.logError("No security definitions found for " + args.integration);
      return;
    }
    let secOptions = Object.keys(secDefs).map(name => {
      return {
        name: name,
        def: secDefs[name],
      }
    });
    let accounts = getAccounts(integration.name);
    let accountToEdit = null;
    let secOption = null;
    if (args.as) {
      accountToEdit = accounts[args.as];
      if (!accountToEdit) throw new Error("Account " + args.as + " not found");
      secOption = secOptions.filter(o => o.name === accountToEdit.securityDefinition)[0];
      if (!secOption) throw new Error("Security definition " + accountToEdit.securityDefinition + " not found");
    } else if (secOptions.length === 1) {
      secOption = secOptions[0];
    }
    let questions = secOption ? [] : getChooseDefQuestion(secOptions);
    inquirer.prompt(questions).then(answers => {
      if (answers.definition) secOption = answers.definition;
      if (args.set_default) {
        accounts.default = args.set_default;
        saveAccounts(integration, accounts);
      } else if (args.generate_token) {
        let clientAccount = accountToEdit;
        if (args.client) {
          clientAccount = accounts[args.client];
        }
        generateToken(integration, secOption, accounts, accountToEdit, clientAccount);
      } else {
        authenticate(integration, secOption, accounts, accountToEdit);
      }
    })
  })
}

let authenticate = (integration, secOption, accounts, accountToEdit) => {
  let questions = getQuestions(secOption.def, integration.spec.securityDefinitions);
  if (accountToEdit) questions = setDefaults(questions, accountToEdit);
  inquirer.prompt(questions).then(answers => {
    for (let k in answers) {
      if (!answers[k]) delete answers[k];
    }
    answers.securityDefinition = secOption.name;
    if (accountToEdit) {
      for (let k in answers) accountToEdit[k] = answers[k];
      saveAccounts(integration, accounts);
      return
    } else {
      inquirer.prompt(QUESTION_SETS.alias).then(aliasAnswer => {
        accounts[aliasAnswer.alias] = answers;
        saveAccounts(integration, accounts);
      })
    }
  })
}

let generateToken = (integration, secOption, accounts, accountToEdit, clientAccount) => {
  let questions = [];
  if (!accountToEdit) questions = questions.concat(QUESTION_SETS.alias);
  if (!clientAccount) questions = questions.concat(QUESTION_SETS.oauth_client);
  inquirer.prompt(questions).then(answers => {
    if (answers.alias) accountToEdit = accounts[answers.alias] = {};
    if (answers.client_id) accountToEdit.client_id = answers.client_id;
    if (answers.client_secret) accountToEdit.client_secret = answers.client_secret;
    if (answers.redirect_uri) accountToEdit.redirect_uri = answers.redirect_uri;
    if (!clientAccount) clientAccount = accountToEdit;
    accountToEdit.securityDefinition = secOption.name;
    startOAuthServer(integration, secOption.def, accounts, accountToEdit, clientAccount)
  })
}
let saveAccounts = (integration, accounts) => {
  let oldCreds = getAccounts(integration.name);
  let credFile = path.join(locations.credentials[0], integration.name + '.json');
  logger.log('Saving credentials to ' + credFile.replace(process.cwd(), '.'));
  fs.writeFileSync(credFile, JSON.stringify(accounts, null, 2));
}

let getOAuthURL = (integration, secDef, clientAccount, scopes) => {
  var flow = secDef.flow;
  var url = secDef.authorizationUrl;
  var state = Math.random();
  url += '?response_type=' + (flow === 'implicit' ? 'token' : 'code');
  url += '&redirect_uri=' + clientAccount.redirect_uri || DEFAULT_REDIRECT_URI;
  url += '&client_id=' + encodeURIComponent(clientAccount.client_id);
  if (flow === 'accessCode') url += '&access_type=offline';
  if (scopes.length > 0) {
    url += '&scope=' + encodeURIComponent(scopes.join(' '));
  }
  url += '&state=' + encodeURIComponent(state);
  return url;
}

let startOAuthServer = (integration, secDef, accounts, accountToEdit, clientAccount) => {
  let server = http.createServer((req, res) => {
    let urlObj = urlParser.parse(req.url);
    if (urlObj.pathname !== '/') {
      res.writeHead(404);
      res.end();
      return;
    }
    let search = urlParser.parse(req.url).search || '?';
    search = search.substring(1);
    search = querystring.parse(search);
    if (search.code) {
      request.post({
        url: secDef.tokenUrl,
        form: {
          code: search.code,
          client_id: clientAccount.client_id,
          client_secret: clientAccount.client_secret,
          redirect_uri: clientAccount.redirect_uri || DEFAULT_REDIRECT_URI,
          grant_type: 'authorization_code',
        },
        json: true,
      }, (err, resp, body) => {
        if (err) throw err;
        let newURL = '/?saved=true#access_token=' + encodeURIComponent(body.access_token);
        newURL += '&refresh_token=' + encodeURIComponent(body.refresh_token);
        newURL += '&saved=true';
        res.writeHead(302, {
          'Location': newURL,
        });
        res.end();
        accountToEdit.access_token = body.access_token;
        accountToEdit.refresh_token = body.refresh_token;
        accountToEdit.client_id = clientAccount.client_id;
        accountToEdit.client_secret = clientAccount.client_secret;
        saveAccounts(integration, accounts);
      })
    } else {
      fs.readFile(CALLBACK_HTML_FILE, 'utf8', (err, data) => {
        if (err) throw err;
        res.end(data);
        if (!search.saved) {
          inquirer.prompt(QUESTION_SETS.oauth_tokens).then(answers => {
            if (answers.access_token) accountToEdit.access_token = answers.access_token;
            if (answers.refresh_token) accountToEdit.refresh_token = answers.refresh_token;
            accountToEdit.client_id = clientAccount.client_id;
            accountToEdit.client_secret = clientAccount.client_secret;
            saveAccounts(integration, accounts);
            server.close();
            process.exit(0);
          })
        } else {
          server.close();
          process.exit(0);
        }
      })
    }
  }).listen(OAUTH_PORT, (err) => {
    if (err) throw err;
    QUESTION_SETS.scopes[0].choices = Object.keys(secDef.scopes).map(s => {
      return {value: s, name: s + ' (' + secDef.scopes[s] + ')'}
    })
    inquirer.prompt(QUESTION_SETS.scopes).then(answers => {
      let url = getOAuthURL(integration, secDef, clientAccount, answers.scopes);
      logger.log("Visit this url to retrieve your access and refresh tokens:")
      logger.logURL(url);
    })
  });
}
<|endoftext|>


<commit_before>"use strict";

const net = require("net");
const os = require("os");
const execa = require("execa");

const args = {
  v4: ["-4", "r"],
  v6: ["-6", "r"],
};

const parse = (stdout, family) => {
  let result;

  (stdout || "").trim().split("\n").some(line => {
    const results = /default( via .+?)?( dev .+?)( |$)/.exec(line) || [];
    const gateway = (results[1] || "").substring(5);
    const iface = (results[2] || "").substring(5);
    if (gateway && net.isIP(gateway)) { // default via 1.2.3.4 dev en0
      result = {gateway, interface: (iface ? iface : null)};
      return true;
    } else if (iface && !gateway) { // default via dev en0
      const interfaces = os.networkInterfaces();
      const addresses = interfaces[iface];
      if (!addresses || !addresses.length) return;

      addresses.some(addr => {
        if (addr.family.substring(2) === family && net.isIP(addr.address)) {
          result = {gateway: addr.address, interface: (iface ? iface : null)};
          return true;
        }
      });
    }
  });

  if (!result) {
    throw new Error("Unable to determine default gateway");
  }

  return result;
};

const promise = async family => {
  const {stdout} = await execa("netstat", args[family]);
  return parse(stdout, family);
};

const sync = family => {
  const {stdout} = execa.sync("ip", args[family]);
  return parse(stdout, family);
};

module.exports.v4 = () => promise("v4");
module.exports.v6 = () => promise("v6");

module.exports.v4.sync = () => sync("v4");
module.exports.v6.sync = () => sync("v6");
<commit_msg>fix linux typo<commit_after>"use strict";

const net = require("net");
const os = require("os");
const execa = require("execa");

const args = {
  v4: ["-4", "r"],
  v6: ["-6", "r"],
};

const parse = (stdout, family) => {
  let result;

  (stdout || "").trim().split("\n").some(line => {
    const results = /default( via .+?)?( dev .+?)( |$)/.exec(line) || [];
    const gateway = (results[1] || "").substring(5);
    const iface = (results[2] || "").substring(5);
    if (gateway && net.isIP(gateway)) { // default via 1.2.3.4 dev en0
      result = {gateway, interface: (iface ? iface : null)};
      return true;
    } else if (iface && !gateway) { // default via dev en0
      const interfaces = os.networkInterfaces();
      const addresses = interfaces[iface];
      if (!addresses || !addresses.length) return;

      addresses.some(addr => {
        if (addr.family.substring(2) === family && net.isIP(addr.address)) {
          result = {gateway: addr.address, interface: (iface ? iface : null)};
          return true;
        }
      });
    }
  });

  if (!result) {
    throw new Error("Unable to determine default gateway");
  }

  return result;
};

const promise = async family => {
  const {stdout} = await execa("ip", args[family]);
  return parse(stdout, family);
};

const sync = family => {
  const {stdout} = execa.sync("ip", args[family]);
  return parse(stdout, family);
};

module.exports.v4 = () => promise("v4");
module.exports.v6 = () => promise("v6");

module.exports.v4.sync = () => sync("v4");
module.exports.v6.sync = () => sync("v6");
<|endoftext|>