<commit_before>var async = require('async')
    , assert = require('assert')
    , host = process.env.COREOS_HOST || '127.0.0.1'
    , etcd = new (require('node-etcd'))(host)
    , winston = require('winston')
    , bunyan = require('bunyan')
    , Stream = require('stream').Stream
    , util = require('util')
    , kafka = require('kafka-node');

var logger = exports.create_logger('auth0-coreos-tools', ['system']);

// Accepts an array of strings as configuration keys.
// Each {key1} corresponds to /config/{key1} entry in etcd.
// Returns an object with properties corresponding to input keys.
// Each property is an EventEmitter. It emits the `modified` event
// when the value of the configuration key. In addition, each EventEmitter
// has a `current` property which returns the current string value stored in etcd.
exports.etcd_config = function (keys, path, callback) {
    if (typeof path === 'function') {
        callback = path;
        path = '/config/';
    }
    var config = {};
    async.each(
        keys,
        function (key, callback) {
            etcd.get(path + key, function (error, result) {
                if (error) {
                    error.key = path + key;
                    logger.error(error, 'unable to read etcd key');
                    return callback(error);
                }

                var watch = etcd.watcher(path + key, result.node.modifiedIndex + 1);
                watch.current = result.node.value;
                watch.on('change', function (result) {
                    if (watch.current !== result.node.value)
                        this.emit('modified', result.node.value);
                    watch.current = result.node.value;
                });
                watch.on('error', function (error) {
                    error.key = path + key;
                    logger.error(error, 'error watching etcd key');
                    throw error; // TODO (tjanczuk): consider surfacing etcd errors differently?
                });

                config[key] = watch;
                callback();
            });
        },
        function (error) {
            callback && (error ? callback(error) : callback(null, config));
        }
    );

    return config; // for convenience of REPL
};

// Converts response from etcd into a JS object representing only
// the data and its hierarchy. Etcd directories are represented as JS objects.

function etcd2js(node) {
    if (node.value !== undefined)
        return node.value;
    var result = {};
    if (Array.isArray(node.nodes)) {
        node.nodes.forEach(function (child) {
            result[child.key.substring(node.key.length === 1 ? 1 : node.key.length + 1)]
                = etcd2js(child);
        });
    }
    return result;
}

exports.etcd2js = etcd2js;

// Gets the key from etcd (recursively) and returns a JS representation of
// the hierarchy.
exports.etcd_get = function (key, callback) {
    etcd.get(key, { recursive: true }, function (error, result) {
        error ? callback(error): callback(null, etcd2js(result.node));
    });
};

// Convert object property names to uppercase
exports.upper = function (o) {
    var r = {};
    for (var k in o)
        r[k.toUpperCase()] = typeof o[k] === 'object' ? upper(o[k]) : o[k];
    return r;
};

// The .lowr async function completes in either of two situations:
//
// 1. If the sync_root is not locked, it obtains an exclusive, distributed lock of the sync_root
// via etcd and returns a function that can be used to release the lock as a callback parameter.
//
// 2. If the sync_root is locked, it waits for that lock to be released, and returns without any results.
//
// This logic allows several homogenic, distributed processes to ensure that a certain workload is
// completed by only one of them.
//
// The lock TTL in etcd is configured with the `/config/lock.ttl` confguration entry.
// While the lock is held, it is reset to that TTL every `/config/lock.renew` period.
var lock_id = process.pid + '.' + Date.now();
exports.lowr = function (sync_root, etcdconfig, callback) {
    // Attempt to take the lock
    etcd.set(
        '/lock/' + sync_root,
        lock_id,
        { prevExist: false, ttl: +etcdconfig.lock_ttl.current },
        function (error, result) {
            if (error) {
                if (error.errorCode === 105) {
                    // Lock is currently held, wait for its release
                    var watch = etcd.watcher('/lock/' + sync_root, error.error.index + 1);
                    watch.on('change', function (result) {
                        if (result.node.value === undefined)
                            done();
                    });
                    watch.on('error', done);

                    function done(error, result) {
                        watch.stop();
                        callback(error, result);
                    }

                    return;
                }

                return callback(error);
            }

            // Lock was taken, renew it periodically until released

            var renew = setInterval(function () {
                etcd.set('/lock/' + sync_root, lock_id, { ttl: +etcdconfig.lock_ttl.current });
            }, +etcdconfig.lock_renew.current * 1000);

            // Return a function that must be used to release the lock

            callback(null, function (callback) {
                clearInterval(renew);
                etcd.compareAndDelete('/lock/' + sync_root, lock_id, function (error) {
                    return callback && callback(error);
                });
            });
        });
};

var KafkaTransport = exports.KafkaTransport = function (options) {
    assert.ok(options, 'options must be specified');
    assert.ok(typeof options === 'object', 'options must be an object');
    assert.ok(typeof options.zookeeper === 'string', 'options.zookeeper must be specfied');
    assert.ok(Array.isArray(options.topics), 'options.topic must be an array of string topics');
    this.name = options.name || 'kafka';
    this.level = options.level || 'info';
    this.options = options;
    this._pending = [];
    this._client = new kafka.Client(this.options.zookeeper, 'kafka-transport');
    this._producer = new kafka.Producer(this._client);
    var self = this;
    this._producer
        .once('ready', function () {
            self._ready = true;
            self._send_pending();
        })
        .on('error', function (error) {
            self._error = error;
            winston.error('winston-kafka error', error);
        });
};

util.inherits(KafkaTransport, winston.Transport);

KafkaTransport.prototype.log = function (level, msg, meta, callback) {
    if (!this._error) {
        this._pending.push({ time: new Date(), level: level, msg: msg, meta: meta, callback: callback });
        this._send_pending();
    }
    else
        callback(this._error);
};

KafkaTransport.prototype._send_pending = function () {
    if (this._ready && this._pending.length > 0) {
        var current = this._pending;
        this._pending = [];
        var messages = [];
        current.forEach(function (entry) {
            var meta;
            if (entry.meta && Object.getOwnPropertyNames(entry.meta).length > 0)
                meta = ' ' + util.inspect(entry.meta);
            messages.push(
                entry.time.toISOString() + ' '
                + entry.level + ': '
                + entry.msg
                + (meta || '')
            );
        });
        var payload = [];
        this.options.topics.forEach(function (topic) {
            payload.push({
                topic: topic,
                messages: messages
            });
        });
        this._producer.send(payload, function (error) {
            current.forEach(function (entry) {
                entry.callback && entry.callback(error);
            });
        });
    }
};

var KafkaStream = exports.KafkaStream = function (options) {
    assert.ok(options, 'options must be specified');
    assert.ok(typeof options === 'object', 'options must be an object');
    assert.ok(typeof options.zookeeper === 'string', 'options.zookeeper must be specfied');
    assert.ok(Array.isArray(options.topics), 'options.topic must be an array of string topics');
    Stream.call(this);
    this.name = options.name || 'kafka';
    this.writable = true;
    this.options = options;
    this._pending = [];
    this._client = new kafka.Client(this.options.zookeeper, 'kafka-transport');
    this._producer = new kafka.Producer(this._client);
    var self = this;
    this._producer
        .once('ready', function () {
            self._ready = true;
            self._send_pending();
        })
        .on('error', function (error) {
            self._error = error;
            self.emit('error', error);
        });
};

util.inherits(KafkaStream, Stream);

KafkaStream.prototype.close = function close() {
    this._client.close();
};

KafkaStream.prototype.destroy = function destroy() {
    this.writable = false;
    this.close();
};

KafkaStream.prototype.end = function end() {
    if (arguments.length > 0)
            this.write.apply(this, Array.prototype.slice.call(arguments));

    this.writable = false;
    this.close();
};

KafkaStream.prototype.write = function write(r) {
    if (!this._error) {
        this._pending.push(r);
        this._send_pending();
    }
    else
        throw this._error;
};

KafkaStream.prototype._send_pending = function () {
    if (this._ready && this._pending.length > 0) {
        var current = this._pending;
        this._pending = [];
        var messages = [];
        current.forEach(function (entry) {
            messages.push(typeof entry === 'string' ? message : JSON.stringify(entry));
        });
        var payload = [];
        this.options.topics.forEach(function (topic) {
            payload.push({
                topic: topic,
                messages: messages
            });
        });
        var self = this;
        this._producer.send(payload, function (error) {
            if (error) self.emit('error', error);
        });
    }
};

exports.create_logger = function (name, topics) {
    var logger_options = {
        name: name,
        streams: [
            {
                level: 'info',
                stream: process.stdout
            }
        ]
    };

    if (process.env.KAFKA_ZOOKEEPER) {
        // Enable Kafka stream
        logger_options.streams.push(new KafkaStream({
            zookeeper: process.env.KAFKA_ZOOKEEPER,
            topics: topics
        }));
    }

    return bunyan.createLogger(logger_options);
};
<commit_msg>conditionally create kafka logger<commit_after>var async = require('async')
    , assert = require('assert')
    , host = process.env.COREOS_HOST || '127.0.0.1'
    , etcd = new (require('node-etcd'))(host)
    , winston = require('winston')
    , bunyan = require('bunyan')
    , Stream = require('stream').Stream
    , util = require('util')
    , kafka = require('kafka-node');

var logger = exports.create_logger('auth0-coreos-tools', ['system']);

// Accepts an array of strings as configuration keys.
// Each {key1} corresponds to /config/{key1} entry in etcd.
// Returns an object with properties corresponding to input keys.
// Each property is an EventEmitter. It emits the `modified` event
// when the value of the configuration key. In addition, each EventEmitter
// has a `current` property which returns the current string value stored in etcd.
exports.etcd_config = function (keys, path, callback) {
    if (typeof path === 'function') {
        callback = path;
        path = '/config/';
    }
    var config = {};
    async.each(
        keys,
        function (key, callback) {
            etcd.get(path + key, function (error, result) {
                if (error) {
                    error.key = path + key;
                    logger.error(error, 'unable to read etcd key');
                    return callback(error);
                }

                var watch = etcd.watcher(path + key, result.node.modifiedIndex + 1);
                watch.current = result.node.value;
                watch.on('change', function (result) {
                    if (watch.current !== result.node.value)
                        this.emit('modified', result.node.value);
                    watch.current = result.node.value;
                });
                watch.on('error', function (error) {
                    error.key = path + key;
                    logger.error(error, 'error watching etcd key');
                    throw error; // TODO (tjanczuk): consider surfacing etcd errors differently?
                });

                config[key] = watch;
                callback();
            });
        },
        function (error) {
            callback && (error ? callback(error) : callback(null, config));
        }
    );

    return config; // for convenience of REPL
};

// Converts response from etcd into a JS object representing only
// the data and its hierarchy. Etcd directories are represented as JS objects.

function etcd2js(node) {
    if (node.value !== undefined)
        return node.value;
    var result = {};
    if (Array.isArray(node.nodes)) {
        node.nodes.forEach(function (child) {
            result[child.key.substring(node.key.length === 1 ? 1 : node.key.length + 1)]
                = etcd2js(child);
        });
    }
    return result;
}

exports.etcd2js = etcd2js;

// Gets the key from etcd (recursively) and returns a JS representation of
// the hierarchy.
exports.etcd_get = function (key, callback) {
    etcd.get(key, { recursive: true }, function (error, result) {
        error ? callback(error): callback(null, etcd2js(result.node));
    });
};

// Convert object property names to uppercase
exports.upper = function (o) {
    var r = {};
    for (var k in o)
        r[k.toUpperCase()] = typeof o[k] === 'object' ? upper(o[k]) : o[k];
    return r;
};

// The .lowr async function completes in either of two situations:
//
// 1. If the sync_root is not locked, it obtains an exclusive, distributed lock of the sync_root
// via etcd and returns a function that can be used to release the lock as a callback parameter.
//
// 2. If the sync_root is locked, it waits for that lock to be released, and returns without any results.
//
// This logic allows several homogenic, distributed processes to ensure that a certain workload is
// completed by only one of them.
//
// The lock TTL in etcd is configured with the `/config/lock.ttl` confguration entry.
// While the lock is held, it is reset to that TTL every `/config/lock.renew` period.
var lock_id = process.pid + '.' + Date.now();
exports.lowr = function (sync_root, etcdconfig, callback) {
    // Attempt to take the lock
    etcd.set(
        '/lock/' + sync_root,
        lock_id,
        { prevExist: false, ttl: +etcdconfig.lock_ttl.current },
        function (error, result) {
            if (error) {
                if (error.errorCode === 105) {
                    // Lock is currently held, wait for its release
                    var watch = etcd.watcher('/lock/' + sync_root, error.error.index + 1);
                    watch.on('change', function (result) {
                        if (result.node.value === undefined)
                            done();
                    });
                    watch.on('error', done);

                    function done(error, result) {
                        watch.stop();
                        callback(error, result);
                    }

                    return;
                }

                return callback(error);
            }

            // Lock was taken, renew it periodically until released

            var renew = setInterval(function () {
                etcd.set('/lock/' + sync_root, lock_id, { ttl: +etcdconfig.lock_ttl.current });
            }, +etcdconfig.lock_renew.current * 1000);

            // Return a function that must be used to release the lock

            callback(null, function (callback) {
                clearInterval(renew);
                etcd.compareAndDelete('/lock/' + sync_root, lock_id, function (error) {
                    return callback && callback(error);
                });
            });
        });
};

var KafkaTransport = exports.KafkaTransport = function (options) {
    assert.ok(options, 'options must be specified');
    assert.ok(typeof options === 'object', 'options must be an object');
    assert.ok(typeof options.zookeeper === 'string', 'options.zookeeper must be specfied');
    assert.ok(Array.isArray(options.topics), 'options.topic must be an array of string topics');
    this.name = options.name || 'kafka';
    this.level = options.level || 'info';
    this.options = options;
    this._pending = [];
    this._client = new kafka.Client(this.options.zookeeper, 'kafka-transport');
    this._producer = new kafka.Producer(this._client);
    var self = this;
    this._producer
        .once('ready', function () {
            self._ready = true;
            self._send_pending();
        })
        .on('error', function (error) {
            self._error = error;
            winston.error('winston-kafka error', error);
        });
};

util.inherits(KafkaTransport, winston.Transport);

KafkaTransport.prototype.log = function (level, msg, meta, callback) {
    if (!this._error) {
        this._pending.push({ time: new Date(), level: level, msg: msg, meta: meta, callback: callback });
        this._send_pending();
    }
    else
        callback(this._error);
};

KafkaTransport.prototype._send_pending = function () {
    if (this._ready && this._pending.length > 0) {
        var current = this._pending;
        this._pending = [];
        var messages = [];
        current.forEach(function (entry) {
            var meta;
            if (entry.meta && Object.getOwnPropertyNames(entry.meta).length > 0)
                meta = ' ' + util.inspect(entry.meta);
            messages.push(
                entry.time.toISOString() + ' '
                + entry.level + ': '
                + entry.msg
                + (meta || '')
            );
        });
        var payload = [];
        this.options.topics.forEach(function (topic) {
            payload.push({
                topic: topic,
                messages: messages
            });
        });
        this._producer.send(payload, function (error) {
            current.forEach(function (entry) {
                entry.callback && entry.callback(error);
            });
        });
    }
};

var KafkaStream = exports.KafkaStream = function (options) {
    assert.ok(options, 'options must be specified');
    assert.ok(typeof options === 'object', 'options must be an object');
    assert.ok(typeof options.zookeeper === 'string', 'options.zookeeper must be specfied');
    assert.ok(Array.isArray(options.topics), 'options.topic must be an array of string topics');
    Stream.call(this);
    this.name = options.name || 'kafka';
    this.writable = true;
    this.options = options;
    this._pending = [];
    this._client = new kafka.Client(this.options.zookeeper, 'kafka-transport');
    this._producer = new kafka.Producer(this._client);
    var self = this;
    this._producer
        .once('ready', function () {
            self._ready = true;
            self._send_pending();
        })
        .on('error', function (error) {
            self._error = error;
            self.emit('error', error);
        });
};

util.inherits(KafkaStream, Stream);

KafkaStream.prototype.close = function close() {
    this._client.close();
};

KafkaStream.prototype.destroy = function destroy() {
    this.writable = false;
    this.close();
};

KafkaStream.prototype.end = function end() {
    if (arguments.length > 0)
            this.write.apply(this, Array.prototype.slice.call(arguments));

    this.writable = false;
    this.close();
};

KafkaStream.prototype.write = function write(r) {
    if (!this._error) {
        this._pending.push(r);
        this._send_pending();
    }
    else
        throw this._error;
};

KafkaStream.prototype._send_pending = function () {
    if (this._ready && this._pending.length > 0) {
        var current = this._pending;
        this._pending = [];
        var messages = [];
        current.forEach(function (entry) {
            messages.push(typeof entry === 'string' ? message : JSON.stringify(entry));
        });
        var payload = [];
        this.options.topics.forEach(function (topic) {
            payload.push({
                topic: topic,
                messages: messages
            });
        });
        var self = this;
        this._producer.send(payload, function (error) {
            if (error) self.emit('error', error);
        });
    }
};

exports.create_logger = function (name, topics) {
    var logger_options = {
        name: name,
        streams: [
            {
                level: 'info',
                stream: process.stdout
            }
        ]
    };

    if (Array.isArray(topics) && process.env.KAFKA_ZOOKEEPER) {
        // Enable Kafka stream
        logger_options.streams.push(new KafkaStream({
            zookeeper: process.env.KAFKA_ZOOKEEPER,
            topics: topics
        }));
    }

    return bunyan.createLogger(logger_options);
};
<|endoftext|>


<commit_before>'use strict';

var Writable = require('stream').Writable;
var bodyParser = require('body-parser');
var compression = require('compression');
var BaseService = require('./service');
var inherits = require('util').inherits;
var BlockController = require('./blocks');
var TxController = require('./transactions');
var AddressController = require('./addresses');
var StatusController = require('./status');
var MessagesController = require('./messages');
var UtilsController = require('./utils');
var CurrencyController = require('./currency');
var RateLimiter = require('./ratelimiter');
var morgan = require('morgan');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var Transaction = bitcore.Transaction;
var EventEmitter = require('events').EventEmitter;

/**
 * A service for Bitcore to enable HTTP routes to query information about the blockchain.
 *
 * @param {Object} options
 * @param {Boolean} options.enableCache - This will enable cache-control headers
 * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses.
 * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses.
 * @param {String} options.routePrefix - The URL route prefix
 */
var InsightAPI = function(options) {
  BaseService.call(this, options);

  // in minutes
  this.currencyRefresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY;

  this.subscriptions = {
    inv: []
  };

  if (!_.isUndefined(options.enableCache)) {
    $.checkArgument(_.isBoolean(options.enableCache));
    this.enableCache = options.enableCache;
  }
  this.cacheShortSeconds = options.cacheShortSeconds;
  this.cacheLongSeconds = options.cacheLongSeconds;

  this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE;
  this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE;

  if (!_.isUndefined(options.routePrefix)) {
    this.routePrefix = options.routePrefix;
  } else {
    this.routePrefix = this.name;
  }

  this.txController = new TxController(this.node);
};

InsightAPI.dependencies = ['bitcoind', 'web'];

inherits(InsightAPI, BaseService);

InsightAPI.prototype.cache = function(maxAge) {
  var self = this;
  return function(req, res, next) {
    if (self.enableCache) {
      res.header('Cache-Control', 'public, max-age=' + maxAge);
    }
    next();
  };
};

InsightAPI.prototype.cacheShort = function() {
  var seconds = this.cacheShortSeconds || 30; // thirty seconds
  return this.cache(seconds);
};

InsightAPI.prototype.cacheLong = function() {
  var seconds = this.cacheLongSeconds || 86400; // one day
  return this.cache(seconds);
};

InsightAPI.prototype.getRoutePrefix = function() {
  return this.routePrefix;
};

InsightAPI.prototype.start = function(callback) {
  this.node.services.bitcoind.on('tx', this.transactionEventHandler.bind(this));
  this.node.services.bitcoind.on('block', this.blockEventHandler.bind(this));
  setImmediate(callback);
};

InsightAPI.prototype.createLogInfoStream = function() {
  var self = this;

  function Log(options) {
    Writable.call(this, options);
  }
  inherits(Log, Writable);

  Log.prototype._write = function (chunk, enc, callback) {
    self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger
    callback();
  };
  var stream = new Log();

  return stream;
};

InsightAPI.prototype.setupRoutes = function(app) {

  //Enable rate limiter
  var limiter = new RateLimiter({node: this.node});
  app.use(limiter.middleware());

  //Setup logging
  var logFormat = ':remote-addr ":method :url" :status :res[content-length] :response-time ":user-agent" ';
  var logStream = this.createLogInfoStream();
  app.use(morgan(logFormat, {stream: logStream}));

  //Enable compression
  app.use(compression());

  //Enable urlencoded data
  app.use(bodyParser.urlencoded({extended: true}));

  //Enable CORS
  app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
  });

  //Block routes
  var blockOptions = {
    node: this.node,
    blockSummaryCacheSize: this.blockSummaryCacheSize,
    blockCacheSize: this.blockCacheSize
  };
  var blocks = new BlockController(blockOptions);
  app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks));

  app.get('/block/:blockHash', this.cacheLong(), blocks.show.bind(blocks));
  app.param('blockHash', blocks.block.bind(blocks));

  app.get('/rawblock/:blockArg', this.cacheLong(), blocks.showRaw.bind(blocks));
  app.param('blockArg', blocks.rawBlock.bind(blocks));

  app.get('/block-index/:height', this.cacheLong(), blocks.blockIndex.bind(blocks));
  app.param('height', blocks.blockIndex.bind(blocks));

  // Transaction routes
  var transactions = new TxController(this.node);
  app.get('/tx/:txid', this.cacheLong(), transactions.show.bind(transactions));
  app.param('txid', transactions.transaction.bind(transactions));
  app.get('/txs', this.cacheShort(), transactions.list.bind(transactions));
  app.post('/tx/send', transactions.send.bind(transactions));

  // Raw Routes
  app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions));
  app.param('txid', transactions.rawTransaction.bind(transactions));

  // Address routes
  var addresses = new AddressController(this.node);
  app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses));
  app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses));
  app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
  app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
  app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));
  app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));

  // Address property routes
  app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses));
  app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses));
  app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses));
  app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses));

  // Status route
  var status = new StatusController(this.node);
  app.get('/status', this.cacheShort(), status.show.bind(status));
  app.get('/sync', this.cacheShort(), status.sync.bind(status));
  app.get('/peer', this.cacheShort(), status.peer.bind(status));
  app.get('/version', this.cacheShort(), status.version.bind(status));

  // Address routes
  var messages = new MessagesController(this.node);
  app.get('/messages/verify', messages.verify.bind(messages));
  app.post('/messages/verify', messages.verify.bind(messages));

  // Utils route
  var utils = new UtilsController(this.node);
  app.get('/utils/estimatefee', utils.estimateFee.bind(utils));

  // Currency
  var currency = new CurrencyController({
    node: this.node,
    currencyRefresh: this.currencyRefresh
  });
  app.get('/currency', currency.index.bind(currency));

  // Not Found
  app.use(function(req, res) {
    res.status(404).jsonp({
      status: 404,
      url: req.originalUrl,
      error: 'Not found'
    });
  });

};

InsightAPI.prototype.getPublishEvents = function() {
  return [
    {
      name: 'inv',
      scope: this,
      subscribe: this.subscribe.bind(this),
      unsubscribe: this.unsubscribe.bind(this),
      extraEvents: ['tx', 'block']
    }
  ];
};

InsightAPI.prototype.blockEventHandler = function(hashBuffer) {
  // Notify inv subscribers
  for (var i = 0; i < this.subscriptions.inv.length; i++) {
    this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex'));
  }
};
InsightAPI.prototype.transactionEventHandler = function(txBuffer) {
  var tx = new Transaction().fromBuffer(txBuffer);
  var result = this.txController.transformInvTransaction(tx);

  for (var i = 0; i < this.subscriptions.inv.length; i++) {
    this.subscriptions.inv[i].emit('tx', result);
  }
};

InsightAPI.prototype.subscribe = function(emitter) {
  $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');

  var emitters = this.subscriptions.inv;
  var index = emitters.indexOf(emitter);
  if(index === -1) {
    emitters.push(emitter);
  }
};

InsightAPI.prototype.unsubscribe = function(emitter) {
  $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');

  var emitters = this.subscriptions.inv;
  var index = emitters.indexOf(emitter);
  if(index > -1) {
    emitters.splice(index, 1);
  }
};

module.exports = InsightAPI;
<commit_msg>index: log remote connecting ip address<commit_after>'use strict';

var Writable = require('stream').Writable;
var bodyParser = require('body-parser');
var compression = require('compression');
var BaseService = require('./service');
var inherits = require('util').inherits;
var BlockController = require('./blocks');
var TxController = require('./transactions');
var AddressController = require('./addresses');
var StatusController = require('./status');
var MessagesController = require('./messages');
var UtilsController = require('./utils');
var CurrencyController = require('./currency');
var RateLimiter = require('./ratelimiter');
var morgan = require('morgan');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var Transaction = bitcore.Transaction;
var EventEmitter = require('events').EventEmitter;

/**
 * A service for Bitcore to enable HTTP routes to query information about the blockchain.
 *
 * @param {Object} options
 * @param {Boolean} options.enableCache - This will enable cache-control headers
 * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses.
 * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses.
 * @param {String} options.routePrefix - The URL route prefix
 */
var InsightAPI = function(options) {
  BaseService.call(this, options);

  // in minutes
  this.currencyRefresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY;

  this.subscriptions = {
    inv: []
  };

  if (!_.isUndefined(options.enableCache)) {
    $.checkArgument(_.isBoolean(options.enableCache));
    this.enableCache = options.enableCache;
  }
  this.cacheShortSeconds = options.cacheShortSeconds;
  this.cacheLongSeconds = options.cacheLongSeconds;

  this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE;
  this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE;

  if (!_.isUndefined(options.routePrefix)) {
    this.routePrefix = options.routePrefix;
  } else {
    this.routePrefix = this.name;
  }

  this.txController = new TxController(this.node);
};

InsightAPI.dependencies = ['bitcoind', 'web'];

inherits(InsightAPI, BaseService);

InsightAPI.prototype.cache = function(maxAge) {
  var self = this;
  return function(req, res, next) {
    if (self.enableCache) {
      res.header('Cache-Control', 'public, max-age=' + maxAge);
    }
    next();
  };
};

InsightAPI.prototype.cacheShort = function() {
  var seconds = this.cacheShortSeconds || 30; // thirty seconds
  return this.cache(seconds);
};

InsightAPI.prototype.cacheLong = function() {
  var seconds = this.cacheLongSeconds || 86400; // one day
  return this.cache(seconds);
};

InsightAPI.prototype.getRoutePrefix = function() {
  return this.routePrefix;
};

InsightAPI.prototype.start = function(callback) {
  this.node.services.bitcoind.on('tx', this.transactionEventHandler.bind(this));
  this.node.services.bitcoind.on('block', this.blockEventHandler.bind(this));
  setImmediate(callback);
};

InsightAPI.prototype.createLogInfoStream = function() {
  var self = this;

  function Log(options) {
    Writable.call(this, options);
  }
  inherits(Log, Writable);

  Log.prototype._write = function (chunk, enc, callback) {
    self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger
    callback();
  };
  var stream = new Log();

  return stream;
};

InsightAPI.prototype.getRemoteAddress = function(req) {
  if (req.headers['cf-connecting-ip']) {
    return req.headers['cf-connecting-ip'];
  }
  return req.socket.remoteAddress;
};

InsightAPI.prototype.setupRoutes = function(app) {

  var self = this;

  //Enable rate limiter
  var limiter = new RateLimiter({node: this.node});
  app.use(limiter.middleware());

  //Setup logging
  morgan.token('remote-forward-addr', function(req){
    return self.getRemoteAddress(req);
  });
  var logFormat = ':remote-forward-addr ":method :url" :status :res[content-length] :response-time ":user-agent" ';
  var logStream = this.createLogInfoStream();
  app.use(morgan(logFormat, {stream: logStream}));

  //Enable compression
  app.use(compression());

  //Enable urlencoded data
  app.use(bodyParser.urlencoded({extended: true}));

  //Enable CORS
  app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
  });

  //Block routes
  var blockOptions = {
    node: this.node,
    blockSummaryCacheSize: this.blockSummaryCacheSize,
    blockCacheSize: this.blockCacheSize
  };
  var blocks = new BlockController(blockOptions);
  app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks));

  app.get('/block/:blockHash', this.cacheLong(), blocks.show.bind(blocks));
  app.param('blockHash', blocks.block.bind(blocks));

  app.get('/rawblock/:blockArg', this.cacheLong(), blocks.showRaw.bind(blocks));
  app.param('blockArg', blocks.rawBlock.bind(blocks));

  app.get('/block-index/:height', this.cacheLong(), blocks.blockIndex.bind(blocks));
  app.param('height', blocks.blockIndex.bind(blocks));

  // Transaction routes
  var transactions = new TxController(this.node);
  app.get('/tx/:txid', this.cacheLong(), transactions.show.bind(transactions));
  app.param('txid', transactions.transaction.bind(transactions));
  app.get('/txs', this.cacheShort(), transactions.list.bind(transactions));
  app.post('/tx/send', transactions.send.bind(transactions));

  // Raw Routes
  app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions));
  app.param('txid', transactions.rawTransaction.bind(transactions));

  // Address routes
  var addresses = new AddressController(this.node);
  app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses));
  app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses));
  app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
  app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));
  app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));
  app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses));

  // Address property routes
  app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses));
  app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses));
  app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses));
  app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses));

  // Status route
  var status = new StatusController(this.node);
  app.get('/status', this.cacheShort(), status.show.bind(status));
  app.get('/sync', this.cacheShort(), status.sync.bind(status));
  app.get('/peer', this.cacheShort(), status.peer.bind(status));
  app.get('/version', this.cacheShort(), status.version.bind(status));

  // Address routes
  var messages = new MessagesController(this.node);
  app.get('/messages/verify', messages.verify.bind(messages));
  app.post('/messages/verify', messages.verify.bind(messages));

  // Utils route
  var utils = new UtilsController(this.node);
  app.get('/utils/estimatefee', utils.estimateFee.bind(utils));

  // Currency
  var currency = new CurrencyController({
    node: this.node,
    currencyRefresh: this.currencyRefresh
  });
  app.get('/currency', currency.index.bind(currency));

  // Not Found
  app.use(function(req, res) {
    res.status(404).jsonp({
      status: 404,
      url: req.originalUrl,
      error: 'Not found'
    });
  });

};

InsightAPI.prototype.getPublishEvents = function() {
  return [
    {
      name: 'inv',
      scope: this,
      subscribe: this.subscribe.bind(this),
      unsubscribe: this.unsubscribe.bind(this),
      extraEvents: ['tx', 'block']
    }
  ];
};

InsightAPI.prototype.blockEventHandler = function(hashBuffer) {
  // Notify inv subscribers
  for (var i = 0; i < this.subscriptions.inv.length; i++) {
    this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex'));
  }
};
InsightAPI.prototype.transactionEventHandler = function(txBuffer) {
  var tx = new Transaction().fromBuffer(txBuffer);
  var result = this.txController.transformInvTransaction(tx);

  for (var i = 0; i < this.subscriptions.inv.length; i++) {
    this.subscriptions.inv[i].emit('tx', result);
  }
};

InsightAPI.prototype.subscribe = function(emitter) {
  $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');

  var emitters = this.subscriptions.inv;
  var index = emitters.indexOf(emitter);
  if(index === -1) {
    emitters.push(emitter);
  }
};

InsightAPI.prototype.unsubscribe = function(emitter) {
  $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');

  var emitters = this.subscriptions.inv;
  var index = emitters.indexOf(emitter);
  if(index > -1) {
    emitters.splice(index, 1);
  }
};

module.exports = InsightAPI;
<|endoftext|>


<commit_before>/*
Copyright 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import MImageBody from './MImageBody';
import sdk from '../../../index';
import TintableSVG from '../elements/TintableSvg';

export default class MStickerBody extends MImageBody {
    displayName: 'MStickerBody'

    constructor(props) {
      super(props);

      this._onMouseEnter = this._onMouseEnter.bind(this);
      this._onMouseLeave = this._onMouseLeave.bind(this);
      this._onImageLoad = this._onImageLoad.bind(this);
    }

    _onMouseEnter() {
        this.setState({showTooltip: true});
    }

    _onMouseLeave() {
        this.setState({showTooltip: false});
    }

    _onImageLoad() {
        this.setState({
            placeholderClasses: 'mx_MStickerBody_placeholder_invisible',
        });
        const hidePlaceholderTimer = setTimeout(() => {
            this.setState({
                placeholderVisible: false,
                imageClasses: 'mx_MStickerBody_image_visible',
            });
        }, 500);
        this.setState({hidePlaceholderTimer});
        if (this.props.onWidgetLoad) {
            this.props.onWidgetLoad();
        }
    }

    _afterComponentDidMount() {
        if (this.refs.image.complete) {
            // Image already loaded
            this.setState({
                placeholderVisible: false,
                placeholderClasses: '.mx_MStickerBody_placeholder_invisible',
                imageClasses: 'mx_MStickerBody_image_visible',
            });
        } else {
            // Image not already loaded
            this.setState({
                placeholderVisible: true,
                placeholderClasses: '',
                imageClasses: '',
            });
        }
    }

    _afterComponentWillUnmount() {
        if (this.state.hidePlaceholderTimer) {
            clearTimeout(this.state.hidePlaceholderTimer);
            this.setState({hidePlaceholderTimer: null});
        }
    }

    _messageContent(contentUrl, thumbUrl, content) {
        let tooltip;
        const tooltipBody = (
            this.props.mxEvent &&
            this.props.mxEvent.getContent() &&
            this.props.mxEvent.getContent().body) ?
            this.props.mxEvent.getContent().body : null;
        if (this.state.showTooltip && tooltipBody) {
            const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
            tooltip = <RoomTooltip
                className='mx_RoleButton_tooltip'
                label={tooltipBody} />;
        }

        const gutterSize = 0;
        let placeholderSize = 75;
        let placeholderFixupHeight = '100px';
        let placeholderTop = 0;
        let placeholderLeft = 0;

        if (content.info) {
            placeholderTop = Math.floor((content.info.h/2) - (placeholderSize/2)) + 'px';
            placeholderLeft = Math.floor((content.info.w/2) - (placeholderSize/2) + gutterSize) + 'px';
            placeholderFixupHeight = content.info.h + 'px';
        }

        placeholderSize = placeholderSize + 'px';

        // Body 'ref' required by MImageBody
        return (

                  { this.state.placeholderVisible &&

                        <TintableSVG
                            src={'img/icons-show-stickers.svg'}
                            width={placeholderSize}
                            height={placeholderSize} />

 }

                    { tooltip }

        );
    }

    // Empty to prevent default behaviour of MImageBody
    onClick() {
    }
}
<commit_msg>blind fix to the overlapping sticker bug<commit_after>/*
Copyright 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

import MImageBody from './MImageBody';
import sdk from '../../../index';
import TintableSVG from '../elements/TintableSvg';

export default class MStickerBody extends MImageBody {
    displayName: 'MStickerBody'

    constructor(props) {
      super(props);

      this._onMouseEnter = this._onMouseEnter.bind(this);
      this._onMouseLeave = this._onMouseLeave.bind(this);
      this._onImageLoad = this._onImageLoad.bind(this);
    }

    _onMouseEnter() {
        this.setState({showTooltip: true});
    }

    _onMouseLeave() {
        this.setState({showTooltip: false});
    }

    _onImageLoad() {
        this.fixupHeight();
        this.setState({
            placeholderClasses: 'mx_MStickerBody_placeholder_invisible',
        });
        const hidePlaceholderTimer = setTimeout(() => {
            this.setState({
                placeholderVisible: false,
                imageClasses: 'mx_MStickerBody_image_visible',
            });
        }, 500);
        this.setState({hidePlaceholderTimer});
        if (this.props.onWidgetLoad) {
            this.props.onWidgetLoad();
        }
    }

    _afterComponentDidMount() {
        if (this.refs.image.complete) {
            // Image already loaded
            this.setState({
                placeholderVisible: false,
                placeholderClasses: '.mx_MStickerBody_placeholder_invisible',
                imageClasses: 'mx_MStickerBody_image_visible',
            });
        } else {
            // Image not already loaded
            this.setState({
                placeholderVisible: true,
                placeholderClasses: '',
                imageClasses: '',
            });
        }
    }

    _afterComponentWillUnmount() {
        if (this.state.hidePlaceholderTimer) {
            clearTimeout(this.state.hidePlaceholderTimer);
            this.setState({hidePlaceholderTimer: null});
        }
    }

    _messageContent(contentUrl, thumbUrl, content) {
        let tooltip;
        const tooltipBody = (
            this.props.mxEvent &&
            this.props.mxEvent.getContent() &&
            this.props.mxEvent.getContent().body) ?
            this.props.mxEvent.getContent().body : null;
        if (this.state.showTooltip && tooltipBody) {
            const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
            tooltip = <RoomTooltip
                className='mx_RoleButton_tooltip'
                label={tooltipBody} />;
        }

        const gutterSize = 0;
        let placeholderSize = 75;
        let placeholderFixupHeight = '100px';
        let placeholderTop = 0;
        let placeholderLeft = 0;

        if (content.info) {
            placeholderTop = Math.floor((content.info.h/2) - (placeholderSize/2)) + 'px';
            placeholderLeft = Math.floor((content.info.w/2) - (placeholderSize/2) + gutterSize) + 'px';
            placeholderFixupHeight = content.info.h + 'px';
        }

        placeholderSize = placeholderSize + 'px';

        // Body 'ref' required by MImageBody
        return (

                  { this.state.placeholderVisible &&

                        <TintableSVG
                            src={'img/icons-show-stickers.svg'}
                            width={placeholderSize}
                            height={placeholderSize} />

 }

                    { tooltip }

        );
    }

    // Empty to prevent default behaviour of MImageBody
    onClick() {
    }
}
<|endoftext|>


<commit_before>const fs = require('fs');

var bitmap = 'palette-bitmap.bmp';

var invertBitmap = function(file) {
  fs.readFile(file, function(err, data) {
    if (err) return console.log(err);
    for (var i = 54; i < 1077; i++) {
      debugger;
      data[i] = 255 - data[i];
      debugger;
    }
    fs.writeFile('inverted-palette-bitmap.bmp', data);
  });
};

invertBitmap(bitmap);
<commit_msg>removed debugger statements<commit_after>const fs = require('fs');

var bitmap = 'palette-bitmap.bmp';

var invertBitmap = function(file) {
  fs.readFile(file, function(err, data) {
    if (err) return console.log(err);
    for (var i = 54; i < 1077; i++) {
      data[i] = 255 - data[i];
    }
    fs.writeFile('inverted-palette-bitmap.bmp', data);
  });
};

invertBitmap(bitmap);
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const React = require('react');

const CallHandler = require('../../../CallHandler');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const Modal = require('../../../Modal');
const sdk = require('../../../index');
const dis = require('../../../dispatcher');
// import Autocomplete from './Autocomplete';
import classNames from 'classnames';

import UserSettingsStore from '../../../UserSettingsStore';

export default class MessageComposer extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.onCallClick = this.onCallClick.bind(this);
        this.onHangupClick = this.onHangupClick.bind(this);
        this.onUploadClick = this.onUploadClick.bind(this);
        this.onShowAppsClick = this.onShowAppsClick.bind(this);
        this.onHideAppsClick = this.onHideAppsClick.bind(this);
        this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
        this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
        this.onInputContentChanged = this.onInputContentChanged.bind(this);
        this.onUpArrow = this.onUpArrow.bind(this);
        this.onDownArrow = this.onDownArrow.bind(this);
        this._tryComplete = this._tryComplete.bind(this);
        this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
        this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
        this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
        this.onInputStateChanged = this.onInputStateChanged.bind(this);
        this.onEvent = this.onEvent.bind(this);

        this.state = {
            autocompleteQuery: '',
            selection: null,
            inputState: {
                style: [],
                blockType: null,
                isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true),
                wordCount: 0,
            },
            showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
        };
    }

    componentDidMount() {
        // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
        // for 'event' fires *after* 'RoomEvent', and our room won't have yet been
        // marked as encrypted.
        // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
        MatrixClientPeg.get().on("event", this.onEvent);
    }

    componentWillUnmount() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("event", this.onEvent);
        }
    }

    onEvent(event) {
        if (event.getType() !== 'm.room.encryption') return;
        if (event.getRoomId() !== this.props.room.roomId) return;
        this.forceUpdate();
    }

    onUploadClick(ev) {
        if (MatrixClientPeg.get().isGuest()) {
            const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
            Modal.createDialog(NeedToRegisterDialog, {
                title: "Please Register",
                description: "Guest users can't upload files. Please register to upload.",
            });
            return;
        }

        this.refs.uploadInput.click();
    }

    onUploadFileSelected(files, isPasted) {
        if (!isPasted) {
            files = files.target.files;
        }

        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
        const TintableSvg = sdk.getComponent("elements.TintableSvg");

        const fileList = [];
        for (let i=0; i<files.length; i++) {
            fileList.push(

                <TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}

);
        }

        Modal.createDialog(QuestionDialog, {
            title: "Upload Files",
            description: (

Are you sure you want upload the following files?

                        {fileList}

            ),
            onFinished: (shouldUpload) => {
                if(shouldUpload) {
                    // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
                    if (files) {
                        for(let i=0; i<files.length; i++) {
                            this.props.uploadFile(files[i]);
                        }
                    }
                }

                this.refs.uploadInput.value = null;
            },
        });
    }

    onHangupClick() {
        const call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId,
        });
    }

    onCallClick(ev) {
        console.warn("Call but clicked!");
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId,
        });
    }

    onVoiceCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: 'voice',
            room_id: this.props.room.roomId,
        });
    }

    onShowAppsClick(ev) {
        alert("Showing apps");
        console.warn("Showing apps");
        // dis.dispatch({
        //     action: 'showApps',
        //     room_id: this.props.room.roomId,
        // });
    }

    onHideAppsClick(ev) {
        alert("Hiding apps");
        console.warn("Hiding apps");
        // dis.dispatch({
        //     action: 'hideApps',
        //     room_id: this.props.room.roomId,
        // });
    }

    onInputContentChanged(content: string, selection: {start: number, end: number}) {
        this.setState({
            autocompleteQuery: content,
            selection,
        });
    }

    onInputStateChanged(inputState) {
        this.setState({inputState});
    }

    onUpArrow() {
        return this.refs.autocomplete.onUpArrow();
    }

    onDownArrow() {
        return this.refs.autocomplete.onDownArrow();
    }

    _tryComplete(): boolean {
        if (this.refs.autocomplete) {
            return this.refs.autocomplete.onCompletionClicked();
        }
        return false;
    }

    _onAutocompleteConfirm(range, completion) {
        if (this.messageComposerInput) {
            this.messageComposerInput.setDisplayedCompletion(range, completion);
        }
    }

    onFormatButtonClicked(name: "bold" | "italic" | "strike" | "code" | "underline" | "quote" | "bullet" | "numbullet", event) {
        event.preventDefault();
        this.messageComposerInput.onFormatButtonClicked(name, event);
    }

    onToggleFormattingClicked() {
        UserSettingsStore.setSyncedSetting('MessageComposer.showFormatting', !this.state.showFormatting);
        this.setState({showFormatting: !this.state.showFormatting});
    }

    onToggleMarkdownClicked(e) {
        e.preventDefault(); // don't steal focus from the editor!
        this.messageComposerInput.enableRichtext(!this.state.inputState.isRichtextEnabled);
    }

    render() {
        const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
        const uploadInputStyle = {display: 'none'};
        const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        const TintableSvg = sdk.getComponent("elements.TintableSvg");
        const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput" +
            (UserSettingsStore.isFeatureEnabled('rich_text_editor') ? "" : "Old"));

        const controls = [];

        controls.push(

                <MemberAvatar member={me} width={24} height={24} />

,
        );

        let e2eImg, e2eTitle, e2eClass;
        const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
        if (roomIsEncrypted) {
            // FIXME: show a /!\ if there are untrusted devices in the room...
            e2eImg = 'img/e2e-verified.svg';
            e2eTitle = 'Encrypted room';
            e2eClass = 'mx_MessageComposer_e2eIcon';
        } else {
            e2eImg = 'img/e2e-unencrypted.svg';
            e2eTitle = 'Unencrypted room';
            e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
        }

        controls.push(
            ,
        );
        let callButton, videoCallButton, hangupButton, showAppsButton, hideAppsButton;
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        } else {
            callButton =

                    <TintableSvg src="img/icon-call.svg" width="35" height="35"/>

;
            videoCallButton =

                    <TintableSvg src="img/icons-video.svg" width="35" height="35"/>

;
        }

        // Apps
        if (this.props.showAppsState && this.props.showAppsState == 'visible') {
            hideAppsButton =

                    <TintableSvg src="img/icons-apps-active.svg" width="35" height="35"/>

;
        } else {
            showAppsButton =

                    <TintableSvg src="img/icons-apps.svg" width="35" height="35"/>

;
        }

        const canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            const uploadButton = (

                    <TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            const formattingButton = (

            );

            const placeholderText = roomIsEncrypted ?
                "Send an encrypted message…" : "Send a message (unencrypted)…";

            controls.push(
                <MessageComposerInput
                    ref={(c) => this.messageComposerInput = c}
                    key="controls_input"
                    onResize={this.props.onResize}
                    room={this.props.room}
                    placeholder={placeholderText}
                    tryComplete={this._tryComplete}
                    onUpArrow={this.onUpArrow}
                    onDownArrow={this.onDownArrow}
                    onUploadFileSelected={this.onUploadFileSelected}
                    tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
                    onContentChanged={this.onInputContentChanged}
                    onInputStateChanged={this.onInputStateChanged} />,
                formattingButton,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton,
                showAppsButton,
                hideAppsButton,
            );
        } else {
            controls.push(

                    You do not have permission to post to this room

,
            );
        }

        // let autoComplete;
        // if (UserSettingsStore.isFeatureEnabled('rich_text_editor')) {
        //     autoComplete =

        //         <Autocomplete
        //             ref="autocomplete"
        //             onConfirm={this._onAutocompleteConfirm}
        //             query={this.state.autocompleteQuery}
        //             selection={this.state.selection} />
        //
;
        // }

        const {style, blockType} = this.state.inputState;
        const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map(
            (name) => {
                const active = style.includes(name) || blockType === name;
                const suffix = active ? '-o-n' : '';
                const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
                const disabled = !this.state.inputState.isRichtextEnabled && 'underline' === name;
                const className = classNames("mx_MessageComposer_format_button", {
                    mx_MessageComposer_format_button_disabled: disabled,
                    mx_filterFlipColor: true,
                });
                return <img className={className}
                            title={name}
                            onMouseDown={disabled ? null : onFormatButtonClicked}
                            key={name}
                            src={`img/button-text-${name}${suffix}.svg`}
                            height="17" />;
            },
        );

        return (

                        {controls}

                {UserSettingsStore.isFeatureEnabled('rich_text_editor') ?

                            {formatButtons}

                            <img title={`Turn Markdown ${this.state.inputState.isRichtextEnabled ? 'on' : 'off'}`}
                                 onMouseDown={this.onToggleMarkdownClicked}
                                className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
                                src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />

: null
                }

        );
    }
}

MessageComposer.propTypes = {
    tabComplete: React.PropTypes.any,

    // a callback which is called when the height of the composer is
    // changed due to a change in content.
    onResize: React.PropTypes.func,

    // js-sdk Room object
    room: React.PropTypes.object.isRequired,

    // string representing the current voip call state
    callState: React.PropTypes.string,

    // callback when a file to upload is chosen
    uploadFile: React.PropTypes.func.isRequired,

    // opacity for dynamic UI fading effects
    opacity: React.PropTypes.number,

    // string representing the current room app drawer state
    showAppsState: React.PropTypes.string,
};
<commit_msg>Dispatch show hide app drawer events<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const React = require('react');

const CallHandler = require('../../../CallHandler');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const Modal = require('../../../Modal');
const sdk = require('../../../index');
const dis = require('../../../dispatcher');
// import Autocomplete from './Autocomplete';
import classNames from 'classnames';

import UserSettingsStore from '../../../UserSettingsStore';

export default class MessageComposer extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.onCallClick = this.onCallClick.bind(this);
        this.onHangupClick = this.onHangupClick.bind(this);
        this.onUploadClick = this.onUploadClick.bind(this);
        this.onShowAppsClick = this.onShowAppsClick.bind(this);
        this.onHideAppsClick = this.onHideAppsClick.bind(this);
        this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
        this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
        this.onInputContentChanged = this.onInputContentChanged.bind(this);
        this.onUpArrow = this.onUpArrow.bind(this);
        this.onDownArrow = this.onDownArrow.bind(this);
        this._tryComplete = this._tryComplete.bind(this);
        this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
        this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
        this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
        this.onInputStateChanged = this.onInputStateChanged.bind(this);
        this.onEvent = this.onEvent.bind(this);

        this.state = {
            autocompleteQuery: '',
            selection: null,
            inputState: {
                style: [],
                blockType: null,
                isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true),
                wordCount: 0,
            },
            showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
        };
    }

    componentDidMount() {
        // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
        // for 'event' fires *after* 'RoomEvent', and our room won't have yet been
        // marked as encrypted.
        // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
        MatrixClientPeg.get().on("event", this.onEvent);
    }

    componentWillUnmount() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("event", this.onEvent);
        }
    }

    onEvent(event) {
        if (event.getType() !== 'm.room.encryption') return;
        if (event.getRoomId() !== this.props.room.roomId) return;
        this.forceUpdate();
    }

    onUploadClick(ev) {
        if (MatrixClientPeg.get().isGuest()) {
            const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
            Modal.createDialog(NeedToRegisterDialog, {
                title: "Please Register",
                description: "Guest users can't upload files. Please register to upload.",
            });
            return;
        }

        this.refs.uploadInput.click();
    }

    onUploadFileSelected(files, isPasted) {
        if (!isPasted) {
            files = files.target.files;
        }

        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
        const TintableSvg = sdk.getComponent("elements.TintableSvg");

        const fileList = [];
        for (let i=0; i<files.length; i++) {
            fileList.push(

                <TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}

);
        }

        Modal.createDialog(QuestionDialog, {
            title: "Upload Files",
            description: (

Are you sure you want upload the following files?

                        {fileList}

            ),
            onFinished: (shouldUpload) => {
                if(shouldUpload) {
                    // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
                    if (files) {
                        for(let i=0; i<files.length; i++) {
                            this.props.uploadFile(files[i]);
                        }
                    }
                }

                this.refs.uploadInput.value = null;
            },
        });
    }

    onHangupClick() {
        const call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId,
        });
    }

    onCallClick(ev) {
        console.warn("Call but clicked!");
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId,
        });
    }

    onVoiceCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: 'voice',
            room_id: this.props.room.roomId,
        });
    }

    onShowAppsClick(ev) {
        console.warn("Showing apps");
        dis.dispatch({
            action: 'showApps',
            room_id: this.props.room.roomId,
        });
    }

    onHideAppsClick(ev) {
        console.warn("Hiding apps");
        dis.dispatch({
            action: 'hideApps',
            room_id: this.props.room.roomId,
        });
    }

    onInputContentChanged(content: string, selection: {start: number, end: number}) {
        this.setState({
            autocompleteQuery: content,
            selection,
        });
    }

    onInputStateChanged(inputState) {
        this.setState({inputState});
    }

    onUpArrow() {
        return this.refs.autocomplete.onUpArrow();
    }

    onDownArrow() {
        return this.refs.autocomplete.onDownArrow();
    }

    _tryComplete(): boolean {
        if (this.refs.autocomplete) {
            return this.refs.autocomplete.onCompletionClicked();
        }
        return false;
    }

    _onAutocompleteConfirm(range, completion) {
        if (this.messageComposerInput) {
            this.messageComposerInput.setDisplayedCompletion(range, completion);
        }
    }

    onFormatButtonClicked(name: "bold" | "italic" | "strike" | "code" | "underline" | "quote" | "bullet" | "numbullet", event) {
        event.preventDefault();
        this.messageComposerInput.onFormatButtonClicked(name, event);
    }

    onToggleFormattingClicked() {
        UserSettingsStore.setSyncedSetting('MessageComposer.showFormatting', !this.state.showFormatting);
        this.setState({showFormatting: !this.state.showFormatting});
    }

    onToggleMarkdownClicked(e) {
        e.preventDefault(); // don't steal focus from the editor!
        this.messageComposerInput.enableRichtext(!this.state.inputState.isRichtextEnabled);
    }

    render() {
        const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
        const uploadInputStyle = {display: 'none'};
        const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        const TintableSvg = sdk.getComponent("elements.TintableSvg");
        const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput" +
            (UserSettingsStore.isFeatureEnabled('rich_text_editor') ? "" : "Old"));

        const controls = [];

        controls.push(

                <MemberAvatar member={me} width={24} height={24} />

,
        );

        let e2eImg, e2eTitle, e2eClass;
        const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
        if (roomIsEncrypted) {
            // FIXME: show a /!\ if there are untrusted devices in the room...
            e2eImg = 'img/e2e-verified.svg';
            e2eTitle = 'Encrypted room';
            e2eClass = 'mx_MessageComposer_e2eIcon';
        } else {
            e2eImg = 'img/e2e-unencrypted.svg';
            e2eTitle = 'Unencrypted room';
            e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
        }

        controls.push(
            ,
        );
        let callButton, videoCallButton, hangupButton, showAppsButton, hideAppsButton;
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        } else {
            callButton =

                    <TintableSvg src="img/icon-call.svg" width="35" height="35"/>

;
            videoCallButton =

                    <TintableSvg src="img/icons-video.svg" width="35" height="35"/>

;
        }

        // Apps
        if (this.props.showAppsState && this.props.showAppsState == 'visible') {
            hideAppsButton =

                    <TintableSvg src="img/icons-apps-active.svg" width="35" height="35"/>

;
        } else {
            showAppsButton =

                    <TintableSvg src="img/icons-apps.svg" width="35" height="35"/>

;
        }

        const canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            const uploadButton = (

                    <TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            const formattingButton = (

            );

            const placeholderText = roomIsEncrypted ?
                "Send an encrypted message…" : "Send a message (unencrypted)…";

            controls.push(
                <MessageComposerInput
                    ref={(c) => this.messageComposerInput = c}
                    key="controls_input"
                    onResize={this.props.onResize}
                    room={this.props.room}
                    placeholder={placeholderText}
                    tryComplete={this._tryComplete}
                    onUpArrow={this.onUpArrow}
                    onDownArrow={this.onDownArrow}
                    onUploadFileSelected={this.onUploadFileSelected}
                    tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
                    onContentChanged={this.onInputContentChanged}
                    onInputStateChanged={this.onInputStateChanged} />,
                formattingButton,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton,
                showAppsButton,
                hideAppsButton,
            );
        } else {
            controls.push(

                    You do not have permission to post to this room

,
            );
        }

        // let autoComplete;
        // if (UserSettingsStore.isFeatureEnabled('rich_text_editor')) {
        //     autoComplete =

        //         <Autocomplete
        //             ref="autocomplete"
        //             onConfirm={this._onAutocompleteConfirm}
        //             query={this.state.autocompleteQuery}
        //             selection={this.state.selection} />
        //
;
        // }

        const {style, blockType} = this.state.inputState;
        const formatButtons = ["bold", "italic", "strike", "underline", "code", "quote", "bullet", "numbullet"].map(
            (name) => {
                const active = style.includes(name) || blockType === name;
                const suffix = active ? '-o-n' : '';
                const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
                const disabled = !this.state.inputState.isRichtextEnabled && 'underline' === name;
                const className = classNames("mx_MessageComposer_format_button", {
                    mx_MessageComposer_format_button_disabled: disabled,
                    mx_filterFlipColor: true,
                });
                return <img className={className}
                            title={name}
                            onMouseDown={disabled ? null : onFormatButtonClicked}
                            key={name}
                            src={`img/button-text-${name}${suffix}.svg`}
                            height="17" />;
            },
        );

        return (

                        {controls}

                {UserSettingsStore.isFeatureEnabled('rich_text_editor') ?

                            {formatButtons}

                            <img title={`Turn Markdown ${this.state.inputState.isRichtextEnabled ? 'on' : 'off'}`}
                                 onMouseDown={this.onToggleMarkdownClicked}
                                className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
                                src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />

: null
                }

        );
    }
}

MessageComposer.propTypes = {
    tabComplete: React.PropTypes.any,

    // a callback which is called when the height of the composer is
    // changed due to a change in content.
    onResize: React.PropTypes.func,

    // js-sdk Room object
    room: React.PropTypes.object.isRequired,

    // string representing the current voip call state
    callState: React.PropTypes.string,

    // callback when a file to upload is chosen
    uploadFile: React.PropTypes.func.isRequired,

    // opacity for dynamic UI fading effects
    opacity: React.PropTypes.number,

    // string representing the current room app drawer state
    showAppsState: React.PropTypes.string,
};
<|endoftext|>


<commit_before>/*
 * Node module wrapper for the PhantomJS script
 */

var path = require('path');
var spawn = require('child_process').spawn;
var phantomjs = require('phantomjs');
var phantomJsBinPath = phantomjs.path;
var configString = ('--config=' + path.join(__dirname, 'phantomjs', 'ssl-config.json'));
var script = path.join(__dirname, 'phantomjs', 'core.js');

var m = module.exports = function (options, callback) {
    var scriptArgs = [],
        stdOut = '',
        stdErr = '',
        debuggingHelp = '',
        cp;

//        m.DEBUG = true;

    // set the options, skipping undefined options
    if(options.width) scriptArgs = scriptArgs.concat(['--width', options.width])
    if(options.height) scriptArgs = scriptArgs.concat(['--height', options.height])
    scriptArgs.push(options.url);
    scriptArgs.push(options.css);

    cp = spawn(phantomJsBinPath, [configString, script].concat(scriptArgs));

    // Errors arise before the process starts
    cp.on('error', function (err) {
        debuggingHelp += 'Error executing penthouse using ' + phantomJsBinPath;
        debuggingHelp += err.stack;
        err.debug = debuggingHelp;
        callback(err);
    });

    cp.stdout.on('data', function (data) {
        stdOut += data;
        if(m.DEBUG) { console.log('' + data); }
    });

    cp.stderr.on('data', function (data) {
        stdErr += data;
        if(m.DEBUG) console.error( '' + data);
    });

    cp.on('close', function (code) {
        if (code !== 0) {
            debuggingHelp += 'PhantomJS process closed with code ' + code;
        }
    });

    cp.on('exit', function (code) {
        if (code === 0) {
            if(m.DEBUG) {
                console.log('stdout: ' + stdOut);
                console.log('stderr: ' + stdErr);
            }
            callback(null, stdOut);
        } else {
            debuggingHelp += 'PhantomJS process exited with code ' + code;
            var err = new Error(stdErr + stdOut);
            err.code = code;
            err.debug = debuggingHelp;
            err.stdout = stdOut;
            err.stderr = stdErr;
            callback( err );
        }
    });

    process.on('SIGTERM', function () {
        cp.kill('SIGTERM');
        process.exit(1);
    });

};
<commit_msg>bugfix: old config filename replaced<commit_after>/*
 * Node module wrapper for the PhantomJS script
 */

var path = require('path');
var spawn = require('child_process').spawn;
var phantomjs = require('phantomjs');
var phantomJsBinPath = phantomjs.path;
var configString = ('--config=' + path.join(__dirname, 'phantomjs', 'config.json'));
var script = path.join(__dirname, 'phantomjs', 'core.js');

var m = module.exports = function (options, callback) {
    var scriptArgs = [],
        stdOut = '',
        stdErr = '',
        debuggingHelp = '',
        cp;

//        m.DEBUG = true;

    // set the options, skipping undefined options
    if(options.width) scriptArgs = scriptArgs.concat(['--width', options.width])
    if(options.height) scriptArgs = scriptArgs.concat(['--height', options.height])
    scriptArgs.push(options.url);
    scriptArgs.push(options.css);

    cp = spawn(phantomJsBinPath, [configString, script].concat(scriptArgs));

    // Errors arise before the process starts
    cp.on('error', function (err) {
        debuggingHelp += 'Error executing penthouse using ' + phantomJsBinPath;
        debuggingHelp += err.stack;
        err.debug = debuggingHelp;
        callback(err);
    });

    cp.stdout.on('data', function (data) {
        stdOut += data;
        if(m.DEBUG) { console.log('' + data); }
    });

    cp.stderr.on('data', function (data) {
        stdErr += data;
        if(m.DEBUG) console.error( '' + data);
    });

    cp.on('close', function (code) {
        if (code !== 0) {
            debuggingHelp += 'PhantomJS process closed with code ' + code;
        }
    });

    cp.on('exit', function (code) {
        if (code === 0) {
            if(m.DEBUG) {
                console.log('stdout: ' + stdOut);
                console.log('stderr: ' + stdErr);
            }
            callback(null, stdOut);
        } else {
            debuggingHelp += 'PhantomJS process exited with code ' + code;
            var err = new Error(stdErr + stdOut);
            err.code = code;
            err.debug = debuggingHelp;
            err.stdout = stdOut;
            err.stderr = stdErr;
            callback( err );
        }
    });

    process.on('SIGTERM', function () {
        cp.kill('SIGTERM');
        process.exit(1);
    });

};
<|endoftext|>


<commit_before>jest.setTimeout(15000)
const createProducer = require('../../producer')
const createConsumer = require('../index')

const {
  secureRandom,
  createCluster,
  createTopic,
  createModPartitioner,
  newLogger,
  testIfKafkaAtLeast_0_11,
  waitForMessages,
  generateMessages,
  waitForConsumerToJoinGroup,
} = require('testHelpers')

describe('Consumer', () => {
  let topicName, groupId, transactionalId, cluster, producer, consumer

  beforeEach(async () => {
    topicName = `test-topic-${secureRandom()}`
    groupId = `consumer-group-id-${secureRandom()}`
    transactionalId = `transaction-id-${secureRandom()}`

    await createTopic({ topic: topicName })

    cluster = createCluster({
      maxInFlightRequests: 1,
    })

    producer = createProducer({
      cluster,
      transactionalId,
      idempotent: true,
      createPartitioner: createModPartitioner,
      logger: newLogger(),
    })

    consumer = createConsumer({
      cluster,
      groupId,
      maxWaitTimeInMs: 100,
      maxBytes: 170,
      logger: newLogger(),
    })
  })

  afterEach(async () => {
    consumer && (await consumer.disconnect())
    producer && (await producer.disconnect())
  })

  testIfKafkaAtLeast_0_11('forwards empty control batches to eachBatch', async () => {
    jest.spyOn(cluster, 'refreshMetadataIfNecessary')

    await consumer.connect()
    await producer.connect()
    await consumer.subscribe({ topic: topicName, fromBeginning: true })

    const messagesConsumed = []
    consumer.run({
      eachBatchAutoResolve: false,
      eachBatch: async ({ batch, resolveOffset, heartbeat, isRunning, isStale }) => {
        for (const message of batch.messages) {
          if (!isRunning() || isStale()) break
          messagesConsumed.push(message)
          resolveOffset(message.offset)
          await heartbeat()
        }
      },
    })

    await waitForConsumerToJoinGroup(consumer)
    const messagesTransaction1 = generateMessages({ number: 20 })

    const transaction = await producer.transaction()
    for (const message of messagesTransaction1) {
      await transaction.send({ topic: topicName, messages: [message] })
    }

    await transaction.commit()
    await waitForMessages(messagesConsumed, { number: 20 })

    producer.send({ topic: topicName, messages: generateMessages({ number: 2 }) })
    await waitForMessages(messagesConsumed, { number: 22 })
  })
})
<commit_msg>chore: added failing test for aborted messages<commit_after>jest.setTimeout(15000)
const createProducer = require('../../producer')
const createConsumer = require('../index')
const crypto = require('crypto')

const {
  secureRandom,
  createCluster,
  createTopic,
  createModPartitioner,
  newLogger,
  testIfKafkaAtLeast_0_11,
  waitForMessages,
  waitFor,
  generateMessages,
  waitForConsumerToJoinGroup,
} = require('testHelpers')

describe('Consumer', () => {
  let topicName, groupId, transactionalId, cluster, producer, consumer

  beforeEach(async () => {
    topicName = `test-topic-${secureRandom()}`
    groupId = `consumer-group-id-${secureRandom()}`
    transactionalId = `transaction-id-${secureRandom()}`

    await createTopic({ topic: topicName })

    cluster = createCluster({
      maxInFlightRequests: 1,
    })

    producer = createProducer({
      cluster,
      transactionalId,
      idempotent: true,
      createPartitioner: createModPartitioner,
      logger: newLogger(),
    })

    consumer = createConsumer({
      cluster,
      groupId,
      maxWaitTimeInMs: 100,
      maxBytes: 170,
      logger: newLogger(),
    })
  })

  afterEach(async () => {
    consumer && (await consumer.disconnect())
    producer && (await producer.disconnect())
  })

  testIfKafkaAtLeast_0_11('forwards empty control batches to eachBatch', async () => {
    jest.spyOn(cluster, 'refreshMetadataIfNecessary')

    await consumer.connect()
    await producer.connect()
    await consumer.subscribe({ topic: topicName, fromBeginning: true })

    const messagesConsumed = []
    consumer.run({
      eachBatchAutoResolve: false,
      eachBatch: async ({ batch, resolveOffset, heartbeat, isRunning, isStale }) => {
        for (const message of batch.messages) {
          if (!isRunning() || isStale()) break
          messagesConsumed.push(message)
          resolveOffset(message.offset)
          await heartbeat()
        }
      },
    })

    await waitForConsumerToJoinGroup(consumer)
    const messagesTransaction1 = generateMessages({ number: 20 })

    const transaction = await producer.transaction()
    for (const message of messagesTransaction1) {
      await transaction.send({ topic: topicName, messages: [message] })
    }

    await transaction.commit()
    await waitForMessages(messagesConsumed, { number: 20 })

    producer.send({ topic: topicName, messages: generateMessages({ number: 2 }) })
    await waitForMessages(messagesConsumed, { number: 22 })
  })

  it('can process transactions across multiple batches', async () => {
    await consumer.connect()
    await producer.connect()
    await consumer.subscribe({ topic: topicName, fromBeginning: true })

    let offset = '0'
    consumer.on(consumer.events.END_BATCH_PROCESS, event => {
      console.log(event.payload.firstOffset)
      offset = event.payload.lastOffset
    })

    consumer.run({
      eachMessage: async payload => {},
    })

    const range = [1, 2]
    const message = {
      key: 'test',
      // half size of consumer maxBytes
      value: crypto.randomBytes(130),
    }

    const transaction = await producer.transaction()
    await transaction.send({
      topic: topicName,
      acks: -1,
      messages: range.map(() => message),
    })
    await transaction.abort()

    const done = waitFor(() => offset === '2', {
      delay: 50,
      maxWait: 5000,
    })

    await expect(done).resolves.toBe(true)
  })
})
<|endoftext|>


<commit_before>var Watcher = require('./watcher')
  , Deployer = require('./deployer')
  , Generator = require('./generator')
  , Config = require('./config')
  , Compiler = require('./compiler')

  , events = require('events')
  , path = require('path')
  , when = require('when')
  , fs = require('fs');

var Peak = function (root_path, options) {
  this.path = path.resolve(root_path);
  this.config = config = new Config(this, options);
  this.options = config.options();
  this.emitter = new events.EventEmitter;
}

Peak.prototype = {

  generate: function () {
    var generator = new Generator(this, this.config.options());
    return generator.start();
  },

  watch: function () {
    var watcher = new Watcher(this, this.config.options('watch'));
    return when.try(this.validate_path.bind(this))
      .then(watcher.start.bind(watcher));
  },

  deploy: function (options) {
    var deployer = new Deployer(this, this.config.options('deploy'));
    return when.try(this.validate_path.bind(this))
      .then(deployer.start.bind(deployer));
  },

  validate_path: function () {
    if (!fs.existsSync(this.path)) throw new Error('Path doesn\'t exist.');

    this.lstat = fs.lstatSync(this.path);
    if (!this.lstat.isDirectory()) throw new Error('Path is not a directory.');
  }

}

module.exports = Peak;
<commit_msg>Remove superfluous config.options call.<commit_after>var Watcher = require('./watcher')
  , Deployer = require('./deployer')
  , Generator = require('./generator')
  , Config = require('./config')
  , Compiler = require('./compiler')

  , events = require('events')
  , path = require('path')
  , when = require('when')
  , fs = require('fs');

var Peak = function (root_path, options) {
  this.path = path.resolve(root_path);
  this.config = config = new Config(this, options);
  this.emitter = new events.EventEmitter;
}

Peak.prototype = {

  generate: function () {
    var generator = new Generator(this, this.config.options());
    return generator.start();
  },

  watch: function () {
    var watcher = new Watcher(this, this.config.options('watch'));
    return when.try(this.validate_path.bind(this))
      .then(watcher.start.bind(watcher));
  },

  deploy: function (options) {
    var deployer = new Deployer(this, this.config.options('deploy'));
    return when.try(this.validate_path.bind(this))
      .then(deployer.start.bind(deployer));
  },

  validate_path: function () {
    if (!fs.existsSync(this.path)) throw new Error('Path doesn\'t exist.');

    this.lstat = fs.lstatSync(this.path);
    if (!this.lstat.isDirectory()) throw new Error('Path is not a directory.');
  }

}

module.exports = Peak;
<|endoftext|>