<commit_before>var _ = require('lodash'),
  Backbone = require('backbone'),
  Db = require('backbone-db'),
  redis = require('redis'),
  debug = require('debug')('backbone-db-redis'),
  indexing = require('./lib/indexing'),
  query = require('./lib/query');

var RedisDb = Backbone.RedisDb = function(name, client) {
  this.name = name ||  '';
  this.redis = client;
  if (!this.redis) {
    this.redis = redis.createClient();
  }
};

Backbone.RedisDb.prototype.key = function(key) {
  if (this.name === '') {
    return key;
  } else {
    return this.name + ':' + key;
  }
};

var loadFnMap = {
  'string': 'get',
  'hash': 'hgetall'
};

var saveFnMap = {
  'string': 'set',
  'hash': 'hmset'
};

_.extend(RedisDb.prototype, Db.prototype, {
  createClient: function() {
    if (this.redis) {
      return redis.createClient(this.redis.port, this.redis.host);
    }
  },
  _getLoadFn: function(model, options) {
    var type = model.redis_type || 'string';
    return loadFnMap[type.toLowerCase()] || 'get';
  },
  _getSaveFn: function(model, options) {
    var type = model.redis_type || 'string';
    return saveFnMap[type.toLowerCase()] || 'set';
  },
  _getSaveArgs: function(model, options, fn) {
    var args = [this.getIdKey(model, options)];
    options = options || {};
    if (fn === 'hmset') {
      var data = model.toJSON();
      var out = {};
      Object.keys(data).forEach(function(attr) {
        out[attr] = JSON.stringify(data[attr]);
      });
      args.push(out);
    } else if (fn === 'set') {
      args.push(JSON.stringify(model));
    }
    return args;
  },
  _getLoadArgs: function(model, options) {
    var args = [this.getIdKey(model, options)];
    options = options || {};
    return args;
  },
  getFetchCommand: function(model, options) {
    var fn = this._getLoadFn(model);
    var res = {};
    res.args = this._getLoadArgs(model, options, fn);
    res.fn = fn;
    return res;
  },
  getSaveCommand: function(model, options) {
    var fn = this._getSaveFn(model);
    var res = {};
    res.args = this._getSaveArgs(model, options, fn);
    res.fn = fn;
    return res;
  },
  // get key for set where ids are stored
  getIdKey: function(model, options) {
    var key = '';
    options = options || {};
    if (options.url) {
      key = typeof options.url === 'function' ? options.url() : options.url;
    } else if (model.url) {
      key = typeof model.url === 'function' ? model.url() : model.url;
    } else if (model.id) {
      key = model.id;
    }
    return this.name + (key ? ':' + key : '');
  },

  // get Redis key for set, where key/value is stored
  getValueSetKey: function(model, key, val) {
    var baseKey = model.dbBaseKey || model.type;
    return this.name + ':i:' + baseKey + ':' + key + ':' + val;
  },

  // get Redis key for set for sorted property
  getSortSetKey: function(model, sortProp) {
    var baseKey = model.dbBaseKey || model.type;
    return this.name + ':i:' + baseKey + ':' + sortProp;
  },

  findAll: function(model, options, callback) {
    options = options || {};
    var collectionKey = this.getIdKey(model, options);
    var modelKey;
    var dbOpts;

    debug('findAll ' + collectionKey);
    // if Collection
    if (model.model) {
      var m = new model.model();
      modelKey = this.getIdKey(m, {});
      dbOpts = {
        db: this,
        model: model,
        ModelClass: model.model,
        modelKey: modelKey,
        collectionKey: collectionKey,
        indexes: m.indexes,
        redis_type: m.redis_type
      };
      query.queryModels(options, dbOpts, callback);
    } else {
      // fetch a model with given attributes
      // check that initial attributes are indexed
      var indexedKeys = _.chain(model.indexes)
        .where({unique: true})
        .pluck('property')
        .value();
      var objectKeys = Object.keys(model.attributes);
      var searchAttrs = {};
      var allIndexed = _.each(objectKeys, function(attr) {
        if (indexedKeys.indexOf(attr) > -1) {
          searchAttrs[attr] = model.get(attr);
        }
      });
      if (!Object.keys(searchAttrs).length) {
        var err = new Error('Cannot fetch model with given attributes');
        return callback(err);
      }
      options.where = searchAttrs;
      debug('fetch model', collectionKey, options.where);
      modelKey = this.getIdKey(model, {});
      dbOpts = {
        db: this,
        model: model,
        ModelClass: model.constructor,
        modelKey: modelKey,
        collectionKey: collectionKey,
        indexes: model.indexes,
        redis_type: model.redis_type
      };
      query.queryModels(options, dbOpts, function(err, results) {
        callback(err, results && results.length && results[0]);
      });
    }
  },

  find: function(model, options, callback) {
    var key = this.getIdKey(model, options);
    debug('find: ' + key);
    var cmd = this.getFetchCommand(model, options);
    cmd.args.push(function(err, data) {
      if (typeof data === 'string') {
        data = data && JSON.parse(data);
      } else if (data) {
        _.each(data, function(v, k) {
          data[k] = JSON.parse(v);
        });
      }
      callback(err, data);
    });
    this.redis[cmd.fn].apply(this.redis, cmd.args);
  },

  create: function(model, options, callback) {
    var self = this;
    var key = this.getIdKey(model, options);
    debug('create: ' + key);
    if (model.isNew()) {
      self.createId(model, options, function(err, id) {
        if (err || !id) {
          return callback(err || new Error('id is missing'));
        }
        model.set(model.idAttribute, id);
        self.update(model, options, callback);
      });
    } else {
      self.update(model, options, callback);
    }
  },

  createId: function(model, options, callback) {
    if (model.createId) return model.createId(callback);
    var key = this.getIdKey(model, options);
    key += ':ids';
    this.redis.incr(key, callback);
  },

  update: function(model, options, callback) {
    var key = this.getIdKey(model, options);
    var self = this;
    debug('update: ' + key);
    if (model.isNew()) {
      return this.create(model, options, callback);
    }
    var cmd = this.getSaveCommand(model, options);
    cmd.args.push(function(err, res) {
      if (model.collection) {
        var setKey = self.getIdKey(model.collection, {});
        var modelKey = model.get(model.idAttribute);
        debug('adding model ' + modelKey + ' to ' + setKey);
        self.redis.sadd(setKey, modelKey, function(err, res) {
          self._updateIndexes(model, options, callback);
        });
      } else {
        self._updateIndexes(model, options, callback);
      }
    });
    this.redis[cmd.fn].apply(this.redis, cmd.args);
  },

  destroy: function(model, options, callback) {
    // force wait option, since otherwise Backbone removes Model's reference to collection
    // which is required for clearing indexes
    options.wait = true;
    var self = this;
    var key = this.getIdKey(model, options);
    debug("DESTROY: " + key);
    if (model.isNew()) {
      return false;
    }

    function delKey() {
      debug('removing key: ' + key);
      self.redis.del(key, function(err, res) {
        callback(err, model.toJSON());
      });
    }

    this._updateIndexes(model, _.extend({
      operation: 'delete'
    }, options), function(err) {
      if (model.collection) {
        var setKey = self.getIdKey(model.collection, {});
        var modelKey = model.get(model.idAttribute);
        debug('removing model ' + modelKey + ' from ' + setKey);
        self.redis.srem(setKey, modelKey, function(err, res) {
          if (err) return callback(err);
          delKey();
        });
      } else {
        debug('model has no collection specified');
        delKey();
      }
    });
  },

  addToIndex: function(collection, model, options, cb) {
    var setKey = collection.indexKey;
    var key = model.id;
    debug('adding model ' + key + ' to ' + setKey);
    if (collection.indexSort) {
      this.redis.zadd(setKey, collection.indexSort(model), key, cb);
    } else {
      this.redis.sadd(setKey, key, function(err, res) {
        cb(err, res);
      });
    }
  },

  readFromIndex: function(collection, options, cb) {
    var self = this;
    var setKey = options.indexKey || collection.indexKey;
    var done = function(err, data) {
      var models = [];
      var i = 0;
      while (i < data.length) {
        var modelData = {
          id: data[i]
        };
        i++;
        if (options.score && options.score.conversion) {
          var score = options.score.conversion.fn(data[i]);
          modelData[options.score.conversion.attribute] = score;
          i++;
        }
        models.push(modelData);
      }
      collection.set(models, options);
      return cb(err, models);
    };

    var getReadFn = function() {
      if (collection.indexSort) {
        var min = '-inf';
        var max = '+inf';
        if (options.score) {
          min = options.score.min || min;
          max = options.score.max || max;
          var params = [setKey, max, min];
          if (options.score.conversion) params.push('WITHSCORES');
          if (options.limit || options.offset) {
            params = params.concat(['LIMIT', options.offset || 0, options.limit || -1]);
          }
          return _.bind.apply(null, [self.redis.zrevrangebyscore, self.redis].concat(params));
        } else {
          return _.bind(self.redis.zrevrange, self.redis, setKey, options.offset || 0, options.limit || -1);
        }
      }
      return _.bind(self.redis.smembers, self.redis, setKey);
    };

    var readFn = getReadFn();
    debug('reading keys from: ' + setKey);
    readFn(done);
  },

  /**
   * Read from multiple sets, storing union in new set temporarily
   */
  readFromIndexes: function(collection, options, cb) {
    var self = this;
    var unionKey = options.unionKey;
    var params = _.clone(options.indexKeys);
    if (collection.indexSort) params.unshift(options.indexKeys.length); // how many keys to union
    params.unshift(unionKey); // where to store

    var unionFn = collection.indexSort ?
      _.bind(this.redis.zunionstore, this.redis) :
      _.bind(this.redis.sunionstore, this.redis);
    unionFn(params, function(err) {
      self.redis.expire(unionKey, 300);
      options.indexKey = unionKey;
      return self.readFromIndex(collection, options, cb);
    });
  },

  removeFromIndex: function(collection, models, options, cb) {
    var setKey = collection.indexKey;
    var keys = _.pluck(models, models[0].idAttribute);
    var cmd = [setKey].concat(keys);
    debug('removing key: ' + keys + ' from: ' + setKey);
    if (collection.indexSort) {
      this.redis.zrem(cmd, cb);
    } else {
      this.redis.srem(cmd, cb);
    }
  },

  // removes the index completely
  removeIndex: function(collection, options, cb) {
    var setKey = collection.indexKey;
    this.redis.del(setKey, cb);
  },

  existsInIndex: function(collection, model, options, cb) {
    var setKey = collection.indexKey;
    var key = model.id;
    debug('check existance for: ' + key + ' in: ' + setKey);

    function done(err, rank) {
      cb(err, rank !== null);
    }

    if (collection.indexSort) {
      this.redis.zrank(setKey, key, done);
    } else {
      this.redis.sismember(setKey, key, function(err, isMember) {
        cb(err, isMember === 1);
      });
    }
  },

  indexCount: function(collection, options, cb) {
    var setKey = collection.indexKey;
    debug('get count for: ' + setKey);
    if (collection.indexSort) {
      this.redis.zcard(setKey, cb);
    } else {
      this.redis.scard(setKey, cb);
    }
  },

  findKeys: function(collection, options, cb) {
    var prefix = options.prefix || (this.getIdKey(collection, {}));
    if (prefix.indexOf(':', prefix.length - 1) === -1
      && options.keys.length) {
      prefix += ':';
    }
    this.redis.keys(prefix + options.keys + '*', cb);
  },

  _updateIndexes: function(model, options, callback) {
    if (!model.indexes) {
      debug('nothing to index');
      return callback(null, model.toJSON());
    }
    var operation = options.operation || 'add';
    var indexingOpts = {
      db: this,
      indexes: model.indexes,
      data: model.attributes,
      prevData: operation === 'delete' ? model.attributes : model.previousAttributes(),
      operation: operation,
      model: model,
      id: model.id
    };
    indexing.updateIndexes(indexingOpts, callback);
  }
});
RedisDb.sync = RedisDb.prototype.sync;
RedisDb.Hash = require('./lib/hash');
module.exports = RedisDb;<commit_msg>Fix key names when adapter name is empty<commit_after>var _ = require('lodash'),
  Backbone = require('backbone'),
  Db = require('backbone-db'),
  redis = require('redis'),
  debug = require('debug')('backbone-db-redis'),
  indexing = require('./lib/indexing'),
  query = require('./lib/query');

var RedisDb = Backbone.RedisDb = function(name, client) {
  this.name = name ||  '';
  this.redis = client;
  if (!this.redis) {
    this.redis = redis.createClient();
  }
};

Backbone.RedisDb.prototype.key = function(key) {
  if (this.name === '') {
    return key;
  } else {
    return this.name + ':' + key;
  }
};

var loadFnMap = {
  'string': 'get',
  'hash': 'hgetall'
};

var saveFnMap = {
  'string': 'set',
  'hash': 'hmset'
};

_.extend(RedisDb.prototype, Db.prototype, {
  createClient: function() {
    if (this.redis) {
      return redis.createClient(this.redis.port, this.redis.host);
    }
  },
  _getLoadFn: function(model, options) {
    var type = model.redis_type || 'string';
    return loadFnMap[type.toLowerCase()] || 'get';
  },
  _getSaveFn: function(model, options) {
    var type = model.redis_type || 'string';
    return saveFnMap[type.toLowerCase()] || 'set';
  },
  _getSaveArgs: function(model, options, fn) {
    var args = [this.getIdKey(model, options)];
    options = options || {};
    if (fn === 'hmset') {
      var data = model.toJSON();
      var out = {};
      Object.keys(data).forEach(function(attr) {
        out[attr] = JSON.stringify(data[attr]);
      });
      args.push(out);
    } else if (fn === 'set') {
      args.push(JSON.stringify(model));
    }
    return args;
  },
  _getLoadArgs: function(model, options) {
    var args = [this.getIdKey(model, options)];
    options = options || {};
    return args;
  },
  getFetchCommand: function(model, options) {
    var fn = this._getLoadFn(model);
    var res = {};
    res.args = this._getLoadArgs(model, options, fn);
    res.fn = fn;
    return res;
  },
  getSaveCommand: function(model, options) {
    var fn = this._getSaveFn(model);
    var res = {};
    res.args = this._getSaveArgs(model, options, fn);
    res.fn = fn;
    return res;
  },
  // get key for set where ids are stored
  getIdKey: function(model, options) {
    var key = '';
    options = options || {};
    if (options.url) {
      key = typeof options.url === 'function' ? options.url() : options.url;
    } else if (model.url) {
      key = typeof model.url === 'function' ? model.url() : model.url;
    } else if (model.id) {
      key = model.id;
    }
    if (this.name !== '') {
      return this.name + (key ? ':' + key : '');
    } else {
      return key;
    }

  },

  // get Redis key for set, where key/value is stored
  getValueSetKey: function(model, key, val) {
    var baseKey = model.dbBaseKey || model.type;
    var setKey = 'i:' + baseKey + ':' + key + ':' + val;
    console.log('getValueSetKey', setKey, this.name);
    if (this.name !== '') {
      return this.name + ':' + setKey;
    } else {
      return setKey;
    }
  },

  // get Redis key for set for sorted property
  getSortSetKey: function(model, sortProp) {
    var baseKey = model.dbBaseKey || model.type;
    var setKey = 'i:' + baseKey + ':' + sortProp;
    if (this.name !== '') {
      return this.name + ':' + setKey;
    } else {
      return setKey;
    }
  },

  findAll: function(model, options, callback) {
    options = options || {};
    var collectionKey = this.getIdKey(model, options);
    var modelKey;
    var dbOpts;

    debug('findAll ' + collectionKey);
    // if Collection
    if (model.model) {
      var m = new model.model();
      modelKey = this.getIdKey(m, {});
      dbOpts = {
        db: this,
        model: model,
        ModelClass: model.model,
        modelKey: modelKey,
        collectionKey: collectionKey,
        indexes: m.indexes,
        redis_type: m.redis_type
      };
      query.queryModels(options, dbOpts, callback);
    } else {
      // fetch a model with given attributes
      // check that initial attributes are indexed
      var indexedKeys = _.chain(model.indexes)
        .where({unique: true})
        .pluck('property')
        .value();
      var objectKeys = Object.keys(model.attributes);
      var searchAttrs = {};
      var allIndexed = _.each(objectKeys, function(attr) {
        if (indexedKeys.indexOf(attr) > -1) {
          searchAttrs[attr] = model.get(attr);
        }
      });
      if (!Object.keys(searchAttrs).length) {
        var err = new Error('Cannot fetch model with given attributes');
        return callback(err);
      }
      options.where = searchAttrs;
      debug('fetch model', collectionKey, options.where);
      modelKey = this.getIdKey(model, {});
      dbOpts = {
        db: this,
        model: model,
        ModelClass: model.constructor,
        modelKey: modelKey,
        collectionKey: collectionKey,
        indexes: model.indexes,
        redis_type: model.redis_type
      };
      query.queryModels(options, dbOpts, function(err, results) {
        callback(err, results && results.length && results[0]);
      });
    }
  },

  find: function(model, options, callback) {
    var key = this.getIdKey(model, options);
    debug('find: ' + key);
    var cmd = this.getFetchCommand(model, options);
    cmd.args.push(function(err, data) {
      if (typeof data === 'string') {
        data = data && JSON.parse(data);
      } else if (data) {
        _.each(data, function(v, k) {
          data[k] = JSON.parse(v);
        });
      }
      callback(err, data);
    });
    this.redis[cmd.fn].apply(this.redis, cmd.args);
  },

  create: function(model, options, callback) {
    var self = this;
    var key = this.getIdKey(model, options);
    debug('create: ' + key);
    if (model.isNew()) {
      self.createId(model, options, function(err, id) {
        if (err || !id) {
          return callback(err || new Error('id is missing'));
        }
        model.set(model.idAttribute, id);
        self.update(model, options, callback);
      });
    } else {
      self.update(model, options, callback);
    }
  },

  createId: function(model, options, callback) {
    if (model.createId) return model.createId(callback);
    var key = this.getIdKey(model, options);
    key += ':ids';
    this.redis.incr(key, callback);
  },

  update: function(model, options, callback) {
    var key = this.getIdKey(model, options);
    var self = this;
    debug('update: ' + key);
    if (model.isNew()) {
      return this.create(model, options, callback);
    }
    var cmd = this.getSaveCommand(model, options);
    cmd.args.push(function(err, res) {
      if (model.collection) {
        var setKey = self.getIdKey(model.collection, {});
        var modelKey = model.get(model.idAttribute);
        debug('adding model ' + modelKey + ' to ' + setKey);
        self.redis.sadd(setKey, modelKey, function(err, res) {
          self._updateIndexes(model, options, callback);
        });
      } else {
        self._updateIndexes(model, options, callback);
      }
    });
    this.redis[cmd.fn].apply(this.redis, cmd.args);
  },

  destroy: function(model, options, callback) {
    // force wait option, since otherwise Backbone removes Model's reference to collection
    // which is required for clearing indexes
    options.wait = true;
    var self = this;
    var key = this.getIdKey(model, options);
    debug("DESTROY: " + key);
    if (model.isNew()) {
      return false;
    }

    function delKey() {
      debug('removing key: ' + key);
      self.redis.del(key, function(err, res) {
        callback(err, model.toJSON());
      });
    }

    this._updateIndexes(model, _.extend({
      operation: 'delete'
    }, options), function(err) {
      if (model.collection) {
        var setKey = self.getIdKey(model.collection, {});
        var modelKey = model.get(model.idAttribute);
        debug('removing model ' + modelKey + ' from ' + setKey);
        self.redis.srem(setKey, modelKey, function(err, res) {
          if (err) return callback(err);
          delKey();
        });
      } else {
        debug('model has no collection specified');
        delKey();
      }
    });
  },

  addToIndex: function(collection, model, options, cb) {
    var setKey = collection.indexKey;
    var key = model.id;
    debug('adding model ' + key + ' to ' + setKey);
    if (collection.indexSort) {
      this.redis.zadd(setKey, collection.indexSort(model), key, cb);
    } else {
      this.redis.sadd(setKey, key, function(err, res) {
        cb(err, res);
      });
    }
  },

  readFromIndex: function(collection, options, cb) {
    var self = this;
    var setKey = options.indexKey || collection.indexKey;
    var done = function(err, data) {
      var models = [];
      var i = 0;
      while (i < data.length) {
        var modelData = {
          id: data[i]
        };
        i++;
        if (options.score && options.score.conversion) {
          var score = options.score.conversion.fn(data[i]);
          modelData[options.score.conversion.attribute] = score;
          i++;
        }
        models.push(modelData);
      }
      collection.set(models, options);
      return cb(err, models);
    };

    var getReadFn = function() {
      if (collection.indexSort) {
        var min = '-inf';
        var max = '+inf';
        if (options.score) {
          min = options.score.min || min;
          max = options.score.max || max;
          var params = [setKey, max, min];
          if (options.score.conversion) params.push('WITHSCORES');
          if (options.limit || options.offset) {
            params = params.concat(['LIMIT', options.offset || 0, options.limit || -1]);
          }
          return _.bind.apply(null, [self.redis.zrevrangebyscore, self.redis].concat(params));
        } else {
          return _.bind(self.redis.zrevrange, self.redis, setKey, options.offset || 0, options.limit || -1);
        }
      }
      return _.bind(self.redis.smembers, self.redis, setKey);
    };

    var readFn = getReadFn();
    debug('reading keys from: ' + setKey);
    readFn(done);
  },

  /**
   * Read from multiple sets, storing union in new set temporarily
   */
  readFromIndexes: function(collection, options, cb) {
    var self = this;
    var unionKey = options.unionKey;
    var params = _.clone(options.indexKeys);
    if (collection.indexSort) params.unshift(options.indexKeys.length); // how many keys to union
    params.unshift(unionKey); // where to store

    var unionFn = collection.indexSort ?
      _.bind(this.redis.zunionstore, this.redis) :
      _.bind(this.redis.sunionstore, this.redis);
    unionFn(params, function(err) {
      self.redis.expire(unionKey, 300);
      options.indexKey = unionKey;
      return self.readFromIndex(collection, options, cb);
    });
  },

  removeFromIndex: function(collection, models, options, cb) {
    var setKey = collection.indexKey;
    var keys = _.pluck(models, models[0].idAttribute);
    var cmd = [setKey].concat(keys);
    debug('removing key: ' + keys + ' from: ' + setKey);
    if (collection.indexSort) {
      this.redis.zrem(cmd, cb);
    } else {
      this.redis.srem(cmd, cb);
    }
  },

  // removes the index completely
  removeIndex: function(collection, options, cb) {
    var setKey = collection.indexKey;
    this.redis.del(setKey, cb);
  },

  existsInIndex: function(collection, model, options, cb) {
    var setKey = collection.indexKey;
    var key = model.id;
    debug('check existance for: ' + key + ' in: ' + setKey);

    function done(err, rank) {
      cb(err, rank !== null);
    }

    if (collection.indexSort) {
      this.redis.zrank(setKey, key, done);
    } else {
      this.redis.sismember(setKey, key, function(err, isMember) {
        cb(err, isMember === 1);
      });
    }
  },

  indexCount: function(collection, options, cb) {
    var setKey = collection.indexKey;
    debug('get count for: ' + setKey);
    if (collection.indexSort) {
      this.redis.zcard(setKey, cb);
    } else {
      this.redis.scard(setKey, cb);
    }
  },

  findKeys: function(collection, options, cb) {
    var prefix = options.prefix || (this.getIdKey(collection, {}));
    if (prefix.indexOf(':', prefix.length - 1) === -1
      && options.keys.length) {
      prefix += ':';
    }
    this.redis.keys(prefix + options.keys + '*', cb);
  },

  _updateIndexes: function(model, options, callback) {
    if (!model.indexes) {
      debug('nothing to index');
      return callback(null, model.toJSON());
    }
    var operation = options.operation || 'add';
    var indexingOpts = {
      db: this,
      indexes: model.indexes,
      data: model.attributes,
      prevData: operation === 'delete' ? model.attributes : model.previousAttributes(),
      operation: operation,
      model: model,
      id: model.id
    };
    indexing.updateIndexes(indexingOpts, callback);
  }
});
RedisDb.sync = RedisDb.prototype.sync;
RedisDb.Hash = require('./lib/hash');
module.exports = RedisDb;<|endoftext|>


<commit_before>// @flow

import {
  AsyncStorage,
  NativeModules,
  PermissionsAndroid,
  Platform,
} from "react-native";

const { RNPermissions } = NativeModules;

export const ANDROID_PERMISSIONS = {
  ...PermissionsAndroid.PERMISSIONS,
  // Dangerous permissions not included in PermissionsAndroid (which might be unavailable)
  ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER",
  // @TODO Add more of them (https://developer.android.com/reference/android/Manifest.permission)
};

// function keyMirror<O: {}>(obj: O): $ObjMapi<O, <K>(k: K) => K> {
//   return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: key }), {});
// }

export const IOS_PERMISSIONS = {
  BLUETOOTH_PERIPHERICAL: "BLUETOOTH_PERIPHERICAL",
  CALENDARS: "CALENDARS",
  CAMERA: "CAMERA",
  CONTACTS: "CONTACTS",
  FACE_ID: "FACE_ID",
  LOCATION_ALWAYS: "LOCATION_ALWAYS",
  LOCATION_WHEN_IN_USE: "LOCATION_WHEN_IN_USE",
  MEDIA_LIBRARY: "MEDIA_LIBRARY",
  MICROPHONE: "MICROPHONE",
  MOTION: "MOTION",
  NOTIFICATIONS: "NOTIFICATIONS",
  PHOTO_LIBRARY: "PHOTO_LIBRARY",
  REMINDERS: "REMINDERS",
  SIRI: "SIRI",
  SPEECH_RECOGNITION: "SPEECH_RECOGNITION",
  STOREKIT: "STOREKIT",
};

export const RESULTS = {
  GRANTED: "granted",
  DENIED: "denied",
  NEVER_ASK_AGAIN: "never_ask_again",
  UNAVAILABLE: "unavailable",
};

export type Permission =
  | $Keys<typeof ANDROID_PERMISSIONS>
  | $Keys<typeof IOS_PERMISSIONS>;

export type PermissionStatus = $Values<typeof RESULTS>;

export type Rationale = {|
  title: string,
  message: string,
  buttonPositive: string,
  buttonNegative?: string,
  buttonNeutral?: string,
|};

export type NotificationOption =
  | "badge"
  | "sound"
  | "alert"
  | "carPlay"
  | "criticalAlert"
  | "provisional";

export type RequestConfig = {|
  notificationOptions?: NotificationOption[],
  rationale?: Rationale,
|};

const requestedKey = "@RNPermissions:requested";

const platformPermissions = Object.values(
  Platform.OS === "android" ? ANDROID_PERMISSIONS : IOS_PERMISSIONS,
);

function assertValidPermission(permission: string) {
  if (platformPermissions.includes(permission)) {
    return;
  }

  const bulletsList = `• ${platformPermissions.join("\n• ")}`;
  const alertSentence = `Invalid ${
    Platform.OS
  } permission "${permission}". Must be one of:\n\n`;

  throw new Error(`${alertSentence}${bulletsList}`);
}

async function getRequestedPermissions() {
  const requested = await AsyncStorage.getItem(requestedKey);
  return requested ? JSON.parse(requested) : [];
}

async function setRequestedPermission(permission: string) {
  const requested = await getRequestedPermissions();

  if (requested.includes(permission)) {
    return;
  }

  return await AsyncStorage.setItem(
    requestedKey,
    JSON.stringify([...requested, permission]),
  );
}

async function internalCheck(
  permission: Permission,
): Promise<PermissionStatus> {
  if (Platform.OS !== "android") {
    return RNPermissions.check(permission);
  }

  if (!(await RNPermissions.isPermissionAvailable(permission))) {
    return RESULTS.UNAVAILABLE;
  }
  if (await PermissionsAndroid.check(permission)) {
    return RESULTS.GRANTED;
  }
  if (!(await getRequestedPermissions()).includes(permission)) {
    return RESULTS.DENIED;
  }

  return (await NativeModules.PermissionsAndroid.shouldShowRequestPermissionRationale(
    permission,
  ))
    ? RESULTS.DENIED
    : RESULTS.NEVER_ASK_AGAIN;
}

async function internalRequest(
  permission: string,
  config: RequestConfig,
): Promise<PermissionStatus> {
  const { notificationOptions, rationale } = config;

  if (Platform.OS !== "android") {
    return RNPermissions.request(permission, { notificationOptions });
  }
  if (!(await RNPermissions.isPermissionAvailable(permission))) {
    return RESULTS.UNAVAILABLE;
  }

  const result = await PermissionsAndroid.request(permission, rationale);
  await setRequestedPermission(permission);

  return result;
}

export function openSettings(): Promise<boolean> {
  return RNPermissions.openSettings();
}

export function check(permission: Permission): Promise<PermissionStatus> {
  assertValidPermission(permission);
  return internalCheck(permission);
}

export function request(
  permission: string,
  config: RequestConfig = {},
): Promise<PermissionStatus> {
  assertValidPermission(permission);
  return internalRequest(permission, config);
}

export function requestMultiple(
  permissions: Permission[],
): Promise<{ [permission: Permission]: PermissionStatus }> {
  permissions.forEach(permission => {
    assertValidPermission(permission);
  });

  return (async () => {
    const result = {};

    if (Platform.OS !== "android") {
      for (let index = 0; index < permissions.length; index++) {
        const permission = permissions[index];
        result[permission] = await request(permission);
      }

      return result;
    }

    const available = [];

    for (let index = 0; index < permissions.length; index++) {
      const permission = permissions[index];

      // @TODO Do checks in parallel to improve performances
      if (await RNPermissions.isPermissionAvailable(permission)) {
        available.push(permission);
      } else {
        result[permission] = RESULTS.UNAVAILABLE;
      }
    }

    return PermissionsAndroid.requestMultiple(available).then(statuses => ({
      ...result,
      ...statuses,
    }));
  })();
}
<commit_msg>Add checkMultiple, improve parallelism<commit_after>// @flow

import {
  AsyncStorage,
  NativeModules,
  PermissionsAndroid,
  Platform,
} from "react-native";

const { RNPermissions } = NativeModules;

export const ANDROID_PERMISSIONS = {
  ...PermissionsAndroid.PERMISSIONS,
  // Dangerous permissions not included in PermissionsAndroid (which might be unavailable)
  ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER",
  // @TODO Add more of them (https://developer.android.com/reference/android/Manifest.permission)
};

// function keyMirror<O: {}>(obj: O): $ObjMapi<O, <K>(k: K) => K> {
//   return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: key }), {});
// }

export const IOS_PERMISSIONS = {
  BLUETOOTH_PERIPHERICAL: "BLUETOOTH_PERIPHERICAL",
  CALENDARS: "CALENDARS",
  CAMERA: "CAMERA",
  CONTACTS: "CONTACTS",
  FACE_ID: "FACE_ID",
  LOCATION_ALWAYS: "LOCATION_ALWAYS",
  LOCATION_WHEN_IN_USE: "LOCATION_WHEN_IN_USE",
  MEDIA_LIBRARY: "MEDIA_LIBRARY",
  MICROPHONE: "MICROPHONE",
  MOTION: "MOTION",
  NOTIFICATIONS: "NOTIFICATIONS",
  PHOTO_LIBRARY: "PHOTO_LIBRARY",
  REMINDERS: "REMINDERS",
  SIRI: "SIRI",
  SPEECH_RECOGNITION: "SPEECH_RECOGNITION",
  STOREKIT: "STOREKIT",
};

export const RESULTS = {
  GRANTED: "granted",
  DENIED: "denied",
  NEVER_ASK_AGAIN: "never_ask_again",
  UNAVAILABLE: "unavailable",
};

export type Permission =
  | $Keys<typeof ANDROID_PERMISSIONS>
  | $Keys<typeof IOS_PERMISSIONS>;

export type PermissionStatus = $Values<typeof RESULTS>;

export type Rationale = {|
  title: string,
  message: string,
  buttonPositive: string,
  buttonNegative?: string,
  buttonNeutral?: string,
|};

export type NotificationOption =
  | "badge"
  | "sound"
  | "alert"
  | "carPlay"
  | "criticalAlert"
  | "provisional";

export type RequestConfig = {|
  notificationOptions?: NotificationOption[],
  rationale?: Rationale,
|};

const platformPermissions = Object.values(
  Platform.OS === "ios" ? IOS_PERMISSIONS : ANDROID_PERMISSIONS,
);

function assertValidPermission(permission: string) {
  if (!platformPermissions.includes(permission)) {
    const bulletsList = `• ${platformPermissions.join("\n• ")}`;
    const alertSentence = `Invalid ${
      Platform.OS
    } permission "${permission}". Must be one of:\n\n`;

    throw new Error(`${alertSentence}${bulletsList}`);
  }
}

function getUnavailablePermissions(permissions: string[]) {
  return Promise.all(
    permissions.map(p => RNPermissions.isPermissionAvailable(p)),
  ).then(result =>
    result.reduce(
      (acc, isAvailable, i) =>
        isAvailable ? acc : { ...acc, [permissions[i]]: RESULTS.UNAVAILABLE },
      {},
    ),
  );
}

const requestedKey = "@RNPermissions:requested";

async function getRequestedPermissions() {
  const requested = await AsyncStorage.getItem(requestedKey);

  return requested ? JSON.parse(requested) : [];
}

async function setRequestedPermissions(permissions: string[]) {
  const requested = await getRequestedPermissions();
  const dedup = [...new Set([...requested, ...permissions])];

  return AsyncStorage.setItem(requestedKey, dedup);
}

async function internalCheck(
  permission: Permission,
): Promise<PermissionStatus> {
  if (Platform.OS !== "android") {
    return RNPermissions.check(permission);
  }

  if (!(await RNPermissions.isPermissionAvailable(permission))) {
    return RESULTS.UNAVAILABLE;
  }
  if (await PermissionsAndroid.check(permission)) {
    return RESULTS.GRANTED;
  }
  if (!(await getRequestedPermissions()).includes(permission)) {
    return RESULTS.DENIED;
  }

  return (await NativeModules.PermissionsAndroid.shouldShowRequestPermissionRationale(
    permission,
  ))
    ? RESULTS.DENIED
    : RESULTS.NEVER_ASK_AGAIN;
}

async function internalRequest(
  permission: string,
  config: RequestConfig,
): Promise<PermissionStatus> {
  const { notificationOptions, rationale } = config;

  if (Platform.OS !== "android") {
    return RNPermissions.request(permission, { notificationOptions });
  }
  if (!(await RNPermissions.isPermissionAvailable(permission))) {
    return RESULTS.UNAVAILABLE;
  }

  const status = await PermissionsAndroid.request(permission, rationale);

  // set permission as requested
  await setRequestedPermissions([permission]);
  return status;
}

async function internalCheckMultiple(
  permissions: Permission[],
): Promise<{ [permission: Permission]: PermissionStatus }> {
  const result = await getUnavailablePermissions(permissions);
  const unavailable = Object.keys(result);
  const available = permissions.filter(p => !unavailable.includes(p));

  return Promise.all(available.map(p => internalCheck(p)))
    .then(statuses =>
      statuses.reduce((acc, s, i) => ({ ...acc, [available[i]]: s }), {}),
    )
    .then(statuses => ({ ...result, ...statuses }));
}

async function internalRequestMultiple(
  permissions: Permission[],
): Promise<{ [permission: Permission]: PermissionStatus }> {
  if (Platform.OS !== "android") {
    const result = {};

    for (let i = 0; i < permissions.length; i++) {
      const permission = permissions[i];
      // avoid checking them all at once
      result[permission] = await internalRequest(permission);
    }

    return result;
  }

  const result = await getUnavailablePermissions(permissions);
  const unavailable = Object.keys(result);

  const statuses = await PermissionsAndroid.requestMultiple(
    permissions.filter(p => !unavailable.includes(p)),
  ).then(statuses => ({ ...result, ...statuses }));

  // set permissions as requested
  await setRequestedPermissions(permissions);
  return status;
}

export function openSettings(): Promise<boolean> {
  return RNPermissions.openSettings();
}

export function check(permission: Permission): Promise<PermissionStatus> {
  assertValidPermission(permission);
  return internalCheck(permission);
}

export function checkMultiple(
  permissions: Permission[],
): Promise<{ [permission: Permission]: PermissionStatus }> {
  permissions.forEach(assertValidPermission);
  return internalCheckMultiple(permissions);
}

export function request(
  permission: string,
  config: RequestConfig = {},
): Promise<PermissionStatus> {
  assertValidPermission(permission);
  return internalRequest(permission, config);
}

export function requestMultiple(
  permissions: Permission[],
): Promise<{ [permission: Permission]: PermissionStatus }> {
  permissions.forEach(assertValidPermission);
  return internalRequestMultiple(permissions);
}
<|endoftext|>


<commit_before>'use strict';

var retry = require('retry');
var deepExtend = require('deep-extend');

var errorCodes = [
    'EADDRINFO',
    'ETIMEDOUT',
    'ECONNRESET'
];

function requestReplay(request, options) {
    var originalEmit = request.emit;
    var operation;
    var attempts = 0;

    options = deepExtend({
        errorCodes: errorCodes,
        retries: 5,
        factor: 3,
        minTimeout: 2000,
        maxTimeout: 35000,
        randomize: true
    }, options || {});

    options.errorCodes = options.errorCodes || errorCodes;

    operation = retry.operation(options);
    operation.attempt(function () {
        if (attempts) {
            request.start();
        }

        attempts++;
    });

    request.emit = function (name, error) {
        // If not an error, pass-through
        if (name !== 'error') {
            return originalEmit.apply(this, arguments);
        }

        // If not a retry error code, pass-through
        if (options.errorCodes.indexOf(error.code) === -1) {
            return originalEmit.call(this, name, error);
        }

        // Retry
        if (operation.retry(error)) {
            this.emit('replay', attempts - 1, error);
            return 0;
        }

        // No more retries available, error out
        error.replays = attempts - 1;
        return originalEmit.call(this, name, error);
    };

    return request;
}

module.exports = requestReplay;
<commit_msg>Increase max listeners to prevent nodes warn.<commit_after>'use strict';

var retry = require('retry');
var deepExtend = require('deep-extend');

var errorCodes = [
    'EADDRINFO',
    'ETIMEDOUT',
    'ECONNRESET'
];

function requestReplay(request, options) {
    var originalEmit = request.emit;
    var operation;
    var attempts = 0;

    // Default options
    options = deepExtend({
        errorCodes: errorCodes,
        retries: 5,
        factor: 3,
        minTimeout: 2000,
        maxTimeout: 35000,
        randomize: true
    }, options || {});

    // Init retry
    operation = retry.operation(options);
    operation.attempt(function () {
        if (attempts) {
            request.start();
        }

        attempts++;
    });

    // Increase maxListeners because start() adds a new listener each time
    request._maxListeners += options.retries + 1;

    // Monkey patch emit to catch errors and retry
    request.emit = function (name, error) {
        // If not an error, pass-through
        if (name !== 'error') {
            return originalEmit.apply(this, arguments);
        }

        // If not a retry error code, pass-through
        if (options.errorCodes.indexOf(error.code) === -1) {
            return originalEmit.call(this, name, error);
        }

        // Retry
        if (operation.retry(error)) {
            this.emit('replay', attempts - 1, error);
            return 0;
        }

        // No more retries available, error out
        error.replays = attempts - 1;
        return originalEmit.call(this, name, error);
    };

    return request;
}

module.exports = requestReplay;
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var net = require('net');
var fs = require('fs');
var util = require('util');

util.inherits(MaxCube, EventEmitter);

function padLeft(nr, n, str){
  return Array(n-String(nr).length+1).join(str||'0')+nr;
}

function MaxCube(ip, port, heartbeatInterval) {
  this.ip = ip;
  this.port = port;
  this.interval = heartbeatInterval || 20000;
  this.connectionState = 'disconnected';
  this.busy = false;
  this.callback = null;
  this.dutyCycle = 0;
  this.memorySlots = 0;

  this.rooms = [];
  this.devices = {};
  this.deviceCount = 0;
  this.client = new net.Socket();
  var self = this;
  this.client.on('error', function(err){
    self.client.end();
    self.connectionState = 'disconnected';
    self.emit('error', err);
  });

  this.client.on('data', this.onData.bind(this));

  this.connect();
}

MaxCube.prototype.connect = function () {
  if (this.connectionState === 'disconnected') {
    this.connectionState = "connecting";
    this.client.connect(this.port, this.ip, function() {
      this.connectionState = 'connected';
      this.emit('connected');
    }.bind(this));
  } else if(this.connectionState === 'connected'){
    this.send('l:\r\n', function(err){
      if(err) {
        this.client.emit('error', err);
      }
    });
  }

  setTimeout(this.connect.bind(this), this.interval);
};

MaxCube.prototype.send = function (message, callback) {
  if (!this.busy) {
    //console.log('Sending command: ' + message.substr(0,1));
    this.busy = true;
    this.client.write(message, 'utf-8', callback);
  }
};

MaxCube.prototype.onData = function (data) {
  data = data.toString('utf-8');
  data = data.split('\r\n');
  data.forEach(function (line) {
    if (line.length > 0) {
      var commandType = line.substr(0, 1);
      var payload = line.substring(2);
      //console.log('Data received: ' + commandType);
      var dataObj = this.parseCommand(commandType, payload);
      //console.log(dataObj);
    }
  }.bind(this));
  this.busy = false;
  this.emit('update', this.devices);
};

MaxCube.prototype.getDeviceType = function (deviceId) {
  var type = null;
  switch (deviceId) {
    case 0:
      type = 'Cube';
      break;
    case 1:
      type = 'Heating Thermostat';
      break;
    case 2:
      type = 'Heating Thermostat Plus';
      break;
    case 3:
      type = 'Wall mounted Thermostat';
      break;
    case 4:
      type = 'Shutter Contact';
      break;
    case 5:
      type = 'Push Button';
      break;
    default:
      type = 'unknown';
      break;
  }
  return type;
};

MaxCube.prototype.parseCommand = function (type, payload) {
  var data = null;
  switch (type) {
    case 'H':
      data = this.parseCommandHello(payload);
      break;
    case 'M':
      data = this.parseCommandMetadata(payload);
      break;
    case 'C':
      data = this.parseCommandDeviceConfiguration(payload);
      break;
    case 'L':
      data = this.parseCommandDeviceList(payload);
      break;
    case 'S':
      data = this.parseCommandSendDevice(payload);
      break;
    default:
      console.log('Unknown command type: ' + type);
      break;
  }
  return data;
};

MaxCube.prototype.parseCommandHello = function (payload) {
  var payloadArr = payload.split(",");

  var dataObj = {
    serial: payloadArr[0],
    address: payloadArr[1],
    firmware: payloadArr[2],
    connectionId: payloadArr[4],
    dutyCycle: parseInt(payloadArr[5], 16),
    freeMemorySlots: parseInt(payloadArr[6], 16),
    date: 2000 + parseInt(payloadArr[7].substr(0, 2), 16) + '-' + parseInt(payloadArr[7].substr(2, 2), 16) + '-' + parseInt(payloadArr[7].substr(4, 2), 16),
    time: parseInt(payloadArr[8].substr(0, 2), 16) + ':' + parseInt(payloadArr[8].substr(2, 2), 16) ,
    stateTime: payloadArr[9],
    ntpCounter: payloadArr[10],
  };
  this.dutyCycle = dataObj.dutyCycle;
  this.memorySlots = dataObj.freeMemorySlots;
  this.emit('status', {
    dutyCycle: this.dutyCycle,
    memorySlots: this.memorySlots
  });
  return dataObj;
};

MaxCube.prototype.parseCommandDeviceConfiguration = function (payload) {
  var payloadArr = payload.split(",");
  var decodedPayload = new Buffer(payloadArr[1], 'base64');

  var address = decodedPayload.slice(1, 4).toString('hex');
  var devicetype = this.getDeviceType(parseInt(decodedPayload[4].toString(10)));
  if ( (devicetype == 'Heating Thermostat' || devicetype == 'Heating Thermostat Plus') && this.devices[address]) {
    this.devices[address].comfortTemperature = parseInt(decodedPayload[18].toString(10)) / 2;
    this.devices[address].ecoTemperature = parseInt(decodedPayload[19].toString(10)) / 2;
    this.devices[address].maxTemperature = parseInt(decodedPayload[20].toString(10)) / 2;
    this.devices[address].minTemperature = parseInt(decodedPayload[21].toString(10)) / 2;
    this.devices[address].temperatureOffset = parseInt(decodedPayload[22].toString(10)) / 2;
    this.devices[address].windowOpenTemperature = parseInt(decodedPayload[23].toString(10)) / 2;
    return this.devices[address];
  }
  return null;
};

MaxCube.prototype.parseCommandMetadata = function (payload) {
  var payloadArr = payload.split(",");

  var decodedPayload = new Buffer(payloadArr[2], 'base64');
  var room_count = parseInt(decodedPayload[2].toString(10));
  var currentIndex = 3;

  // parse rooms
  for (var i = 0; i < room_count; i++) {
    var roomData = {};
    roomData.roomId = parseInt(decodedPayload[currentIndex].toString(10));
    var room_length = parseInt(decodedPayload[currentIndex + 1].toString(10));
    roomData.name = decodedPayload.slice(currentIndex + 2, currentIndex + 2 + room_length).toString('utf-8');
    roomData.groupAddress = decodedPayload.slice(currentIndex + 2 + room_length, currentIndex + room_length + 5).toString('hex');
    this.rooms.push(roomData);
    currentIndex = currentIndex + room_length + 5;
  }

  if (currentIndex < decodedPayload.length) {
    this.deviceCount = parseInt(decodedPayload[currentIndex].toString(10));

    for (var j = 0; j < this.deviceCount; j++) {
      var deviceData = {};
      deviceData.type = this.getDeviceType(parseInt(decodedPayload[currentIndex + 1].toString(10)));
      deviceData.address = decodedPayload.slice(currentIndex + 2, currentIndex + 5).toString('hex');
      deviceData.serial = decodedPayload.slice(currentIndex + 5, currentIndex + 13).toString();
      device_length = parseInt(decodedPayload[currentIndex + 15].toString(10));
      deviceData.name = decodedPayload.slice(currentIndex + 16, currentIndex + 16 + device_length).toString('utf-8');
      deviceData.roomId = parseInt(decodedPayload[currentIndex + 16 + device_length].toString(10));
      this.devices[deviceData.address] = deviceData;
      currentIndex = currentIndex + 16 + device_length;
    }
  }
  return {
    rooms: this.rooms,
    devices: this.devices
  };
};

MaxCube.prototype.parseCommandDeviceList = function (payload) {
  var decodedPayload = new Buffer(payload, 'base64');
  var currentIndex = 1;
  var actualTemp = 0;
  for(var i = 1; i <= this.deviceCount; i++) {
    var data = '';
    var length = parseInt(decodedPayload[currentIndex - 1].toString());
    var address = decodedPayload.slice(currentIndex, currentIndex + 3).toString('hex');
    if (this.devices[address] && (this.devices[address].type == 'Heating Thermostat' || this.devices[address].type == 'Heating Thermostat Plus') ) {
      this.devices[address].valve = decodedPayload[currentIndex + 6];
      this.devices[address].setpoint = parseInt(decodedPayload[currentIndex + 7].toString(10)) / 2;
      /* byte 5 from http://www.domoticaforum.eu/viewtopic.php?f=66&t=6654
5          1  12          bit 4     Valid              0=invalid;1=information provided is valid
                          bit 3     Error              0=no; 1=Error occurred
                          bit 2     Answer             0=an answer to a command,1=not an answer to a command
                          bit 1     Status initialized 0=not initialized, 1=yes

                          12  = 00010010b
                              = Valid, Initialized
*/
      this.devices[address].initialized = !!(decodedPayload[currentIndex + 4] & (1 << 1));
      this.devices[address].fromCmd = !!(decodedPayload[currentIndex + 4] & (1 << 2));
      this.devices[address].error = !!(decodedPayload[currentIndex + 4] & (1 << 3));
      this.devices[address].valid = !!(decodedPayload[currentIndex + 4] & (1 << 4));
      this.devices[address].dstActive = !!(decodedPayload[currentIndex + 5] & 8);
      this.devices[address].gatewayKnown = !!(decodedPayload[currentIndex + 5] & 16);
      this.devices[address].panelLocked = !!(decodedPayload[currentIndex + 5] & 32);
      this.devices[address].linkError = !!(decodedPayload[currentIndex + 5] & 64);

      data = padLeft(decodedPayload[currentIndex + 5].toString(2), 8);
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
      switch (data.substr(6, 2)) {
        case '00': mode = "auto"; break;
        case '01': mode = "manu"; break;
        case '10': mode = "vacation"; break;
        case '11': mode = "boost"; break;
      }
      if(typeof mode == "string") {
        this.devices[address].mode = mode;
      }

      if(decodedPayload[currentIndex + 8] !== 0 || decodedPayload[currentIndex + 9] !== 0) {
        actualTemp = (decodedPayload[currentIndex + 8] * 256 + decodedPayload[currentIndex + 9]) / 10;
      } else {
        actualTemp = undefined;
      }
      this.devices[address].actualTemperature = actualTemp;

    } else if (this.devices[address] && this.devices[address].type === 'Wall mounted Thermostat') {
      actualTemp = (decodedPayload[currentIndex + 11] + (decodedPayload[currentIndex + 7] & 0x80) * 2) / 10;
      this.devices[address].actualTemperature = actualTemp;
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
    } else if (this.devices[address] && this.devices[address].type === 'Shutter Contact') {
      data = padLeft(decodedPayload[currentIndex + 5].toString(2), 8);
      this.devices[address].state = parseInt(data.substr(6, 1)) ? 'open' : 'closed';
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
    }
    currentIndex = currentIndex + length + 1;
  }
  return this.devices;
};

MaxCube.prototype.parseCommandSendDevice = function (payload) {
  var payloadArr = payload.split(",");

  var dataObj = {
    accepted: payloadArr[1] == '0',
    duty_cycle: parseInt(payloadArr[0], 16),
    free_memory_slots: parseInt(payloadArr[2], 16)
  };
  this.dutyCycle = dataObj.duty_cycle;
  this.memorySlots = dataObj.free_memory_slots;
  this.emit('status', {
    dutyCycle: this.dutyCycle,
    memorySlots: this.memorySlots
  });
  this.emit('response', dataObj);
  return dataObj;
};

MaxCube.prototype.allWindowsClosed = function(roomId) {
  var self = this;
  if(!self.devices) {
    return;
  }
  // check if a window is open
  var isWindowOpen = false;
  Object.keys(self.devices).forEach(function(key) {
    var otherDevice = self.devices[key];
    if(otherDevice.type === 'Shutter Contact' && otherDevice.roomId === roomId && otherDevice.state !== 'closed') {
      isWindowOpen = true;
    }
  });
  return !isWindowOpen;
};

MaxCube.prototype.setTemperature = function (rfAdress, mode, temperature, callback) {
  var reqTempHex, reqTempBinary, reqRoomHex;
  var self = this;
  if (this.connectionState !== 'connected') {
    callback(new Error("Not connected"));
    return;
  }

  var date_until = '0000';
  var time_until = '00';

  // 00 = Auto weekprog (no temp is needed, just make the whole byte 00)
  // 01 = Permanent
  // 10 = Temporarily
  var modeBin;
  switch (mode) {
    case 'auto':
      modeBin = '00';
      break;
    case 'manu':
      modeBin = '01';
      break;
    case 'boost':
      modeBin = '11';
      break;
    default:
      callback(new Error('Unknown mode: ' + mode));
      return false;
  }

  var device = this.devices[rfAdress];
  if(!device) {
    callback(new Error("Could not find a device with this rfAdress!"));
    return;
  }
  var roomId = device.roomId;

  reqRoomHex = padLeft(roomId.toString(16), 2);

  if(mode == 'auto' && (typeof temperature === "undefined" || temperature === null)) {
    reqTempHex = '00';
  } else {
    reqTempBinary = modeBin + ("000000" + (temperature * 2).toString(2)).substr(-6);
    reqTempHex = parseInt(reqTempBinary, 2).toString(16);
  }

  var payload = new Buffer('000440000000' + rfAdress + reqRoomHex + reqTempHex + date_until + time_until, 'hex').toString('base64');
  var data = 's:' + payload + '\r\n';

  this.send(data, function(err) {
      if(err) {
        this.connectionState = 'disconnected';
        if(callback) {
          callback(err);
          callback = null;
        } else {
          this.client.emit('error', err);
        }
      }
  });

  this.once('response', function(res) {
    if(!callback) {
      return;
    }
    if(res.accepted) {
      callback(null);
    } else {
      var reason = "";
      var reasonCode = "Unknown";
      if(res.free_memory_slots === 0) {
        reason = ": Too many commands send, the cube has no memoery slots left.";
        reasonCode = "NO_MEMORY";
      } else {
        var isWindowOpen = !self.allWindowsClosed(roomId);
        if(isWindowOpen) {
          reason = ": A window in the room is open";
          reasonCode = "WINDOW_OPEN";
        }
      }
      var err = new Error('Command was rejected' + reason);
      err.code = reasonCode;
      callback(err);
    }
    callback = null;
  });

};

MaxCube.prototype.sendResetError = function (rfAdress, callback) {
  var payload = new Buffer(rfAdress).toString('base64');
  var data = 'r:01,' + payload + '\r\n';
  this.send(data, callback);
};

module.exports = MaxCube;
<commit_msg>Fix a bug where no callback was called and so connection was hanging<commit_after>var EventEmitter = require('events').EventEmitter;
var net = require('net');
var fs = require('fs');
var util = require('util');

util.inherits(MaxCube, EventEmitter);

function padLeft(nr, n, str){
  return Array(n-String(nr).length+1).join(str||'0')+nr;
}

function MaxCube(ip, port, heartbeatInterval) {
  this.ip = ip;
  this.port = port;
  this.interval = heartbeatInterval || 20000;
  this.connectionState = 'disconnected';
  this.busy = false;
  this.callback = null;
  this.dutyCycle = 0;
  this.memorySlots = 0;

  this.rooms = [];
  this.devices = {};
  this.deviceCount = 0;
  this.client = new net.Socket();
  var self = this;
  this.client.on('error', function(err){
    self.client.end();
    self.connectionState = 'disconnected';
    self.emit('error', err);
  });

  this.client.on('data', this.onData.bind(this));

  this.connect();
}

MaxCube.prototype.connect = function () {
  if (this.connectionState === 'disconnected') {
    this.connectionState = "connecting";
    this.client.connect(this.port, this.ip, function() {
      this.connectionState = 'connected';
      this.emit('connected');
    }.bind(this));
  } else if(this.connectionState === 'connected'){
    this.send('l:\r\n', function(err){
      if(err) {
        this.client.emit('error', err);
      }
    });
  }

  setTimeout(this.connect.bind(this), this.interval);
};

MaxCube.prototype.send = function (message, callback) {
  if (!this.busy) {
    //console.log('Sending command: ' + message.substr(0,1));
    this.busy = true;
    this.client.write(message, 'utf-8', callback);
  } else {
    callback(new Error("The cube is busy"));
  }
};

MaxCube.prototype.onData = function (data) {
  this.busy = false;
  data = data.toString('utf-8');
  data = data.split('\r\n');
  data.forEach(function (line) {
    if (line.length > 0) {
      var commandType = line.substr(0, 1);
      var payload = line.substring(2);
      //console.log('Data received: ' + commandType);
      var dataObj = this.parseCommand(commandType, payload);
      //console.log(dataObj);
    }
  }.bind(this));
  this.emit('update', this.devices);
};

MaxCube.prototype.getDeviceType = function (deviceId) {
  var type = null;
  switch (deviceId) {
    case 0:
      type = 'Cube';
      break;
    case 1:
      type = 'Heating Thermostat';
      break;
    case 2:
      type = 'Heating Thermostat Plus';
      break;
    case 3:
      type = 'Wall mounted Thermostat';
      break;
    case 4:
      type = 'Shutter Contact';
      break;
    case 5:
      type = 'Push Button';
      break;
    default:
      type = 'unknown';
      break;
  }
  return type;
};

MaxCube.prototype.parseCommand = function (type, payload) {
  var data = null;
  switch (type) {
    case 'H':
      data = this.parseCommandHello(payload);
      break;
    case 'M':
      data = this.parseCommandMetadata(payload);
      break;
    case 'C':
      data = this.parseCommandDeviceConfiguration(payload);
      break;
    case 'L':
      data = this.parseCommandDeviceList(payload);
      break;
    case 'S':
      data = this.parseCommandSendDevice(payload);
      break;
    default:
      console.log('Unknown command type: ' + type);
      break;
  }
  return data;
};

MaxCube.prototype.parseCommandHello = function (payload) {
  var payloadArr = payload.split(",");

  var dataObj = {
    serial: payloadArr[0],
    address: payloadArr[1],
    firmware: payloadArr[2],
    connectionId: payloadArr[4],
    dutyCycle: parseInt(payloadArr[5], 16),
    freeMemorySlots: parseInt(payloadArr[6], 16),
    date: 2000 + parseInt(payloadArr[7].substr(0, 2), 16) + '-' + parseInt(payloadArr[7].substr(2, 2), 16) + '-' + parseInt(payloadArr[7].substr(4, 2), 16),
    time: parseInt(payloadArr[8].substr(0, 2), 16) + ':' + parseInt(payloadArr[8].substr(2, 2), 16) ,
    stateTime: payloadArr[9],
    ntpCounter: payloadArr[10],
  };
  this.dutyCycle = dataObj.dutyCycle;
  this.memorySlots = dataObj.freeMemorySlots;
  this.emit('status', {
    dutyCycle: this.dutyCycle,
    memorySlots: this.memorySlots
  });
  return dataObj;
};

MaxCube.prototype.parseCommandDeviceConfiguration = function (payload) {
  var payloadArr = payload.split(",");
  var decodedPayload = new Buffer(payloadArr[1], 'base64');

  var address = decodedPayload.slice(1, 4).toString('hex');
  var devicetype = this.getDeviceType(parseInt(decodedPayload[4].toString(10)));
  if ( (devicetype == 'Heating Thermostat' || devicetype == 'Heating Thermostat Plus') && this.devices[address]) {
    this.devices[address].comfortTemperature = parseInt(decodedPayload[18].toString(10)) / 2;
    this.devices[address].ecoTemperature = parseInt(decodedPayload[19].toString(10)) / 2;
    this.devices[address].maxTemperature = parseInt(decodedPayload[20].toString(10)) / 2;
    this.devices[address].minTemperature = parseInt(decodedPayload[21].toString(10)) / 2;
    this.devices[address].temperatureOffset = parseInt(decodedPayload[22].toString(10)) / 2;
    this.devices[address].windowOpenTemperature = parseInt(decodedPayload[23].toString(10)) / 2;
    return this.devices[address];
  }
  return null;
};

MaxCube.prototype.parseCommandMetadata = function (payload) {
  var payloadArr = payload.split(",");

  var decodedPayload = new Buffer(payloadArr[2], 'base64');
  var room_count = parseInt(decodedPayload[2].toString(10));
  var currentIndex = 3;

  // parse rooms
  for (var i = 0; i < room_count; i++) {
    var roomData = {};
    roomData.roomId = parseInt(decodedPayload[currentIndex].toString(10));
    var room_length = parseInt(decodedPayload[currentIndex + 1].toString(10));
    roomData.name = decodedPayload.slice(currentIndex + 2, currentIndex + 2 + room_length).toString('utf-8');
    roomData.groupAddress = decodedPayload.slice(currentIndex + 2 + room_length, currentIndex + room_length + 5).toString('hex');
    this.rooms.push(roomData);
    currentIndex = currentIndex + room_length + 5;
  }

  if (currentIndex < decodedPayload.length) {
    this.deviceCount = parseInt(decodedPayload[currentIndex].toString(10));

    for (var j = 0; j < this.deviceCount; j++) {
      var deviceData = {};
      deviceData.type = this.getDeviceType(parseInt(decodedPayload[currentIndex + 1].toString(10)));
      deviceData.address = decodedPayload.slice(currentIndex + 2, currentIndex + 5).toString('hex');
      deviceData.serial = decodedPayload.slice(currentIndex + 5, currentIndex + 13).toString();
      device_length = parseInt(decodedPayload[currentIndex + 15].toString(10));
      deviceData.name = decodedPayload.slice(currentIndex + 16, currentIndex + 16 + device_length).toString('utf-8');
      deviceData.roomId = parseInt(decodedPayload[currentIndex + 16 + device_length].toString(10));
      this.devices[deviceData.address] = deviceData;
      currentIndex = currentIndex + 16 + device_length;
    }
  }
  return {
    rooms: this.rooms,
    devices: this.devices
  };
};

MaxCube.prototype.parseCommandDeviceList = function (payload) {
  var decodedPayload = new Buffer(payload, 'base64');
  var currentIndex = 1;
  var actualTemp = 0;
  for(var i = 1; i <= this.deviceCount; i++) {
    var data = '';
    var length = parseInt(decodedPayload[currentIndex - 1].toString());
    var address = decodedPayload.slice(currentIndex, currentIndex + 3).toString('hex');
    if (this.devices[address] && (this.devices[address].type == 'Heating Thermostat' || this.devices[address].type == 'Heating Thermostat Plus') ) {
      this.devices[address].valve = decodedPayload[currentIndex + 6];
      this.devices[address].setpoint = parseInt(decodedPayload[currentIndex + 7].toString(10)) / 2;
      /* byte 5 from http://www.domoticaforum.eu/viewtopic.php?f=66&t=6654
5          1  12          bit 4     Valid              0=invalid;1=information provided is valid
                          bit 3     Error              0=no; 1=Error occurred
                          bit 2     Answer             0=an answer to a command,1=not an answer to a command
                          bit 1     Status initialized 0=not initialized, 1=yes

                          12  = 00010010b
                              = Valid, Initialized
*/
      this.devices[address].initialized = !!(decodedPayload[currentIndex + 4] & (1 << 1));
      this.devices[address].fromCmd = !!(decodedPayload[currentIndex + 4] & (1 << 2));
      this.devices[address].error = !!(decodedPayload[currentIndex + 4] & (1 << 3));
      this.devices[address].valid = !!(decodedPayload[currentIndex + 4] & (1 << 4));
      this.devices[address].dstActive = !!(decodedPayload[currentIndex + 5] & 8);
      this.devices[address].gatewayKnown = !!(decodedPayload[currentIndex + 5] & 16);
      this.devices[address].panelLocked = !!(decodedPayload[currentIndex + 5] & 32);
      this.devices[address].linkError = !!(decodedPayload[currentIndex + 5] & 64);

      data = padLeft(decodedPayload[currentIndex + 5].toString(2), 8);
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
      switch (data.substr(6, 2)) {
        case '00': mode = "auto"; break;
        case '01': mode = "manu"; break;
        case '10': mode = "vacation"; break;
        case '11': mode = "boost"; break;
      }
      if(typeof mode == "string") {
        this.devices[address].mode = mode;
      }

      if(decodedPayload[currentIndex + 8] !== 0 || decodedPayload[currentIndex + 9] !== 0) {
        actualTemp = (decodedPayload[currentIndex + 8] * 256 + decodedPayload[currentIndex + 9]) / 10;
      } else {
        actualTemp = undefined;
      }
      this.devices[address].actualTemperature = actualTemp;

    } else if (this.devices[address] && this.devices[address].type === 'Wall mounted Thermostat') {
      actualTemp = (decodedPayload[currentIndex + 11] + (decodedPayload[currentIndex + 7] & 0x80) * 2) / 10;
      this.devices[address].actualTemperature = actualTemp;
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
    } else if (this.devices[address] && this.devices[address].type === 'Shutter Contact') {
      data = padLeft(decodedPayload[currentIndex + 5].toString(2), 8);
      this.devices[address].state = parseInt(data.substr(6, 1)) ? 'open' : 'closed';
      this.devices[address].battery = parseInt(data.substr(0, 1)) ? 'low' : 'ok';
    }
    currentIndex = currentIndex + length + 1;
  }
  return this.devices;
};

MaxCube.prototype.parseCommandSendDevice = function (payload) {
  var payloadArr = payload.split(",");

  var dataObj = {
    accepted: payloadArr[1] == '0',
    duty_cycle: parseInt(payloadArr[0], 16),
    free_memory_slots: parseInt(payloadArr[2], 16)
  };
  this.dutyCycle = dataObj.duty_cycle;
  this.memorySlots = dataObj.free_memory_slots;
  this.emit('status', {
    dutyCycle: this.dutyCycle,
    memorySlots: this.memorySlots
  });
  this.emit('response', dataObj);
  return dataObj;
};

MaxCube.prototype.allWindowsClosed = function(roomId) {
  var self = this;
  if(!self.devices) {
    return;
  }
  // check if a window is open
  var isWindowOpen = false;
  Object.keys(self.devices).forEach(function(key) {
    var otherDevice = self.devices[key];
    if(otherDevice.type === 'Shutter Contact' && otherDevice.roomId === roomId && otherDevice.state !== 'closed') {
      isWindowOpen = true;
    }
  });
  return !isWindowOpen;
};

MaxCube.prototype.setTemperature = function (rfAdress, mode, temperature, callback) {
  var reqTempHex, reqTempBinary, reqRoomHex;
  var self = this;
  if (this.connectionState !== 'connected') {
    callback(new Error("Not connected"));
    return;
  }

  var date_until = '0000';
  var time_until = '00';

  // 00 = Auto weekprog (no temp is needed, just make the whole byte 00)
  // 01 = Permanent
  // 10 = Temporarily
  var modeBin;
  switch (mode) {
    case 'auto':
      modeBin = '00';
      break;
    case 'manu':
      modeBin = '01';
      break;
    case 'boost':
      modeBin = '11';
      break;
    default:
      callback(new Error('Unknown mode: ' + mode));
      return false;
  }

  var device = this.devices[rfAdress];
  if(!device) {
    callback(new Error("Could not find a device with this rfAdress!"));
    return;
  }
  var roomId = device.roomId;

  reqRoomHex = padLeft(roomId.toString(16), 2);

  if(mode == 'auto' && (typeof temperature === "undefined" || temperature === null)) {
    reqTempHex = '00';
  } else {
    reqTempBinary = modeBin + ("000000" + (temperature * 2).toString(2)).substr(-6);
    reqTempHex = parseInt(reqTempBinary, 2).toString(16);
  }

  var payload = new Buffer('000440000000' + rfAdress + reqRoomHex + reqTempHex + date_until + time_until, 'hex').toString('base64');
  var data = 's:' + payload + '\r\n';

  this.send(data, function(err) {
      if(err) {
        this.connectionState = 'disconnected';
        if(callback) {
          callback(err);
          callback = null;
        } else {
          this.client.emit('error', err);
        }
      } else {
        var timeoutTime = 10000;
        setTimeout(function(){
          if(!callback) {
            return;
          }
          callback(new Error("No awnser from cube after " + timeoutTime + "ms"));
          callback = null;
        }, timeoutTime)
      }
  });

  this.once('response', function(res) {
    if(!callback) {
      return;
    }
    if(res.accepted) {
      callback(null);
    } else {
      var reason = "";
      var reasonCode = "Unknown";
      if(res.free_memory_slots === 0) {
        reason = ": Too many commands send, the cube has no memoery slots left.";
        reasonCode = "NO_MEMORY";
      } else {
        var isWindowOpen = !self.allWindowsClosed(roomId);
        if(isWindowOpen) {
          reason = ": A window in the room is open";
          reasonCode = "WINDOW_OPEN";
        }
      }
      var err = new Error('Command was rejected' + reason);
      err.code = reasonCode;
      callback(err);
    }
    callback = null;
  });

};

MaxCube.prototype.sendResetError = function (rfAdress, callback) {
  var payload = new Buffer(rfAdress).toString('base64');
  var data = 'r:01,' + payload + '\r\n';
  this.send(data, callback);
};

module.exports = MaxCube;
<|endoftext|>


<commit_before>import React from 'react-native';
const {
  PixelRatio,
  StatusBar,
  Component,
  Text,
  View,
  PropTypes,
  Platform
} = React;
import NavbarButton from './NavbarButton';
import styles from './styles';

const ButtonShape = {
  title: PropTypes.string.isRequired,
  style: PropTypes.any,
  handler: PropTypes.func,
};

const TitleShape = {
  title: PropTypes.string.isRequired,
  tintColor: PropTypes.string,
};

const StatusBarShape = {
  style: PropTypes.oneOf(['light-content', 'default', ]),
  hidden: PropTypes.bool,
  tintColor: PropTypes.string,
  hideAnimation: PropTypes.oneOf(['fade', 'slide', 'none', ]),
  showAnimation: PropTypes.oneOf(['fade', 'slide', 'none', ])
};

function customizeStatusBar(data) {
  if (Platform.OS === 'ios') {
    if (data.style) {
      StatusBar.barStyle(data.style);
    }
    const animation = data.hidden ?
    (data.hideAnimation || NavigationBar.defaultProps.statusBar.hideAnimation) :
    (data.showAnimation || NavigationBar.defaultProps.statusBar.showAnimation);

    StatusBar.showHideTransition(animation);
    StatusBar.hidden = data.hidden;
  }
}

class NavigationBar extends Component {
  componentDidMount() {
    customizeStatusBar(this.props.statusBar);
  }

  componentWillReceiveProps(props) {
    customizeStatusBar(this.props.statusBar);
  }

  getButtonElement(data = {}, style) {
    if (!!data.props) {
      return <View style={styles.navBarButton}>{data}</View>;
    }

    return <NavbarButton
      title={data.title}
      style={[data.style, style, ]}
      tintColor={data.tintColor}
      handler={data.handler} />;
  }

  getTitleElement(data) {
    if (!!data.props) {
      return <View style={styles.customTitle}>{data}</View>;
    }

    const colorStyle = data.tintColor ? { color: data.tintColor, } : null;

    return (
      <Text
        style={[styles.navBarTitleText, colorStyle, ]}>
        {data.title}
      </Text>
    );
  }

  render() {
    const customTintColor = this.props.tintColor ?
      { backgroundColor: this.props.tintColor } : null;

    const customStatusBarTintColor = this.props.statusBar.tintColor ?
      { backgroundColor: this.props.statusBar.tintColor } : null;

    const statusBar = !this.props.statusBar.hidden ?
      <View style={[styles.statusBar, customStatusBarTintColor ]} /> : null;

    return (
      <View style={[styles.navBarContainer, customTintColor, ]}>
        {statusBar}
        <View style={[styles.navBar, this.props.style, ]}>
          {this.getTitleElement(this.props.title)}
          {this.getButtonElement(this.props.leftButton, { marginLeft: 8, })}
          {this.getButtonElement(this.props.rightButton, { marginRight: 8, })}
        </View>
      </View>
    );
  }

  static propTypes = {
    tintColor: PropTypes.string,
    statusBar: PropTypes.shape(StatusBarShape),
    leftButton: PropTypes.oneOfType([
      PropTypes.shape(ButtonShape),
      PropTypes.element,
    ]),
    rightButton: PropTypes.oneOfType([
      PropTypes.shape(ButtonShape),
      PropTypes.element,
    ]),
    title: PropTypes.oneOfType([
      PropTypes.shape(TitleShape),
      PropTypes.element,
    ]),
  };

  static defaultProps = {
    statusBar: {
      style: 'default',
      hidden: false,
      hideAnimation: 'slide',
      showAnimation: 'slide',
    },
    title: {
      title: '',
    },
  };
}
module.exports = NavigationBar;
<commit_msg>Fix broken StatusBar props<commit_after>import React from 'react-native';
const {
  PixelRatio,
  StatusBar,
  Component,
  Text,
  View,
  PropTypes,
  Platform
} = React;
import NavbarButton from './NavbarButton';
import styles from './styles';

const ButtonShape = {
  title: PropTypes.string.isRequired,
  style: PropTypes.any,
  handler: PropTypes.func,
};

const TitleShape = {
  title: PropTypes.string.isRequired,
  tintColor: PropTypes.string,
};

const StatusBarShape = {
  style: PropTypes.oneOf(['light-content', 'default', ]),
  hidden: PropTypes.bool,
  tintColor: PropTypes.string,
  hideAnimation: PropTypes.oneOf(['fade', 'slide', 'none', ]),
  showAnimation: PropTypes.oneOf(['fade', 'slide', 'none', ])
};

function customizeStatusBar(data) {
  if (Platform.OS === 'ios') {
    if (data.style) {
      StatusBar.barStyle = data.style;
    }
    const animation = data.hidden ?
    (data.hideAnimation || NavigationBar.defaultProps.statusBar.hideAnimation) :
    (data.showAnimation || NavigationBar.defaultProps.statusBar.showAnimation);

    StatusBar.showHideTransition = animation;
    StatusBar.hidden = data.hidden;
  }
}

class NavigationBar extends Component {
  componentDidMount() {
    customizeStatusBar(this.props.statusBar);
  }

  componentWillReceiveProps(props) {
    customizeStatusBar(this.props.statusBar);
  }

  getButtonElement(data = {}, style) {
    if (!!data.props) {
      return <View style={styles.navBarButton}>{data}</View>;
    }

    return <NavbarButton
      title={data.title}
      style={[data.style, style, ]}
      tintColor={data.tintColor}
      handler={data.handler} />;
  }

  getTitleElement(data) {
    if (!!data.props) {
      return <View style={styles.customTitle}>{data}</View>;
    }

    const colorStyle = data.tintColor ? { color: data.tintColor, } : null;

    return (
      <Text
        style={[styles.navBarTitleText, colorStyle, ]}>
        {data.title}
      </Text>
    );
  }

  render() {
    const customTintColor = this.props.tintColor ?
      { backgroundColor: this.props.tintColor } : null;

    const customStatusBarTintColor = this.props.statusBar.tintColor ?
      { backgroundColor: this.props.statusBar.tintColor } : null;

    const statusBar = !this.props.statusBar.hidden ?
      <View style={[styles.statusBar, customStatusBarTintColor ]} /> : null;

    return (
      <View style={[styles.navBarContainer, customTintColor, ]}>
        {statusBar}
        <View style={[styles.navBar, this.props.style, ]}>
          {this.getTitleElement(this.props.title)}
          {this.getButtonElement(this.props.leftButton, { marginLeft: 8, })}
          {this.getButtonElement(this.props.rightButton, { marginRight: 8, })}
        </View>
      </View>
    );
  }

  static propTypes = {
    tintColor: PropTypes.string,
    statusBar: PropTypes.shape(StatusBarShape),
    leftButton: PropTypes.oneOfType([
      PropTypes.shape(ButtonShape),
      PropTypes.element,
    ]),
    rightButton: PropTypes.oneOfType([
      PropTypes.shape(ButtonShape),
      PropTypes.element,
    ]),
    title: PropTypes.oneOfType([
      PropTypes.shape(TitleShape),
      PropTypes.element,
    ]),
  };

  static defaultProps = {
    statusBar: {
      style: 'default',
      hidden: false,
      hideAnimation: 'slide',
      showAnimation: 'slide',
    },
    title: {
      title: '',
    },
  };
}
module.exports = NavigationBar;
<|endoftext|>


<commit_before>/**
  AuthGateway Middleware
  @description Exposes an API to create a user token schema and registers
    the required middleware for verifying and unpacking user data into the req
    object, also exposes an API for generating user tokens with a given payload
  @author tylerFowler
**/
'use strict';

const crypto  = require('crypto');
const _       = require('underscore');
const Promise = require('bluebird');
const JWT     = Promise.promisifyAll(require('jsonwebtoken'));

module.exports = exports = AuthGW;

// secondary components
let middleware = exports.Middleware = {};

/**
  @constructor AuthGW
  @desc Constructor function that creates a new gateway manager instance with
    the configured settings
  @param { String[] } roles - list of all possible user roles from lowest
    access level to highest access level :optional
  @param { String } authorityName - serves as the JWT 'issuer' name
  @param { Object[] } dataSchema - defines the keys to r/w to/from tokens
  @param { Object } opts
  @returns AuthGW instance
**/
function AuthGW(roles, authorityName, dataSchema, opts) {
  if (!Array.isArray(roles)) roles = [roles];

  this.roles = roles;
  this.authorityName = authorityName;

  // if required is omitted default to false
  this.dataSchema = dataSchema.map(s => _.defaults(s, { required: false }));

  this.opts = Object.assign({
    tokenHeader: 'x-access-token',
    tokenSecret: crypto.randomBytes(24).toString('hex'),
    tokenExpiredCode: 419
  }, opts);
}

/**
  @name AuthGW#verifyAuthToken
  @desc Verifies that a given token is valid, unexpired, and trusted
  @param { String } token is the encrypted auth token
  @returns Promise<Object> contains the token's data
**/
AuthGW.prototype.verifyAuthToken = function verifyAuthtoken(token) {
  return JWT
  .verifyAsync(token, this.opts.tokenSecret, { issuer: this.authorityName });
};

/**
  @name AuthGW#middleware
  @desc Factory that creates the two Express middleware functions necessary
    for writing the data we need & verifying the auth token
  @returns [ verifyTokenFn(req,res,next), injectDataFn(req,res,next) ]
**/
AuthGW.prototype.middleware = function createMiddleware() {
  return [
    middleware.verifyTokenExpress.call(this),
    middleware.injectTokenDataExpress.call(this)
  ];
};

/**
  @name Middleware#verifyToken
  @desc ExpressJS flavor of the verify token middleware, verifies the auth token
    Note that if no auth token was given we *will* move along, but without
    writing any data to the request object
  @returns { Function } verifyToken
  @returns 401 if the given token is invalid or expired
**/
middleware.verifyTokenExpress = function verifyToken() {
  return (req, res, next) => {
    const authToken = req.headers[this.opts.tokenHeader];

    // if there's no token just don't inject the user data
    if (!authToken) return next();

    this.verifyAuthToken(authToken)
    .then(tokenData => {
      req._tokenData = tokenData;
      next();
    })

    // NOTE: it is **vitally** important that the client does *not* send a
    // another request with this token after receiving this error code
    // (say to prompt signin) or else this will be sent again,
    // resulting in an infinite loop
    .catch(
      JWT.TokenExpiredError, () => res.sendStatus(this.opts.tokenExpiredCode)
    )

    // token is malformed or otherwise unreadable
    .catch(JWT.JsonWebTokenError, () =>
      res.status(401).send('Invalid authorization token')
    )

    // general error, send 500
    .catch(() => res.sendStatus(500));
  };
};

/**
  @name Middleware#injectTokenData
  @desc ExpressJS flavor of the injection middleware, injects the decoded
    auth token's data into the req object, returning an error if a required
    field from the schema is missing
  @returns { Function } injectTokenData
  @returns 401 if the given token is missing required data
**/
middleware.injectTokenDataExpress = function injectTokenData() {
  // TODO:
  // - validate data fields match schema, throw away anything extra
  // - write schema fields into request object

  return (req, res, next) => {
    if (!req._tokenData) return next();
    const data = req._tokenData;
  };
};
<commit_msg>Implemented token data injection middleware<commit_after>/**
  AuthGateway Middleware
  @description Exposes an API to create a user token schema and registers
    the required middleware for verifying and unpacking user data into the req
    object, also exposes an API for generating user tokens with a given payload
  @author tylerFowler
**/
'use strict';

const crypto  = require('crypto');
const _       = require('underscore');
const Promise = require('bluebird');
const JWT     = Promise.promisifyAll(require('jsonwebtoken'));

module.exports = exports = AuthGW;

// secondary components
let middleware = exports.Middleware = {};

/**
  @constructor AuthGW
  @desc Constructor function that creates a new gateway manager instance with
    the configured settings
  @param { String[] } roles - list of all possible user roles from lowest
    access level to highest access level :optional
  @param { String } authorityName - serves as the JWT 'issuer' name
  @param { Object[] } dataSchema - defines the keys to r/w to/from tokens
  @param { Object } opts
  @returns AuthGW instance
**/
function AuthGW(roles, authorityName, dataSchema, opts) {
  if (!Array.isArray(roles)) roles = [roles];

  this.roles = roles;
  this.authorityName = authorityName;

  // if required is omitted default to false
  this.dataSchema = dataSchema.map(s => _.defaults(s, { required: false }));

  this.opts = Object.assign({
    tokenHeader: 'x-access-token',
    tokenSecret: crypto.randomBytes(24).toString('hex'),
    tokenExpiredCode: 419
  }, opts);
}

/**
  @name AuthGW#verifyAuthToken
  @desc Verifies that a given token is valid, unexpired, and trusted
  @param { String } token is the encrypted auth token
  @returns Promise<Object> contains the token's data
**/
AuthGW.prototype.verifyAuthToken = function verifyAuthtoken(token) {
  return JWT
  .verifyAsync(token, this.opts.tokenSecret, { issuer: this.authorityName });
};

/**
  @name AuthGW#middleware
  @desc Factory that creates the two Express middleware functions necessary
    for writing the data we need & verifying the auth token
  @returns [ verifyTokenFn(req,res,next), injectDataFn(req,res,next) ]
**/
AuthGW.prototype.middleware = function createMiddleware() {
  return [
    middleware.verifyTokenExpress.call(this),
    middleware.injectTokenDataExpress.call(this)
  ];
};

/**
  @name Middleware#verifyToken
  @desc ExpressJS flavor of the verify token middleware, verifies the auth token
    Note that if no auth token was given we *will* move along, but without
    writing any data to the request object
  @returns { Function } verifyToken
  @returns 401 if the given token is invalid or expired
**/
middleware.verifyTokenExpress = function verifyToken() {
  return (req, res, next) => {
    const authToken = req.headers[this.opts.tokenHeader];

    // if there's no token just don't inject the user data
    if (!authToken) return next();

    this.verifyAuthToken(authToken)
    .then(tokenData => {
      req._tokenData = tokenData;
      next();
    })

    // NOTE: it is **vitally** important that the client does *not* send a
    // another request with this token after receiving this error code
    // (say to prompt signin) or else this will be sent again,
    // resulting in an infinite loop
    .catch(
      JWT.TokenExpiredError, () => res.sendStatus(this.opts.tokenExpiredCode)
    )

    // token is malformed or otherwise unreadable
    .catch(JWT.JsonWebTokenError, () =>
      res.status(401).send('Invalid authorization token')
    )

    // general error, send 500
    .catch(() => res.sendStatus(500));
  };
};

/**
  @name Middleware#injectTokenData
  @desc ExpressJS flavor of the injection middleware, injects the decoded
    auth token's data into the req object, returning an error if a required
    field from the schema is missing
  @returns { Function } injectTokenData
  @returns 401 if the given token is missing required data
**/
middleware.injectTokenDataExpress = function injectTokenData() {
  return (req, res, next) => {
    if (!req._tokenData) return next();

    _.chain(req._tokenData)
    .pick(_.pluck(this.dataSchema, 'name'))
    .tap(filtered => {
      let missing = _.find(
        this.dataSchema, s => s.required && !_.has(filtered, s.name)
      );

      if (missing)
        next(new Error(`Required item ${missingItm.name} is missing`));
    })
    .each((v, k) => req[k] = v);

    next();
  };
};
<|endoftext|>