<commit_before>"use strict";

/*

GroundDB is a thin layer providing Meteor offline database and methods

Concept, localstorage is simple wide spread but slow

GroundDB saves outstanding methods and minimongo into localstorage at window
unload, but can be configured to save at any changes and at certain interval(ms)

When the app loads GroundDB resumes methods and database changes

Regz. RaiX

*/

////////////////////////////// LOCALSTORAGE ////////////////////////////////////

// Well, I'm still using console.log
window.console = (window && window.console && window.console.log)?
        window.console: {
  log: function() {}
};

// Status of app reload
var _isReloading = false;

// Returns the localstorage if its found and working
// TODO: check if this works in IE
// could use Meteor._localStorage - just needs a rewrite
var _storage = function() {
  var storage,
      fail,
      uid;
  try {
    uid = Random.id();
    (storage = window.localStorage).setItem(uid, uid);
    fail = (storage.getItem(uid) !== uid);
    storage.removeItem(uid);
    if (fail) {
      storage = false;
    }
  } catch(e) {}

  return storage;
};

// get our storage if found
var storage = _storage();

var _prefixGroundDB = 'groundDB.';

// Add a correct prefix for groundDB
var _getGroundDBPrefix = function(suffix) {
  // Should we support multiple users on multiple tabs namespacing data
  // in localstorage by current userId?
  //return prefix + ((Meteor.userId())?Meteor.userId()+'.':'') + suffix;
  return _prefixGroundDB + suffix;
};

// save object into localstorage
var _saveObject = function(name, object) {
  if (storage && _isReloading === false) {
    var cachedDoc = EJSON.stringify(object);

    try {
      storage.setItem(_getGroundDBPrefix(name), cachedDoc);
    } catch (e) {
      GroundDB.onQuotaExceeded();
    }
  }
};

// get object from localstorage, retur null if not found
var _loadObject = function(name) {
  if (storage) {
    var cachedDoc = storage.getItem(_getGroundDBPrefix(name));
    if (cachedDoc && cachedDoc.length > 0 && cachedDoc !== 'undefined') {
      var cachedDocObject = EJSON.parse(cachedDoc);
      return cachedDocObject;
    }
  }
  return null;
};

//////////////////////////////// GROUND DATABASE ///////////////////////////////

// Add a pointer register
var _groundDatabases = {};

// @export GroundDB
window.GroundDB = function(name, options) {
// TODO: change when linker is official

  // Inheritance Meteor Collection can be set by options.collection
  // Accepts smart collections by Arunoda Susiripala
  var self;
  // If name is string or null then assume a normal Meteor.Collection
  if (name === ''+name || name === null || typeof name === 'undefined') {
    // We instanciate a new meteor collection, let it handle undefined
    self = new Meteor.Collection(name, options);
  } else {
    // User set a collection in options
    if (name instanceof Meteor.Collection) {
      self = name;
    } else {
      if (name._localCollection instanceof Meteor.Collection) {
        // We are in a smart collection
        self = name._localCollection;
        self.name = name.name;
      } else {
        // self not set, throw an error
        throw new Error('GroundDB got an invalid name or collection');
      }
    }
  }

  self.name = (self.name)? self.name : self._name;

  // Add to pointer register
  _groundDatabases[ self.name ] = self;

  // We have to overwrite the standard Meteor code - It throws an Error when
  // Documents allready in the docs. So we handle the conflict instead...
  // TODO: Could this be cleaned up?
  ////////// BEGIN mongo-livedata/collection.js 103
  if (self._connection && self._connection._stores[ self.name ]) {
    self._connection._stores[ self.name ].update = function (msg) {
      var mongoId = Meteor.idParse(msg.id);
      var doc = self._collection.findOne(mongoId);
      // Is this a "replace the whole doc" message coming from the quiescence
      // of method writes to an object? (Note that 'undefined' is a valid
      // value meaning "remove it".)
      if (msg.msg === 'replace') {
        var replace = msg.replace;
        if (!replace) {
          if (doc) {
            self._collection.remove(mongoId);
          }
        } else if (!doc) {
          self._collection.insert(replace);
        } else {
          // XXX check that replace has no $ ops
          self._collection.update(mongoId, replace);
        }
        return;
      } else if (msg.msg === 'added') {

        if (doc) {
          // Solve the conflict - server wins
          // Then remove the client document
          self._collection.remove(doc._id);
        }
        // And insert the server document
        self._collection.insert(_.extend({_id: mongoId}, msg.fields));

      } else if (msg.msg === 'removed') {
        if (doc) {
          // doc found - remove it
          self._collection.remove(mongoId);
        } else {
          throw new Error("Expected to find a document present for removed");
        }

      } else if (msg.msg === 'changed') {
        if (!doc) {
          throw new Error("Expected to find a document to change: " + mongoId);
        } else {
          if (!_.isEmpty(msg.fields)) {
            var modifier = {};
            _.each(msg.fields, function (value, key) {
              if (value === undefined) {
                if (!modifier.$unset) {
                  modifier.$unset = {};
                }
                modifier.$unset[key] = 1;
              } else {
                if (!modifier.$set) {
                  modifier.$set = {};
                }
                modifier.$set[key] = value;
              }
            });
            self._collection.update(mongoId, modifier);
          }
        }
      } else {
        throw new Error("I don't know how to deal with this message");
      }

    };
  }
  ///////// EO mongo-livedata/collection.js 153

  self._databaseLoaded = false;

  // We dont trust the localstorage so we make sure it doesn't contain
  // duplicated id's
  self._checkDocs = function(a) {
    var c = {};
    // We create c as an object with no duplicate _id's
    for (var i = 0, keys = Object.keys(a); i < keys.length; i++) {
      var key = keys[i];
      var doc = a[key];
      // set value in c
      c[key] = doc;
    }
    return c;
  };

  // Bulk Load database from local to memory
  self._loadDatabase = function() {
    // Then load the docs into minimongo
    GroundDB.onResumeDatabase(self.name);

    // Load object from localstorage
    var docs = _loadObject('db.' + self.name);

    // Initialize client documents
    _.each(self._checkDocs( (docs) ? docs : {} ), function(doc) {
      self._collection.insert(doc);
    });

    self._databaseLoaded = true;
  };

  // Bulk Save database from memory to local, meant to be as slim, fast and
  // realiable as possible
  self._saveDatabase = function() {
    // If data loaded from localstorage then its ok to save - otherwise we
    // would override with less data
    if (self._databaseLoaded) {
      GroundDB.onCacheDatabase(self.name);
      // Save the collection into localstorage
      _saveObject('db.' + self.name, self._collection.docs);
    }
  };

  // Observe all changes and rely on the less agressive reactive system for
  // providing a reasonable update frequens
  Deps.autorun(function() {
    // Observe changes
    self.find().fetch();
    // Save on changes
    self._saveDatabase();
  });

  // Load the database as soon as possible
  self._loadDatabase();

  return self;
};

///////////////////////////////// EVENTS ///////////////////////////////////////

// This is an overridable method for hooking on to the GroundDB events

GroundDB.onQuotaExceeded = function() {
  throw new Error('Quota exceeded!');
};

GroundDB.onResumeDatabase = function(name) {
  console.log('Resume database: ' + name);
};

GroundDB.onResumeMethods = function() {
  console.log('Resume outstanding methods');
};

GroundDB.onMethodCall = function(methodCall) {
  console.log('Method call ' + methodCall._message.method);
};

GroundDB.onCacheDatabase = function(name) {
  console.log('Cache database: ' + name);
};

GroundDB.onCacheMethods = function() {
  console.log('Cache methods');
};

GroundDB.onTabSync = function(key) {
  console.log('Sync - Cache is updated by: ' + key);
};

GroundDB.onCorruptedClientMemory = function() {
  // Let error be thrown an visible for 5sec then reload app
  Meteor.setTimeout(function() {
    window.location.reload();
  }, 5000);
  // Throw an error, cannot continue - This should never happen
  throw new Error('Outstanding methods is corrupted in memory - '+
          'Reloading app in 5sec');
};

///////////////////////////// RESUME METHODS ///////////////////////////////////

// Is methods resumed?
var _methodsResumed = false;

// Get a nice array of current methods
var _getMethodsList = function() {
  // Array of outstanding methods
  var methods = [];

  // Convert the data into nice array
  _.each(Meteor.default_connection._methodInvokers, function(method) {
    if (method._message.method !== 'login') {
      // Dont cache login calls - they are spawned pr. default when accounts
      // are installed
      methods.push({
        // Format the data
        method: method._message.method,
        args: method._message.params,
        options: { wait: method._wait }
      });
      GroundDB.onMethodCall(method);
    }
  });

  return methods;
};

// Extract only newly added methods from localstorage
var _getMethodUpdates = function(newMethods) {
  var result = [];
  if (newMethods && newMethods.length > 0) {
    // Get the old methods allready in memory
    // We could have done an optimized slice version or just starting at
    // oldMethods.length, but this tab is not in focus
    var oldMethods = _getMethodsList();
    // Iterate over the new methods, old ones should be ordered in beginning of
    // newMethods we do a simple test an throw an error if thats not the case
    for (var i=0; i < newMethods.length; i++) {

      if (i < oldMethods.length) {
        // Do a hard slow test to make sure all is in sync
        if (EJSON.stringify(oldMethods[i]) !== EJSON.stringify(newMethods[i])) {
          // The client data is corrupted, throw error or force the client to
          // reload, does not make sense to continue?
          GroundDB.onCorruptedClientMemory();
        }
      } else {
        // Ok out of oldMethods this is a new method call
        result.push(newMethods[i]);
      }
    } // EO for iteration
  } // EO check newMethods

  // return the result
  return result;
};

// load methods from localstorage and resume the methods
var _loadMethods = function() {
  // Load methods from local
  var methods = _loadObject('methods');

  // We are only going to submit the diff
  methods = _getMethodUpdates(methods);

  // If any methods outstanding
  if (methods) {
    // Iterate over array of methods
    //_.each(methods, function(method) {
    while (methods.length > 0) {
      // FIFO buffer
      var method = methods.shift();
      // parse //

      var params = method.method.split('/');
      var command = (params.length > 2)?params[2]:params[1];
      var collection = (params.length > 2)?params[1]:'';
      // TODO: would have been nicer if SmartCollection used same naming as
      // Meteor
      params = ''+params;
      paramIndex = 0;
      if (params === '_si_' || params === '_su_' || params === '_sr_') {
        // Get collection from SmartCollection call
        command = params;
        collection = method.args[0];
        paramIndex++;
      }

      // Do work on collection
      if (collection !== '') {
        // we are going to run an simulated insert - this is allready in db
        // since we are running local, so we remove it from the collection first
        if (_groundDatabases[collection]) {
          // The database is registered as a ground database
          var mongoId = (method.args && method.args[paramIndex])?
                  method.args[paramIndex]._id || method.args[paramIndex]:'';
          // Get the document on the client - if found
          var doc = _groundDatabases[collection]._collection.findOne(mongoId);

          if (doc) {
            // document found
            // This is a problem: insert stub simulation, would fail so we
            // remove the added document from client and let the method call
            // re-insert it in simulation
            if (command === 'insert') {
              // Remove the item from ground database so it can be correctly
              // inserted
              _groundDatabases[collection]._collection.remove(mongoId);
            } // EO handle insert
            // Add tab support in SmartCollections
            if (command === '_su_') { // TODO: Warn if using $inc or $dec?
              _groundDatabases[collection]._collection.update({ _id: mongoId },
                      method.args[2]);
            }
            if (command === '_sr_') {
              _groundDatabases[collection]._collection.remove({ _id: mongoId });
            }
          } else { // EO Else no doc found in client database
            // Add tab support in SmartCollections
            if (command === '_si_') {
              _groundDatabases[collection]._collection.insert(method.args[1]);
            }
          }
        } // else collection would be a normal database
      } // EO collection work

      // Add method to connection
      Meteor.default_connection.apply(
              method.method, method.args, method.options);
    } // EO while methods
  } // EO if stored outstanding methods

  // Dispatch methods loaded event
  _methodsResumed = true;
  GroundDB.onResumeMethods();
}; // EO load methods

// Save the methods into the localstorage
var _saveMethods = function() {
  if (_methodsResumed) {
    GroundDB.onCacheMethods();

    // Save outstanding methods to localstorage
    _saveObject('methods', _getMethodsList());
  }
};

/////////////////////// ADD TRIGGERS IN LIVEDATACONNECTION /////////////////////

// Modify _LivedataConnection, well just minor
_.extend(Meteor._LivedataConnection.prototype, {
  _super: {
    apply: Meteor._LivedataConnection.prototype.apply,
    _outstandingMethodFinished:
    Meteor._LivedataConnection.prototype._outstandingMethodFinished
  },
  // Modify apply
  apply: function(/* arguments */) {
    var self = this;
    // Call super
    self._super.apply.apply(self, arguments);
    // Save methods
    _saveMethods();
  },
  // Modify _outstandingMethodFinished
  _outstandingMethodFinished: function() {
    var self = this;
    // Call super
    self._super._outstandingMethodFinished.apply(self);
    // We save current status of methods
    _saveMethods();
  }
});

//////////////////////////// STARTUP METHODS RESUME ////////////////////////////
Meteor.startup(function() {
  // Wait some not to conflict with accouts login
  // TODO: Do we have a better way, instead of depending on time should depend
  // on en event.
  Meteor.setTimeout(function() {
    _loadMethods();
  }, 500);
});

/////////////////////// LOAD CHANGES FROM OTHER TABS ///////////////////////////
var _reloadTimeoutId = null;

// Add support for multiple tabs
window.addEventListener('storage', function(e) {
  // Data changed in another tab, it would have updated localstorage, I'm
  // outdated so reload the tab and localstorage - but we test the prefix on the
  // key - since we actually make writes in the localstorage feature test
  var prefixRegExp = new RegExp('^'+_prefixGroundDB+'method');
  // Make sure its a prefixed change
  if (prefixRegExp.test(e.key)) {
    GroundDB.onTabSync(e.key);
    // We are going to into reload, stop all access to localstorage
    _isReloading = true;
    // We are not master and the user is working on another tab, we are not in
    // a hurry to spam the browser with work, plus there are typically acouple
    // of db access required in most operations, we wait a sec?
    if (_reloadTimeoutId !== null) {
      // Stop the current timeout - we have updates
      Meteor.clearTimeout(_reloadTimeoutId);
    }
    _reloadTimeoutId = Meteor.setTimeout(function() {
      // Ok, we reset reference and go to work
      _reloadTimeoutId = null;
      // Resume methods
      _loadMethods();
      // Resume normal writes
      _isReloading = false;
    }, 100);

  }
}, false);
<commit_msg>Fix #7 + minor improvements<commit_after>"use strict";

/*

GroundDB is a thin layer providing Meteor offline database and methods

Concept, localstorage is simple wide spread but slow

GroundDB saves outstanding methods and minimongo into localstorage at window
unload, but can be configured to save at any changes and at certain interval(ms)

When the app loads GroundDB resumes methods and database changes

Regz. RaiX

*/

////////////////////////////// LOCALSTORAGE ////////////////////////////////////

// Well, I'm still using console.log
window.console = (window && window.console && window.console.log)?
        window.console: {
  log: function() {}
};

// Status of app reload
var _isReloading = false;

// Returns the localstorage if its found and working
// TODO: check if this works in IE
// could use Meteor._localStorage - just needs a rewrite
var _storage = function() {
  var storage,
      fail,
      uid;
  try {
    uid = Random.id();
    (storage = window.localStorage).setItem(uid, uid);
    fail = (storage.getItem(uid) !== uid);
    storage.removeItem(uid);
    if (fail) {
      storage = false;
    }
  } catch(e) {}

  return storage;
};

// get our storage if found
var storage = _storage();

var _prefixGroundDB = 'groundDB.';

// Add a correct prefix for groundDB
var _getGroundDBPrefix = function(suffix) {
  // Should we support multiple users on multiple tabs namespacing data
  // in localstorage by current userId?
  //return prefix + ((Meteor.userId())?Meteor.userId()+'.':'') + suffix;
  return _prefixGroundDB + suffix;
};

// save object into localstorage
var _saveObject = function(name, object) {
  if (storage && _isReloading === false) {
    var cachedDoc = EJSON.stringify(object);

    try {
      storage.setItem(_getGroundDBPrefix(name), cachedDoc);
    } catch (e) {
      GroundDB.onQuotaExceeded();
    }
  }
};

// get object from localstorage, retur null if not found
var _loadObject = function(name) {
  if (storage) {
    var cachedDoc = storage.getItem(_getGroundDBPrefix(name));
    if (cachedDoc && cachedDoc.length > 0 && cachedDoc !== 'undefined') {
      var cachedDocObject = EJSON.parse(cachedDoc);
      return cachedDocObject;
    }
  }
  return null;
};

//////////////////////////////// GROUND DATABASE ///////////////////////////////

// Add a pointer register
var _groundDatabases = {};

// @export GroundDB
window.GroundDB = function(name, options) {
// TODO: change when linker is official

  // Inheritance Meteor Collection can be set by options.collection
  // Accepts smart collections by Arunoda Susiripala
  var self;
  // If name is string or null then assume a normal Meteor.Collection
  if (name === ''+name || name === null || typeof name === 'undefined') {
    // We instanciate a new meteor collection, let it handle undefined
    self = new Meteor.Collection(name, options);
  } else {
    // User set a collection in options
    if (name instanceof Meteor.Collection) {
      self = name;
    } else {
      if (name._localCollection instanceof Meteor.Collection) {
        // We are in a smart collection
        self = name._localCollection;
        self.name = name.name;
      } else {
        // self not set, throw an error
        throw new Error('GroundDB got an invalid name or collection');
      }
    }
  }

  self.name = (self.name)? self.name : self._name;

  // Add to pointer register
  _groundDatabases[ self.name ] = self;

  // We have to overwrite the standard Meteor code - It throws an Error when
  // Documents allready in the docs. So we handle the conflict instead...
  // TODO: Could this be cleaned up?
  ////////// BEGIN mongo-livedata/collection.js 103
  if (self._connection && self._connection._stores[ self.name ]) {
    self._connection._stores[ self.name ].update = function (msg) {
      var mongoId = Meteor.idParse(msg.id);
      var doc = self._collection.findOne(mongoId);
      // Is this a "replace the whole doc" message coming from the quiescence
      // of method writes to an object? (Note that 'undefined' is a valid
      // value meaning "remove it".)
      if (msg.msg === 'replace') {
        var replace = msg.replace;
        if (!replace) {
          if (doc) {
            self._collection.remove(mongoId);
          }
        } else if (!doc) {
          self._collection.insert(replace);
        } else {
          // XXX check that replace has no $ ops
          self._collection.update(mongoId, replace);
        }
        return;
      } else if (msg.msg === 'added') {

        if (doc) {
          // Solve the conflict - server wins
          // Then remove the client document
          self._collection.remove(doc._id);
        }
        // And insert the server document
        self._collection.insert(_.extend({_id: mongoId}, msg.fields));

      } else if (msg.msg === 'removed') {
        if (doc) {
          // doc found - remove it
          self._collection.remove(mongoId);
        } else {
          throw new Error("Expected to find a document present for removed");
        }

      } else if (msg.msg === 'changed') {
        if (!doc) {
          throw new Error("Expected to find a document to change: " + mongoId);
        } else {
          if (!_.isEmpty(msg.fields)) {
            var modifier = {};
            _.each(msg.fields, function (value, key) {
              if (value === undefined) {
                if (!modifier.$unset) {
                  modifier.$unset = {};
                }
                modifier.$unset[key] = 1;
              } else {
                if (!modifier.$set) {
                  modifier.$set = {};
                }
                modifier.$set[key] = value;
              }
            });
            self._collection.update(mongoId, modifier);
          }
        }
      } else {
        throw new Error("I don't know how to deal with this message");
      }

    };
  }
  ///////// EO mongo-livedata/collection.js 153

  self._databaseLoaded = false;

  // We dont trust the localstorage so we make sure it doesn't contain
  // duplicated id's
  self._checkDocs = function(a) {
    var c = {};
    // We create c as an object with no duplicate _id's
    for (var i = 0, keys = Object.keys(a); i < keys.length; i++) {
      var key = keys[i];
      var doc = a[key];
      // set value in c
      c[key] = doc;
    }
    return c;
  };

  // Bulk Load database from local to memory
  self._loadDatabase = function() {
    // Then load the docs into minimongo
    GroundDB.onResumeDatabase(self.name);

    // Load object from localstorage
    var docs = _loadObject('db.' + self.name);

    // Initialize client documents
    _.each(self._checkDocs( (docs) ? docs : {} ), function(doc) {
      var exists = self._collection.findOne({ _id: doc });
      // If collection is populated before we get started then the data in
      // memory would be considered latest therefor we dont load from local
      if (!exists) {
        self._collection.insert(doc);
      }
    });

    self._databaseLoaded = true;
  };

  // Bulk Save database from memory to local, meant to be as slim, fast and
  // realiable as possible
  self._saveDatabase = function() {
    // If data loaded from localstorage then its ok to save - otherwise we
    // would override with less data
    if (self._databaseLoaded) {
      GroundDB.onCacheDatabase(self.name);
      // Save the collection into localstorage
      _saveObject('db.' + self.name, self._collection.docs);
    }
  };

  // Observe all changes and rely on the less agressive reactive system for
  // providing a reasonable update frequens
  Deps.autorun(function() {
    // Observe changes
    self.find().fetch();
    // Save on changes
    self._saveDatabase();
  });

  // Load the database as soon as possible
  self._loadDatabase();

  return self;
};

///////////////////////////////// EVENTS ///////////////////////////////////////

// This is an overridable method for hooking on to the GroundDB events

GroundDB.onQuotaExceeded = function() {
  throw new Error('Quota exceeded!');
};

GroundDB.onResumeDatabase = function(name) {
  console.log('Resume database: ' + name);
};

GroundDB.onResumeMethods = function() {
  console.log('Resume outstanding methods');
};

GroundDB.onMethodCall = function(methodCall) {
  console.log('Method call ' + methodCall._message.method);
};

GroundDB.onCacheDatabase = function(name) {
  console.log('Cache database: ' + name);
};

GroundDB.onCacheMethods = function() {
  console.log('Cache methods');
};

GroundDB.onTabSync = function(key) {
  console.log('Sync - Cache is updated by: ' + key);
};

GroundDB.onCorruptedClientMemory = function() {
  // Let error be thrown an visible for 5sec then reload app
  Meteor.setTimeout(function() {
    window.location.reload();
  }, 5000);
  // Throw an error, cannot continue - This should never happen
  throw new Error('Outstanding methods is corrupted in memory - '+
          'Reloading app in 5sec');
};

///////////////////////////// RESUME METHODS ///////////////////////////////////

// Is methods resumed?
var _methodsResumed = false;

// Get a nice array of current methods
var _getMethodsList = function() {
  // Array of outstanding methods
  var methods = [];

  // Convert the data into nice array
  _.each(Meteor.default_connection._methodInvokers, function(method) {
    if (method._message.method !== 'login') {
      // Dont cache login calls - they are spawned pr. default when accounts
      // are installed
      methods.push({
        // Format the data
        method: method._message.method,
        args: method._message.params,
        options: { wait: method._wait }
      });
      GroundDB.onMethodCall(method);
    }
  });

  return methods;
};

// Extract only newly added methods from localstorage
var _getMethodUpdates = function(newMethods) {
  var result = [];
  if (newMethods && newMethods.length > 0) {
    // Get the old methods allready in memory
    // We could have done an optimized slice version or just starting at
    // oldMethods.length, but this tab is not in focus
    var oldMethods = _getMethodsList();
    // Iterate over the new methods, old ones should be ordered in beginning of
    // newMethods we do a simple test an throw an error if thats not the case
    for (var i=0; i < newMethods.length; i++) {

      if (i < oldMethods.length) {
        // Do a hard slow test to make sure all is in sync
        if (EJSON.stringify(oldMethods[i]) !== EJSON.stringify(newMethods[i])) {
          // The client data is corrupted, throw error or force the client to
          // reload, does not make sense to continue?
          GroundDB.onCorruptedClientMemory();
        }
      } else {
        // Ok out of oldMethods this is a new method call
        result.push(newMethods[i]);
      }
    } // EO for iteration
  } // EO check newMethods

  // return the result
  return result;
};

// load methods from localstorage and resume the methods
var _loadMethods = function() {
  // Load methods from local
  var methods = _loadObject('methods');

  // We are only going to submit the diff
  methods = _getMethodUpdates(methods);

  // If any methods outstanding
  if (methods) {
    // Iterate over array of methods
    //_.each(methods, function(method) {
    while (methods.length > 0) {
      // FIFO buffer
      var method = methods.shift();
      // parse //

      var params = method.method.split('/');
      var command = (params.length > 2)?params[2]:params[1];
      var collection = (params.length > 2)?params[1]:'';
      // TODO: would have been nicer if SmartCollection used same naming as
      // Meteor
      params = ''+params;
      paramIndex = 0;
      if (params === '_si_' || params === '_su_' || params === '_sr_') {
        // Get collection from SmartCollection call
        command = params;
        collection = method.args[0];
        paramIndex++;
      }

      // Do work on collection
      if (collection !== '') {
        // we are going to run an simulated insert - this is allready in db
        // since we are running local, so we remove it from the collection first
        if (_groundDatabases[collection]) {
          // The database is registered as a ground database
          var mongoId = Meteor.idParse((method.args && method.args[paramIndex])?
                  method.args[paramIndex]._id || method.args[paramIndex]:'');

          // Get the document on the client - if found
          var doc = _groundDatabases[collection]._collection.findOne(mongoId);

          if (doc) {
            // document found
            // This is a problem: insert stub simulation, would fail so we
            // remove the added document from client and let the method call
            // re-insert it in simulation
            if (command === 'insert') {
              // Remove the item from ground database so it can be correctly
              // inserted
              _groundDatabases[collection]._collection.remove(mongoId);
            } // EO handle insert
            // Add tab support in SmartCollections
            if (command === '_su_') { // TODO: Warn if using $inc or $dec?
              _groundDatabases[collection]._collection.update({ _id: mongoId },
                      method.args[2]);
            }
            if (command === '_sr_') {
              _groundDatabases[collection]._collection.remove(mongoId);
            }
          } else { // EO Else no doc found in client database
            // Add tab support in SmartCollections
            if (command === '_si_') {
              _groundDatabases[collection]._collection.insert(method.args[1]);
            }
          }
        } // else collection would be a normal database
      } // EO collection work

      // Add method to connection
      Meteor.default_connection.apply(
              method.method, method.args, method.options);
    } // EO while methods
  } // EO if stored outstanding methods

  // Dispatch methods loaded event
  _methodsResumed = true;
  GroundDB.onResumeMethods();
}; // EO load methods

// Save the methods into the localstorage
var _saveMethods = function() {
  if (_methodsResumed) {
    GroundDB.onCacheMethods();

    // Save outstanding methods to localstorage
    _saveObject('methods', _getMethodsList());
  }
};

/////////////////////// ADD TRIGGERS IN LIVEDATACONNECTION /////////////////////

// Modify _LivedataConnection, well just minor
_.extend(Meteor._LivedataConnection.prototype, {
  _super: {
    apply: Meteor._LivedataConnection.prototype.apply,
    _outstandingMethodFinished:
    Meteor._LivedataConnection.prototype._outstandingMethodFinished
  },
  // Modify apply
  apply: function(/* arguments */) {
    var self = this;
    // Call super
    self._super.apply.apply(self, arguments);
    // Save methods
    _saveMethods();
  },
  // Modify _outstandingMethodFinished
  _outstandingMethodFinished: function() {
    var self = this;
    // Call super
    self._super._outstandingMethodFinished.apply(self);
    // We save current status of methods
    _saveMethods();
  }
});

//////////////////////////// STARTUP METHODS RESUME ////////////////////////////
Meteor.startup(function() {
  // Wait some not to conflict with accouts login
  // TODO: Do we have a better way, instead of depending on time should depend
  // on en event.
  Meteor.setTimeout(function() {
    _loadMethods();
  }, 500);
});

/////////////////////// LOAD CHANGES FROM OTHER TABS ///////////////////////////
var _reloadTimeoutId = null;

// Add support for multiple tabs
window.addEventListener('storage', function(e) {
  // Data changed in another tab, it would have updated localstorage, I'm
  // outdated so reload the tab and localstorage - but we test the prefix on the
  // key - since we actually make writes in the localstorage feature test
  var prefixRegExp = new RegExp('^'+_prefixGroundDB+'method');
  // Make sure its a prefixed change
  if (prefixRegExp.test(e.key)) {
    GroundDB.onTabSync(e.key);
    // We are going to into reload, stop all access to localstorage
    _isReloading = true;
    // We are not master and the user is working on another tab, we are not in
    // a hurry to spam the browser with work, plus there are typically acouple
    // of db access required in most operations, we wait a sec?
    if (_reloadTimeoutId !== null) {
      // Stop the current timeout - we have updates
      Meteor.clearTimeout(_reloadTimeoutId);
    }
    _reloadTimeoutId = Meteor.setTimeout(function() {
      // Ok, we reset reference and go to work
      _reloadTimeoutId = null;
      // Resume methods
      _loadMethods();
      // Resume normal writes
      _isReloading = false;
    }, 200);

  }
}, false);
<|endoftext|>


<commit_before>"use strict";

const _ = require("lodash");
const Msg = require("../../models/msg");
const Chan = require("../../models/chan");
const Helper = require("../../helper");

module.exports = function(irc, network) {
	const client = this;

	network.channels[0].pushMessage(client, new Msg({
		text: "Network created, connecting to " + network.host + ":" + network.port + "...",
	}), true);

	irc.on("registered", function() {
		if (network.irc.network.cap.enabled.length > 0) {
			network.channels[0].pushMessage(client, new Msg({
				text: "Enabled capabilities: " + network.irc.network.cap.enabled.join(", "),
			}), true);
		}

		// Always restore away message for this network
		if (network.awayMessage) {
			irc.raw("AWAY", network.awayMessage);
		// Only set generic away message if there are no clients attached
		} else if (client.awayMessage && _.size(client.attachedClients) === 0) {
			irc.raw("AWAY", client.awayMessage);
		}

		let delay = 1000;

		if (Array.isArray(network.commands)) {
			network.commands.forEach((cmd) => {
				setTimeout(function() {
					client.input({
						target: network.channels[0].id,
						text: cmd,
					});
				}, delay);
				delay += 1000;
			});
		}

		network.channels.forEach((chan) => {
			if (chan.type !== Chan.Type.CHANNEL) {
				return;
			}

			setTimeout(function() {
				network.irc.join(chan.name, chan.key);
			}, delay);
			delay += 1000;
		});
	});

	irc.on("socket connected", function() {
		network.prefixLookup = {};
		irc.network.options.PREFIX.forEach(function(mode) {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.channels[0].pushMessage(client, new Msg({
			text: "Connected to the network.",
		}), true);

		sendStatus();
	});

	irc.on("close", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network, and will not reconnect. Use /connect to reconnect again.",
		}), true);
	});

	let identSocketId;

	irc.on("raw socket connected", function(socket) {
		identSocketId = client.manager.identHandler.addSocket(socket, client.name || network.username);
	});

	irc.on("socket close", function() {
		if (identSocketId > 0) {
			client.manager.identHandler.removeSocket(identSocketId);
			identSocketId = 0;
		}

		network.channels.forEach((chan) => {
			chan.state = Chan.State.PARTED;
		});

		sendStatus();
	});

	if (Helper.config.debug.ircFramework) {
		irc.on("debug", function(message) {
			log.debug("[" + client.name + " (#" + client.id + ") on " + network.name + " (#" + network.id + ")]", message);
		});
	}

	if (Helper.config.debug.raw) {
		irc.on("raw", function(message) {
			network.channels[0].pushMessage(client, new Msg({
				from: message.from_server ? "«" : "»",
				self: !message.from_server,
				type: "raw",
				text: message.line,
			}), true);
		});
	}

	irc.on("socket error", function(err) {
		network.channels[0].pushMessage(client, new Msg({
			type: Msg.Type.ERROR,
			text: "Socket error: " + err,
		}), true);
	});

	irc.on("reconnecting", function(data) {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network. Reconnecting in " + Math.round(data.wait / 1000) + " seconds… (Attempt " + data.attempt + " of " + data.max_retries + ")",
		}), true);
	});

	irc.on("ping timeout", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Ping timeout, disconnecting…",
		}), true);
	});

	irc.on("server options", function(data) {
		if (network.serverOptions.PREFIX === data.options.PREFIX && network.serverOptions.NETWORK === data.options.NETWORK) {
			return;
		}

		network.prefixLookup = {};

		data.options.PREFIX.forEach((mode) => {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.serverOptions.PREFIX = data.options.PREFIX;
		network.serverOptions.NETWORK = data.options.NETWORK;

		client.emit("network_changed", {
			network: network.id,
			serverOptions: network.serverOptions,
		});
	});

	function sendStatus() {
		const status = network.getNetworkStatus();
		status.network = network.id;

		client.emit("network:status", status);
	}
};
<commit_msg>Log socket close error if any<commit_after>"use strict";

const _ = require("lodash");
const Msg = require("../../models/msg");
const Chan = require("../../models/chan");
const Helper = require("../../helper");

module.exports = function(irc, network) {
	const client = this;

	network.channels[0].pushMessage(client, new Msg({
		text: "Network created, connecting to " + network.host + ":" + network.port + "...",
	}), true);

	irc.on("registered", function() {
		if (network.irc.network.cap.enabled.length > 0) {
			network.channels[0].pushMessage(client, new Msg({
				text: "Enabled capabilities: " + network.irc.network.cap.enabled.join(", "),
			}), true);
		}

		// Always restore away message for this network
		if (network.awayMessage) {
			irc.raw("AWAY", network.awayMessage);
		// Only set generic away message if there are no clients attached
		} else if (client.awayMessage && _.size(client.attachedClients) === 0) {
			irc.raw("AWAY", client.awayMessage);
		}

		let delay = 1000;

		if (Array.isArray(network.commands)) {
			network.commands.forEach((cmd) => {
				setTimeout(function() {
					client.input({
						target: network.channels[0].id,
						text: cmd,
					});
				}, delay);
				delay += 1000;
			});
		}

		network.channels.forEach((chan) => {
			if (chan.type !== Chan.Type.CHANNEL) {
				return;
			}

			setTimeout(function() {
				network.irc.join(chan.name, chan.key);
			}, delay);
			delay += 1000;
		});
	});

	irc.on("socket connected", function() {
		network.prefixLookup = {};
		irc.network.options.PREFIX.forEach(function(mode) {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.channels[0].pushMessage(client, new Msg({
			text: "Connected to the network.",
		}), true);

		sendStatus();
	});

	irc.on("close", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network, and will not reconnect. Use /connect to reconnect again.",
		}), true);
	});

	let identSocketId;

	irc.on("raw socket connected", function(socket) {
		identSocketId = client.manager.identHandler.addSocket(socket, client.name || network.username);
	});

	irc.on("socket close", function(error) {
		if (identSocketId > 0) {
			client.manager.identHandler.removeSocket(identSocketId);
			identSocketId = 0;
		}

		network.channels.forEach((chan) => {
			chan.state = Chan.State.PARTED;
		});

		if (error) {
			network.channels[0].pushMessage(client, new Msg({
				type: Msg.Type.ERROR,
				text: `Connection closed unexpectedly: ${error}`,
			}), true);
		}

		sendStatus();
	});

	if (Helper.config.debug.ircFramework) {
		irc.on("debug", function(message) {
			log.debug("[" + client.name + " (#" + client.id + ") on " + network.name + " (#" + network.id + ")]", message);
		});
	}

	if (Helper.config.debug.raw) {
		irc.on("raw", function(message) {
			network.channels[0].pushMessage(client, new Msg({
				from: message.from_server ? "«" : "»",
				self: !message.from_server,
				type: "raw",
				text: message.line,
			}), true);
		});
	}

	irc.on("socket error", function(err) {
		network.channels[0].pushMessage(client, new Msg({
			type: Msg.Type.ERROR,
			text: "Socket error: " + err,
		}), true);
	});

	irc.on("reconnecting", function(data) {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network. Reconnecting in " + Math.round(data.wait / 1000) + " seconds… (Attempt " + data.attempt + " of " + data.max_retries + ")",
		}), true);
	});

	irc.on("ping timeout", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Ping timeout, disconnecting…",
		}), true);
	});

	irc.on("server options", function(data) {
		if (network.serverOptions.PREFIX === data.options.PREFIX && network.serverOptions.NETWORK === data.options.NETWORK) {
			return;
		}

		network.prefixLookup = {};

		data.options.PREFIX.forEach((mode) => {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.serverOptions.PREFIX = data.options.PREFIX;
		network.serverOptions.NETWORK = data.options.NETWORK;

		client.emit("network_changed", {
			network: network.id,
			serverOptions: network.serverOptions,
		});
	});

	function sendStatus() {
		const status = network.getNetworkStatus();
		status.network = network.id;

		client.emit("network:status", status);
	}
};
<|endoftext|>


<commit_before>"use strict";

(function () {
    angular
        .module("piadinamia")
        .factory("sharedCartService", sharedCartService);

    sharedCartService.$inject = ["$timeout", "sessionService"];

    function sharedCartService($timeout, sessionService) {
        var service = {
                getCartByItem: getCartByItem,
                getCartByUser: getCartByUser

            },
            cartByUser = {},
            cartByItem = {};

        sessionService.isLogged().then(activate);

        return service;

        function activate(userId) {
            var catalogNameRef = firebase.database().ref("/users/" +
                    userId + "/catalog");

            catalogNameRef.on("value", function (catalogNameSnapshot) {
                var catalogName = catalogNameSnapshot.val();

                var subscribersRef = firebase.database().ref("/users/" +
                        userId + "/catalogs/" + catalogName + "/subscribers");

                subscribersRef.on("value", function (snapshot) {
                    var subscribers = snapshot.val();

                    subscribeCarts(catalogName, subscribers);
                });
            });
        }

        function subscribeCarts(catalogName, subscribers) {
            if (!subscribers) {
                return;
            }

            subscribers.forEach(function (user) {
                var cartRef = firebase.database().ref("/users/" + user.id +
                            "/catalogs/" + catalogName + "/cart");

                cartRef.on("value", function (snapshot) {
                    var cart = snapshot.val();

                    $timeout(function () {
                        calcCartByUser(user, cart);
                        calcCartByItem();
                    });
                });
            });
        }

        function calcCartByUser(user, cart) {
            var myCart = [],
                total = 0;

            angular.forEach(cart, function (item) {
                myCart.push(item);
                total += item.qty * item.price;
            });

            cartByUser[user.id] = {
                name: user.name || "Myself",
                total: total,
                cart: myCart
            };
        }

        function calcCartByItem() {
            cartByItem = {};

            angular.forEach(cartByUser, function (user) {
                user.cart.forEach(function (item) {
                    var counter = 0;

                    if (cartByItem[item.item]) {
                        counter = cartByItem[item.item].total + item.qty;
                    } else {
                        counter = item.qty;
                    }

                    cartByItem[item.item] = {
                        total: counter
                    };
                });
            });
        }

        function getCartByItem() {
            return cartByItem;
        }

        function getCartByUser() {
            return cartByUser;
        }

    }

}());
<commit_msg>Fix reset shared cart on catalog change.<commit_after>"use strict";

(function () {
    angular
        .module("piadinamia")
        .factory("sharedCartService", sharedCartService);

    sharedCartService.$inject = ["$timeout", "sessionService"];

    function sharedCartService($timeout, sessionService) {
        var service = {
                getCartByItem: getCartByItem,
                getCartByUser: getCartByUser

            },
            cartByUser = {},
            cartByItem = {};

        sessionService.isLogged().then(activate);

        return service;

        function activate(userId) {
            var catalogNameRef = firebase.database().ref("/users/" +
                    userId + "/catalog");

            catalogNameRef.on("value", function (catalogNameSnapshot) {
                var catalogName = catalogNameSnapshot.val();

                var subscribersRef = firebase.database().ref("/users/" +
                        userId + "/catalogs/" + catalogName + "/subscribers");

                subscribersRef.on("value", function (snapshot) {
                    var subscribers = snapshot.val();

                    subscribeCarts(catalogName, subscribers);
                });
            });
        }

        function subscribeCarts(catalogName, subscribers) {
            if (!subscribers) {
                return;
            }

            reset(cartByUser);
            reset(cartByItem);

            subscribers.forEach(function (user) {
                var cartRef = firebase.database().ref("/users/" + user.id +
                            "/catalogs/" + catalogName + "/cart");

                cartRef.on("value", function (snapshot) {
                    var cart = snapshot.val();

                    $timeout(function () {
                        if (cart) {
                            calcCartByUser(user, cart);
                            calcCartByItem();
                        }
                    });
                });
            });
        }

        function calcCartByUser(user, cart) {
            var myCart = [],
                total = 0;

            angular.forEach(cart, function (item) {
                myCart.push(item);
                total += item.qty * item.price;
            });

            cartByUser[user.id] = {
                name: user.name || "Myself",
                total: total,
                cart: myCart
            };
        }

        function calcCartByItem() {
            reset(cartByItem);

            angular.forEach(cartByUser, function (user) {
                user.cart.forEach(function (item) {
                    var counter = 0;

                    if (cartByItem[item.item]) {
                        counter = cartByItem[item.item].total + item.qty;
                    } else {
                        counter = item.qty;
                    }

                    cartByItem[item.item] = {
                        total: counter
                    };
                });
            });
        }

        function getCartByItem() {
            return cartByItem;
        }

        function getCartByUser() {
            return cartByUser;
        }

        function reset(obj) {
            var key;

            for (key in obj) {
                if (obj.hasOwnProperty(key)) {
                    delete obj[key];
                }
            }
        }

    }

}());
<|endoftext|>


<commit_before>'use strict';

const assertJump = require('./helpers/assertJump');
const expectThrow = require('./helpers/expectThrow');
var Message = artifacts.require('./helpers/Message.sol');
var SmartTokenMock = artifacts.require('./helpers/SmartTokenMock.sol');

var BigNumber = web3.BigNumber;

require('chai')
  .use(require('chai-bignumber')(BigNumber))
  .should();

contract('SmartToken', function(accounts) {

  let token;

  beforeEach(async function() {
    token = await SmartTokenMock.new(accounts[0], 100);
  });

  it('should return the correct totalSupply after construction', async function() {
    let totalSupply = await token.totalSupply();

    assert.equal(totalSupply, 100);
  });

  it('should return the correct allowance amount after approval', async function() {
    let token = await SmartTokenMock.new();
    await token.approve(accounts[1], 100);
    let allowance = await token.allowance(accounts[0], accounts[1]);

    assert.equal(allowance, 100);
  });

  it('should return correct balances after transfer', async function() {
    await token.transfer(accounts[1], 100);
    let balance0 = await token.balanceOf(accounts[0]);
    assert.equal(balance0, 0);

    let balance1 = await token.balanceOf(accounts[1]);
    assert.equal(balance1, 100);
  });

  it('should throw an error when trying to transfer more than balance', async function() {
    try {
      await token.transfer(accounts[1], 101);
      assert.fail('should have thrown before');
    } catch(error) {
      assertJump(error);
    }
  });

  it('should return correct balances after transfering from another account', async function() {
    await token.approve(accounts[1], 100);
    await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});

    let balance0 = await token.balanceOf(accounts[0]);
    assert.equal(balance0, 0);

    let balance1 = await token.balanceOf(accounts[2]);
    assert.equal(balance1, 100);

    let balance2 = await token.balanceOf(accounts[1]);
    assert.equal(balance2, 0);
  });

  it('should throw an error when trying to transfer more than allowed', async function() {
    await token.approve(accounts[1], 99);
    try {
      await token.transferFrom(accounts[0], accounts[2], 100, {from: accounts[1]});
      assert.fail('should have thrown before');
    } catch (error) {
      assertJump(error);
    }
  });

  it('should throw an error when trying to transferFrom more than _from has', async function() {
    let balance0 = await token.balanceOf(accounts[0]);
    await token.approve(accounts[1], 99);
    try {
      await token.transferFrom(accounts[0], accounts[2], balance0+1, {from: accounts[1]});
      assert.fail('should have thrown before');
    } catch (error) {
      assertJump(error);
    }
  });

  describe('validating allowance updates to spender', function() {
    let preApproved;

    it('should start with zero', async function() {
      preApproved = await token.allowance(accounts[0], accounts[1]);
      assert.equal(preApproved, 0);
    })

    it('should increase by 50 then decrease by 10', async function() {
      await token.increaseApproval(accounts[1], 50);
      let postIncrease = await token.allowance(accounts[0], accounts[1]);
      preApproved.plus(50).should.be.bignumber.equal(postIncrease);
      await token.decreaseApproval(accounts[1], 10);
      let postDecrease = await token.allowance(accounts[0], accounts[1]);
      postIncrease.minus(10).should.be.bignumber.equal(postDecrease);
    })
  });

  it('should increase by 50 then set to 0 when decreasing by more than 50', async function() {
    await token.approve(accounts[1], 50);
    await token.decreaseApproval(accounts[1], 60);
    let postDecrease = await token.allowance(accounts[0], accounts[1]);
    postDecrease.should.be.bignumber.equal(0);
  });

  it('should throw an error when trying to transfer to 0x0', async function() {
    try {
      let transfer = await token.transfer(0x0, 100);
      assert.fail('should have thrown before');
    } catch(error) {
      assertJump(error);
    }
  });

  it('should throw an error when trying to transferFrom to 0x0', async function() {
    await token.approve(accounts[1], 100);
    try {
      let transfer = await token.transferFrom(accounts[0], 0x0, 100, {from: accounts[1]});
      assert.fail('should have thrown before');
    } catch(error) {
      assertJump(error);
    }
  });

  it('should return correct balances after approve and show the event on receiver contract', async function() {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.approveData(
      message.contract.address, 100, data, {from: accounts[0]}
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], message.contract.address)
    );

  });

  it('should return correct balances after transferData and show the event on receiver contract', async function() {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.transferData(
      message.contract.address, 100, data, {from: accounts[0]}
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.balanceOf(message.contract.address)
    );

  });

  it('should return correct allowance after approveData and show the event on receiver contract', async function() {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.approveData(
      message.contract.address, 100, data, {from: accounts[0]}
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], message.contract.address)
    );

  });

  it('should return correct balances after transferFrom and show the event on receiver contract', async function() {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    await token.approve(accounts[1], 100, {from: accounts[0]});

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], accounts[1])
    );

    let transaction = await token.transferDataFrom(
      accounts[0], message.contract.address, 100, data, {from: accounts[1]}
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.balanceOf(message.contract.address)
    );

  });

  it('should fail inside approveData', async function() {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    try {
      await token.approveData(
        message.contract.address, 10, data,
        {from: accounts[1]}
      );
      assert(false, 'approveData should have raised');
    } catch(error) {
      assertJump(error);
    }

    // approval should not have gone through so allowance is still 0
    new BigNumber(0).should.be.bignumber
      .equal(await token.allowance(accounts[1], message.contract.address));

  });

  it('should fail inside transferData', async function() {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    try {
      await token.transferData(
        message.contract.address, 10, data,
        {from: accounts[0]}
      );
      assert(false, 'transferData should have failed');
    } catch(error) {
      assertJump(error);
    }

    // transfer should not have gone through, so balance is still 0
    new BigNumber(0).should.be.bignumber
      .equal(await token.balanceOf(message.contract.address));

  });

  it('should fail inside transferDataFrom', async function() {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    await token.approve(accounts[1], 10, {from: accounts[2]});

    try {
      await token.transferDataFrom(
        accounts[2], message.contract.address, 10, data,
        {from: accounts[1]}
      );
      assert(false, 'transferDataFrom should have thrown');
    } catch(error) {
      assertJump(error);
    }

    // transferDataFrom should have failed so balance is still 0 but allowance is 10
    new BigNumber(10).should.be.bignumber
      .equal(await token.allowance(accounts[2], accounts[1]));
    new BigNumber(0).should.be.bignumber
      .equal(await token.balanceOf(message.contract.address));

  });

  it('should fail approveData when using token contract address as receiver', async function() {
    let data = token.contract.approve.getData(accounts[5], 66);

    try {
      await token.approveData(token.contract.address, 100, data, {from: accounts[0]});
      assert(false, 'approveData should have thrown because the spender cannot be the token itself');
    } catch(error) {
      assertJump(error);
    }

  });

  it('should fail transferData when using token contract address as receiver', async function() {

    try {
      await token.transferData(
        token.contract.address, 100, web3.toHex(0), {from: accounts[0]}
      );
      assert(false, 'transferData should have thrown because the spender cannot be the token itself');
    } catch(error) {
      assertJump(error);
    }

  });

  it('should fail transferDataFrom when using token contract address as receiver', async function() {

    await token.approve(accounts[1], 1, {from: accounts[0]});

    try {
      await token.transferDataFrom(
        accounts[0], token.contract.address, 1, web3.toHex(0), {from: accounts[1]}
      );
      assert(false, 'transferDataFrom should have thrown because the spender cannot be the token itself');
    } catch(error) {
      assertJump(error);
    }

  });

});
<commit_msg>change SmartToken test to work with new linter rules<commit_after>
import EVMRevert from './helpers/EVMRevert';
var Message = artifacts.require('./mock/MessageHelper.sol');
var SmartTokenMock = artifacts.require('./mock/SmartTokenMock.sol');

var BigNumber = web3.BigNumber;

require('chai')
  .use(require('chai-as-promised'))
  .use(require('chai-bignumber')(BigNumber))
  .should();

contract('SmartToken', function (accounts) {
  let token;

  beforeEach(async function () {
    token = await SmartTokenMock.new(accounts[0], 100);
  });

  it('should return the correct totalSupply after construction', async function () {
    let totalSupply = await token.totalSupply();

    assert.equal(totalSupply, 100);
  });

  it('should return the correct allowance amount after approval', async function () {
    let token = await SmartTokenMock.new();
    await token.approve(accounts[1], 100);
    let allowance = await token.allowance(accounts[0], accounts[1]);

    assert.equal(allowance, 100);
  });

  it('should return correct balances after transfer', async function () {
    await token.transfer(accounts[1], 100);
    let balance0 = await token.balanceOf(accounts[0]);
    assert.equal(balance0, 0);

    let balance1 = await token.balanceOf(accounts[1]);
    assert.equal(balance1, 100);
  });

  it('should throw an error when trying to transfer more than balance', async function () {
    await token.transfer(accounts[1], 101).should.be.rejectedWith(EVMRevert);
  });

  it('should return correct balances after transfering from another account', async function () {
    await token.approve(accounts[1], 100);
    await token.transferFrom(accounts[0], accounts[2], 100, { from: accounts[1] });

    let balance0 = await token.balanceOf(accounts[0]);
    assert.equal(balance0, 0);

    let balance1 = await token.balanceOf(accounts[2]);
    assert.equal(balance1, 100);

    let balance2 = await token.balanceOf(accounts[1]);
    assert.equal(balance2, 0);
  });

  it('should throw an error when trying to transfer more than allowed', async function () {
    await token.approve(accounts[1], 99);
    await token.transferFrom(
      accounts[0], accounts[2], 100,
      { from: accounts[1] }
    ).should.be.rejectedWith(EVMRevert);
  });

  it('should throw an error when trying to transferFrom more than _from has', async function () {
    let balance0 = await token.balanceOf(accounts[0]);
    await token.approve(accounts[1], 99);
    await token.transferFrom(
      accounts[0], accounts[2], balance0 + 1,
      { from: accounts[1] }
    ).should.be.rejectedWith(EVMRevert);
  });

  describe('validating allowance updates to spender', function () {
    let preApproved;

    it('should start with zero', async function () {
      preApproved = await token.allowance(accounts[0], accounts[1]);
      assert.equal(preApproved, 0);
    });

    it('should increase by 50 then decrease by 10', async function () {
      await token.increaseApproval(accounts[1], 50);
      let postIncrease = await token.allowance(accounts[0], accounts[1]);
      preApproved.plus(50).should.be.bignumber.equal(postIncrease);
      await token.decreaseApproval(accounts[1], 10);
      let postDecrease = await token.allowance(accounts[0], accounts[1]);
      postIncrease.minus(10).should.be.bignumber.equal(postDecrease);
    });
  });

  it('should increase by 50 then set to 0 when decreasing by more than 50', async function () {
    await token.approve(accounts[1], 50);
    await token.decreaseApproval(accounts[1], 60);
    let postDecrease = await token.allowance(accounts[0], accounts[1]);
    postDecrease.should.be.bignumber.equal(0);
  });

  it('should throw an error when trying to transfer to 0x0', async function () {
    await token.transfer(0x0, 100).should.be.rejectedWith(EVMRevert);
  });

  it('should throw an error when trying to transferFrom to 0x0', async function () {
    await token.approve(accounts[1], 100);
    await token.transferFrom(accounts[0], 0x0, 100, { from: accounts[1] })
      .should.be.rejectedWith(EVMRevert);
  });

  it('should return correct balances after approve and show the event on receiver contract', async function () {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.approveData(
      message.contract.address, 100, data, { from: accounts[0] }
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], message.contract.address)
    );
  });

  it('should return correct balances after transferData and show the event on receiver contract', async function () {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.transferData(
      message.contract.address, 100, data, { from: accounts[0] }
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.balanceOf(message.contract.address)
    );
  });

  it('should return correct allowance after approveData and show the event on receiver contract', async function () {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    let transaction = await token.approveData(
      message.contract.address, 100, data, { from: accounts[0] }
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], message.contract.address)
    );
  });

  it('should return correct balances after transferFrom and show the event on receiver contract', async function () {
    let message = await Message.new();

    let data = message.contract.showMessage.getData(
      web3.toHex(123456), 666, 'Transfer Done'
    );

    await token.approve(accounts[1], 100, { from: accounts[0] });

    new BigNumber(100).should.be.bignumber.equal(
      await token.allowance(accounts[0], accounts[1])
    );

    let transaction = await token.transferDataFrom(
      accounts[0], message.contract.address, 100, data, { from: accounts[1] }
    );

    assert.equal(2, transaction.receipt.logs.length);

    new BigNumber(100).should.be.bignumber.equal(
      await token.balanceOf(message.contract.address)
    );
  });

  it('should fail inside approveData', async function () {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    await token.approveData(
      message.contract.address, 10, data,
      { from: accounts[1] }
    ).should.be.rejectedWith(EVMRevert);

    // approval should not have gone through so allowance is still 0
    new BigNumber(0).should.be.bignumber
      .equal(await token.allowance(accounts[1], message.contract.address));
  });

  it('should fail inside transferData', async function () {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    await token.transferData(
      message.contract.address, 10, data,
      { from: accounts[0] }
    ).should.be.rejectedWith(EVMRevert);

    // transfer should not have gone through, so balance is still 0
    new BigNumber(0).should.be.bignumber
      .equal(await token.balanceOf(message.contract.address));
  });

  it('should fail inside transferDataFrom', async function () {
    let message = await Message.new();

    let data = message.contract.fail.getData();

    await token.approve(accounts[1], 10, { from: accounts[2] });

    await token.transferDataFrom(
      accounts[2], message.contract.address, 10, data,
      { from: accounts[1] }
    ).should.be.rejectedWith(EVMRevert);

    // transferDataFrom should have failed so balance is still 0 but allowance is 10
    new BigNumber(10).should.be.bignumber
      .equal(await token.allowance(accounts[2], accounts[1]));
    new BigNumber(0).should.be.bignumber
      .equal(await token.balanceOf(message.contract.address));
  });

  it('should fail approveData when using token contract address as receiver', async function () {
    let data = token.contract.approve.getData(accounts[5], 66);

    await token.approveData(
      token.contract.address, 100, data,
      { from: accounts[0] }
    ).should.be.rejectedWith(EVMRevert);
  });

  it('should fail transferData when using token contract address as receiver', async function () {
    await token.transferData(
      token.contract.address, 100, web3.toHex(0), { from: accounts[0] }
    ).should.be.rejectedWith(EVMRevert);
  });

  it('should fail transferDataFrom when using token contract address as receiver', async function () {
    await token.approve(accounts[1], 1, { from: accounts[0] });
    await token.transferDataFrom(
      accounts[0], token.contract.address, 1, web3.toHex(0), { from: accounts[1] }
    ).should.be.rejectedWith(EVMRevert);
  });
});
<|endoftext|>


<commit_before><commit_msg>forgot a fu**** debug.console statement<commit_after><|endoftext|>


<commit_before>var _ = require("lodash");
var identd = require("../../identd");
var Msg = require("../../models/msg");

module.exports = function(irc, network) {
	var client = this;
	var identHandler = this.manager.identHandler;

	network.channels[0].pushMessage(client, new Msg({
		text: "Network created, connecting to " + network.host + ":" + network.port + "..."
	}));

	irc.on("registered", function() {
		if (network.irc.network.cap.enabled.length > 0) {
			network.channels[0].pushMessage(client, new Msg({
				text: "Enabled capabilities: " + network.irc.network.cap.enabled.join(", ")
			}));
		}

		var delay = 1000;
		var commands = network.commands;
		if (Array.isArray(commands)) {
			commands.forEach(function(cmd) {
				setTimeout(function() {
					client.input({
						target: network.channels[0].id,
						text: cmd
					});
				}, delay);
				delay += 1000;
			});
		}

		network.channels.forEach(function(chan) {
			setTimeout(function() {
				network.irc.join(chan.name);
			}, delay);
			delay += 1000;
		});
	});

	irc.on("socket connected", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Connected to the network."
		}));
	});

	irc.on("close", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network, and will not reconnect. Use /connect to reconnect again."
		}));
	});

	if (identd.isEnabled()) {
		irc.on("socket connected", function() {
			identd.hook(irc.connection.socket, client.name || network.username);
		});
	}

	if (identHandler) {
		irc.on("socket connected", function() {
			identHandler.addSocket(irc.connection.socket, client.name || network.username);
			identHandler.refresh();
		});

		irc.on("socket close", function() {
			identHandler.removeSocket(irc.connection.socket);
			identHandler.refresh();
		});
	}

	irc.on("debug", function(message) {
		log.debug("[" + client.name + " on " + network.host + ":" + network.port + "]", message);
	});

	irc.on("socket error", function(err) {
		network.channels[0].pushMessage(client, new Msg({
			type: Msg.Type.ERROR,
			text: "Socket error: " + err
		}));
	});

	irc.on("reconnecting", function(data) {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network. Reconnecting in " + Math.round(data.wait / 1000) + " seconds… (Attempt " + data.attempt + " of " + data.max_retries + ")"
		}));
	});

	irc.on("ping timeout", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Ping timeout, disconnecting…"
		}));
	});

	irc.on("server options", function(data) {
		if (network.serverOptions.PREFIX === data.options.PREFIX) {
			return;
		}

		network.prefixLookup = {};

		_.each(data.options.PREFIX, function(mode) {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.serverOptions.PREFIX = data.options.PREFIX;

		client.emit("network_changed", {
			network: network.id,
			serverOptions: network.serverOptions
		});
	});
};
<commit_msg>Print network name and internal ids in debug log<commit_after>var _ = require("lodash");
var identd = require("../../identd");
var Msg = require("../../models/msg");

module.exports = function(irc, network) {
	var client = this;
	var identHandler = this.manager.identHandler;

	network.channels[0].pushMessage(client, new Msg({
		text: "Network created, connecting to " + network.host + ":" + network.port + "..."
	}));

	irc.on("registered", function() {
		if (network.irc.network.cap.enabled.length > 0) {
			network.channels[0].pushMessage(client, new Msg({
				text: "Enabled capabilities: " + network.irc.network.cap.enabled.join(", ")
			}));
		}

		var delay = 1000;
		var commands = network.commands;
		if (Array.isArray(commands)) {
			commands.forEach(function(cmd) {
				setTimeout(function() {
					client.input({
						target: network.channels[0].id,
						text: cmd
					});
				}, delay);
				delay += 1000;
			});
		}

		network.channels.forEach(function(chan) {
			setTimeout(function() {
				network.irc.join(chan.name);
			}, delay);
			delay += 1000;
		});
	});

	irc.on("socket connected", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Connected to the network."
		}));
	});

	irc.on("close", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network, and will not reconnect. Use /connect to reconnect again."
		}));
	});

	if (identd.isEnabled()) {
		irc.on("socket connected", function() {
			identd.hook(irc.connection.socket, client.name || network.username);
		});
	}

	if (identHandler) {
		irc.on("socket connected", function() {
			identHandler.addSocket(irc.connection.socket, client.name || network.username);
			identHandler.refresh();
		});

		irc.on("socket close", function() {
			identHandler.removeSocket(irc.connection.socket);
			identHandler.refresh();
		});
	}

	irc.on("debug", function(message) {
		log.debug("[" + client.name + " (#" + client.id + ") on " + network.name + " (#" + network.id + ")]", message);
	});

	irc.on("socket error", function(err) {
		network.channels[0].pushMessage(client, new Msg({
			type: Msg.Type.ERROR,
			text: "Socket error: " + err
		}));
	});

	irc.on("reconnecting", function(data) {
		network.channels[0].pushMessage(client, new Msg({
			text: "Disconnected from the network. Reconnecting in " + Math.round(data.wait / 1000) + " seconds… (Attempt " + data.attempt + " of " + data.max_retries + ")"
		}));
	});

	irc.on("ping timeout", function() {
		network.channels[0].pushMessage(client, new Msg({
			text: "Ping timeout, disconnecting…"
		}));
	});

	irc.on("server options", function(data) {
		if (network.serverOptions.PREFIX === data.options.PREFIX) {
			return;
		}

		network.prefixLookup = {};

		_.each(data.options.PREFIX, function(mode) {
			network.prefixLookup[mode.mode] = mode.symbol;
		});

		network.serverOptions.PREFIX = data.options.PREFIX;

		client.emit("network_changed", {
			network: network.id,
			serverOptions: network.serverOptions
		});
	});
};
<|endoftext|>


<commit_before>/*
 * (C) Copyright 2015 Kurento (http://kurento.org/)
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 */

/**
 * {@link WebRtcPeer} test suite.
 *
 *

 * Methods tested:
 *

 *
{@link WebRtcEndpoint#getLocalSessionDescriptor()}
 *

 *

 * Events tested:
 *

 *
{@link WebRtcEndpoint#addMediaSessionStartListener(MediaEventListener)}
 *

 * {@link HttpEndpoint#addMediaSessionTerminatedListener(MediaEventListener)}
 *

 *
 *
 * @author Jesús Leganés Combarro "piranna" (piranna@gmail.com)
 * @since 4.2.4
 *
 */

if(typeof QUnit == 'undefined')
{
  QUnit = require('qunit-cli');
  QUnit.load();

  kurentoUtils = require('..');

  require('./_common');
};

var WebRtcPeerRecvonly = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
var WebRtcPeerSendonly = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
var WebRtcPeerSendrecv = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv;

function getOscillatorMedia()
{
  var ac = new AudioContext();
  var osc = ac.createOscillator();
  var dest = ac.createMediaStreamDestination();
  osc.connect(dest);

  return dest.stream;
}

QUnit.module('WebRtcPeer');

QUnit.test('WebRtcPeerRecvonly', function(assert)
{
  var done = assert.async();

  assert.expect(1);

  var options =
  {
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerRecvonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = getOscillatorMedia()

      peerConnection.addStream(stream)

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            var stream = this.getRemoteStream()
            assert.notEqual(stream, undefined, 'remote stream')

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('WebRtcPeerSendonly', function(assert)
{
  var done = assert.async();

  assert.expect(2);

  var options =
  {
    audioStream: getOscillatorMedia(),
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var stream = this.getLocalStream()
    assert.notEqual(stream, undefined, 'local stream')

    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = peerConnection.getRemoteStreams()[0]
      assert.notEqual(stream, undefined, 'peer remote stream')

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('WebRtcPeerSendrecv', function(assert)
{
  var done = assert.async();

  assert.expect(3);

  var options =
  {
    audioStream: getOscillatorMedia(),
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendrecv(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var stream = this.getLocalStream()
    assert.notEqual(stream, undefined, 'local stream')

    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = peerConnection.getRemoteStreams()[0]
      assert.notEqual(stream, undefined, 'peer remote stream')

      var stream = getOscillatorMedia();

      peerConnection.addStream(stream)

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            var stream = this.getRemoteStream()
            assert.notEqual(stream, undefined, 'remote stream')

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('videoEnabled', function(assert)
{
  var done = assert.async();

  assert.expect(3);

  var video   = document.getElementById('localVideo')
  var canvas  = document.getElementById('canvas')
  var context = canvas.getContext('2d');

  var options =
  {
    configuration: {iceServers: []},
    localVideo: video,
    mediaConstraints: {audio: false}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpoffer)
  {
    setTimeout(function()
    {
      canvas.width  = video.videoWidth;
      canvas.height = video.videoHeight;

      var x = video.videoWidth  / 2
      var y = video.videoHeight / 2

      context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
      assert.notPixelEqual(canvas, x,y, 0,0,0,0, 'enabled');

      webRtcPeer.videoEnabled = false

      setTimeout(function()
      {
        context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
        assert.pixelEqual(canvas, x,y, 0,0,0,255, 'disabled');

        webRtcPeer.videoEnabled = true

        setTimeout(function()
        {
          context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
          assert.notPixelEqual(canvas, x,y, 0,0,0,255, 'enabled again');

          webRtcPeer.dispose()
          done()
        }, 0)
      }, 0)
    }, 1000)
  })
});
<commit_msg>Test for 'audioEnabled' property<commit_after>/*
 * (C) Copyright 2015 Kurento (http://kurento.org/)
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 */

/**
 * {@link WebRtcPeer} test suite.
 *
 *

 * Methods tested:
 *

 *
{@link WebRtcEndpoint#getLocalSessionDescriptor()}
 *

 *

 * Events tested:
 *

 *
{@link WebRtcEndpoint#addMediaSessionStartListener(MediaEventListener)}
 *

 * {@link HttpEndpoint#addMediaSessionTerminatedListener(MediaEventListener)}
 *

 *
 *
 * @author Jesús Leganés Combarro "piranna" (piranna@gmail.com)
 * @since 4.2.4
 *
 */

if(typeof QUnit == 'undefined')
{
  QUnit = require('qunit-cli');
  QUnit.load();

  kurentoUtils = require('..');

  require('./_common');
};

var WebRtcPeerRecvonly = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
var WebRtcPeerSendonly = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
var WebRtcPeerSendrecv = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv;

function getOscillatorMedia()
{
  var ac = new AudioContext();
  var osc = ac.createOscillator();
  var dest = ac.createMediaStreamDestination();
  osc.connect(dest);

  return dest.stream;
}

QUnit.module('WebRtcPeer');

QUnit.test('WebRtcPeerRecvonly', function(assert)
{
  var done = assert.async();

  assert.expect(1);

  var options =
  {
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerRecvonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = getOscillatorMedia()

      peerConnection.addStream(stream)

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            var stream = this.getRemoteStream()
            assert.notEqual(stream, undefined, 'remote stream')

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('WebRtcPeerSendonly', function(assert)
{
  var done = assert.async();

  assert.expect(2);

  var options =
  {
    audioStream: getOscillatorMedia(),
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var stream = this.getLocalStream()
    assert.notEqual(stream, undefined, 'local stream')

    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = peerConnection.getRemoteStreams()[0]
      assert.notEqual(stream, undefined, 'peer remote stream')

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('WebRtcPeerSendrecv', function(assert)
{
  var done = assert.async();

  assert.expect(3);

  var options =
  {
    audioStream: getOscillatorMedia(),
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendrecv(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpOffer)
  {
    var stream = this.getLocalStream()
    assert.notEqual(stream, undefined, 'local stream')

    var offer = new RTCSessionDescription(
    {
      type: 'offer',
      sdp:  sdpOffer
    });

    var peerConnection = new RTCPeerConnection()

    peerConnection.setRemoteDescription(offer, function()
    {
      var stream = peerConnection.getRemoteStreams()[0]
      assert.notEqual(stream, undefined, 'peer remote stream')

      var stream = getOscillatorMedia();

      peerConnection.addStream(stream)

      peerConnection.createAnswer(function(answer)
      {
        peerConnection.setLocalDescription(answer, function()
        {
          webRtcPeer.processSdpAnswer(answer.sdp, function(error)
          {
            if(error) return onerror(error)

            var stream = this.getRemoteStream()
            assert.notEqual(stream, undefined, 'remote stream')

            this.dispose()
            done()
          })
        },
        onerror);
      },
      onerror);
    },
    onerror)
  })
});

QUnit.test('audioEnabled', function(assert)
{
  var done = assert.async();

  assert.expect(3);

  var options =
  {
    audioStream: getOscillatorMedia(),
    configuration: {iceServers: []}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpoffer)
  {
    var stream = this.getLocalStream()
    var track = stream.getAudioTracks()[0]

    assert.ok(track.enabled, 'enabled')

    this.audioEnabled = false
    assert.ok(!track.enabled, 'disabled')

    this.audioEnabled = true
    assert.ok(track.enabled, 'enabled again')

    webRtcPeer.dispose()
    done()
  })
});

QUnit.test('videoEnabled', function(assert)
{
  var done = assert.async();

  assert.expect(3);

  var video   = document.getElementById('localVideo')
  var canvas  = document.getElementById('canvas')
  var context = canvas.getContext('2d');

  var options =
  {
    configuration: {iceServers: []},
    localVideo: video,
    mediaConstraints: {audio: false}
  }

  function onerror(error)
  {
    webRtcPeer && webRtcPeer.dispose()

    _onerror(error)
    done()
  }

  var webRtcPeer = new WebRtcPeerSendonly(options)

  webRtcPeer.on('error', onerror)
  webRtcPeer.on('sdpoffer', function(sdpoffer)
  {
    setTimeout(function()
    {
      canvas.width  = video.videoWidth;
      canvas.height = video.videoHeight;

      var x = video.videoWidth  / 2
      var y = video.videoHeight / 2

      context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
      assert.notPixelEqual(canvas, x,y, 0,0,0,0, 'enabled');

      webRtcPeer.videoEnabled = false

      setTimeout(function()
      {
        context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
        assert.pixelEqual(canvas, x,y, 0,0,0,255, 'disabled');

        webRtcPeer.videoEnabled = true

        setTimeout(function()
        {
          context.drawImage(video, 0,0,video.videoWidth,video.videoHeight)
          assert.notPixelEqual(canvas, x,y, 0,0,0,255, 'enabled again');

          webRtcPeer.dispose()
          done()
        }, 0)
      }, 0)
    }, 1000)
  })
});
<|endoftext|>