diff --git a/copay.js b/copay.js index 79a70f8b0..fc4f7d4ec 100644 --- a/copay.js +++ b/copay.js @@ -6,7 +6,7 @@ module.exports.PrivateKey = require('./js/models/PrivateKey'); module.exports.HDPath = require('./js/models/HDPath'); module.exports.HDParams = require('./js/models/HDParams'); module.exports.crypto = require('./js/util/crypto'); -module.exports.logger = require('./js/log'); +module.exports.logger = require('./js/util/log'); // components diff --git a/js/models/Async.js b/js/models/Async.js index 067cc50f1..505b7826e 100644 --- a/js/models/Async.js +++ b/js/models/Async.js @@ -2,7 +2,7 @@ var EventEmitter = require('events').EventEmitter; var bitcore = require('bitcore'); -var log = require('../log'); +var log = require('../util/log'); var AuthMessage = bitcore.AuthMessage; var util = bitcore.util; var nodeUtil = require('util'); diff --git a/js/models/Compatibility.js b/js/models/Compatibility.js index d42eb9be0..bab4f5b57 100644 --- a/js/models/Compatibility.js +++ b/js/models/Compatibility.js @@ -5,7 +5,7 @@ var Wallet = require('./Wallet'); var cryptoUtils = require('../util/crypto'); var CryptoJS = require('node-cryptojs-aes').CryptoJS; var sjcl = require('../../lib/sjcl'); -var log = require('../log'); +var log = require('../util/log'); var preconditions = require('preconditions').instance(); var _ = require('lodash'); diff --git a/js/models/Identity.js b/js/models/Identity.js index 53984fbdf..d404fc9f3 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -3,7 +3,6 @@ var _ = require('lodash'); var preconditions = require('preconditions').singleton(); var inherits = require('inherits'); var events = require('events'); -var log = require('../log'); var async = require('async'); var bitcore = require('bitcore'); @@ -14,9 +13,9 @@ var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); var PluginManager = require('./PluginManager'); var Async = require('./Async'); - -var version = require('../../version').version; var cryptoUtil = require('../util/crypto'); +var log = require('../util/log'); +var version = require('../../version').version; /** * @desc diff --git a/js/models/Insight.js b/js/models/Insight.js index e1cde179a..d07484f59 100644 --- a/js/models/Insight.js +++ b/js/models/Insight.js @@ -3,14 +3,16 @@ var util = require('util'); var async = require('async'); var request = require('request'); -var bitcore = require('bitcore'); var io = require('socket.io-client'); var _ = require('lodash'); -var log = require('../log'); - var EventEmitter = require('events').EventEmitter; var preconditions = require('preconditions').singleton(); +var bitcore = require('bitcore'); + +var log = require('../util/log.js'); + + /* This class lets interfaces with the blockchain, making general queries and subscribing to transactions on adressess and blocks. diff --git a/js/models/PluginManager.js b/js/models/PluginManager.js index 2a2788b61..d2d8d57fc 100644 --- a/js/models/PluginManager.js +++ b/js/models/PluginManager.js @@ -1,6 +1,6 @@ 'use strict'; var preconditions = require('preconditions').singleton(); -var log = require('../log'); +var log = require('../util/log'); function PluginManager(config) { this.registered = {}; diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index 6963e8bab..9bfe0caab 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -2,7 +2,7 @@ var preconditions = require('preconditions').instance(); var _ = require('lodash'); -var log = require('../log'); +var log = require('../util/log'); var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; var Address = bitcore.Address; @@ -110,7 +110,7 @@ PublicKeyRing.fromObj = function(opts) { } if (opts.cache && opts.cache.addressToPath) { - log.info('PublicKeyRing: Using address cache'); + log.debug('PublicKeyRing: Using address cache'); pkr.cache.addressToPath = opts.cache.addressToPath; pkr.rebuildCache(); } diff --git a/js/models/RateService.js b/js/models/RateService.js index c0819f8c2..aec56366d 100644 --- a/js/models/RateService.js +++ b/js/models/RateService.js @@ -2,7 +2,7 @@ var util = require('util'); var _ = require('lodash'); -var log = require('../log'); +var log = require('../util/log'); var preconditions = require('preconditions').singleton(); var request = require('request'); diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js index 036dd5b63..8f4fe53c7 100644 --- a/js/models/TxProposal.js +++ b/js/models/TxProposal.js @@ -10,7 +10,7 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Script = bitcore.Script; var Key = bitcore.Key; -var log = require('../log'); +var log = require('../util/log'); var TX_MAX_SIZE_KB = 50; var VERSION = 1; diff --git a/js/models/TxProposals.js b/js/models/TxProposals.js index 477f276a2..074091ad5 100644 --- a/js/models/TxProposals.js +++ b/js/models/TxProposals.js @@ -9,7 +9,7 @@ var Script = bitcore.Script; var Key = bitcore.Key; var buffertools = bitcore.buffertools; -var log = require('../log'); +var log = require('../util/log'); var TxProposal = require('./TxProposal');; function TxProposals(opts) { diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 13a90cfd4..c61eb1e93 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -18,7 +18,7 @@ var Address = bitcore.Address; var PayPro = bitcore.PayPro; var Transaction = bitcore.Transaction; -var log = require('../log'); +var log = require('../util/log'); var cryptoUtil = require('../util/crypto'); var httpUtil = require('../util/HTTP'); var HDParams = require('./HDParams'); diff --git a/js/plugins/EncryptedInsightStorage.js b/js/plugins/EncryptedInsightStorage.js index 0081dc0e3..d6fd4f0f7 100644 --- a/js/plugins/EncryptedInsightStorage.js +++ b/js/plugins/EncryptedInsightStorage.js @@ -1,7 +1,7 @@ var cryptoUtil = require('../util/crypto'); var InsightStorage = require('./InsightStorage'); var inherits = require('inherits'); -var log = require('../log'); +var log = require('../util/log'); var SEPARATOR = '%^#@'; function EncryptedInsightStorage(config) { diff --git a/js/plugins/EncryptedLocalStorage.js b/js/plugins/EncryptedLocalStorage.js index 89c104cab..591308f17 100644 --- a/js/plugins/EncryptedLocalStorage.js +++ b/js/plugins/EncryptedLocalStorage.js @@ -1,5 +1,5 @@ var cryptoUtil = require('../util/crypto'); -var log = require('../log'); +var log = require('../util/log'); var LocalStorage = require('./LocalStorage'); var inherits = require('inherits'); diff --git a/js/plugins/GoogleDrive.js b/js/plugins/GoogleDrive.js index 599314961..b786fca4b 100644 --- a/js/plugins/GoogleDrive.js +++ b/js/plugins/GoogleDrive.js @@ -3,7 +3,7 @@ var preconditions = require('preconditions').singleton(); var loaded = 0; var SCOPES = 'https://www.googleapis.com/auth/drive'; -var log = require('../log'); +var log = require('../util/log'); function GoogleDrive(config) { preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config'); diff --git a/js/plugins/InsightStorage.js b/js/plugins/InsightStorage.js index 26f5fda2f..522444c3e 100644 --- a/js/plugins/InsightStorage.js +++ b/js/plugins/InsightStorage.js @@ -4,7 +4,7 @@ var bitcore = require('bitcore'); var buffers = require('buffer'); var querystring = require('querystring'); var Identity = require('../models/Identity'); -var log = require('../log'); +var log = require('../util/log'); var SEPARATOR = '|'; diff --git a/js/plugins/LocalStorage.js b/js/plugins/LocalStorage.js index ccdfaa1a8..0692a13ae 100644 --- a/js/plugins/LocalStorage.js +++ b/js/plugins/LocalStorage.js @@ -1,8 +1,12 @@ 'use strict'; var _ = require('lodash'); +var preconditions = require('preconditions').singleton(); function LocalStorage() { this.type = 'DB'; + + preconditions.checkState(typeof localStorage !== 'undefined', + 'localstorage not available, cannot run plugin'); }; LocalStorage.prototype.init = function() { diff --git a/js/util/HTTP.js b/js/util/HTTP.js index 00daa6d93..e900c5334 100644 --- a/js/util/HTTP.js +++ b/js/util/HTTP.js @@ -1,3 +1,5 @@ +var preconditions = require('preconditions').singleton(); + module.exports = { request: function(options, callback) { preconditions.checkArgument(_.isObject(options)); @@ -25,9 +27,9 @@ module.exports = { var req = options; req.headers = req.headers || {}; - req.body = req.body || req.data || {}; + req.body = req.body || req.data || ''; - var xhr = new XMLHttpRequest(); + var xhr = options.xhr || new XMLHttpRequest(); xhr.open(method, url, true); Object.keys(req.headers).forEach(function(key) { @@ -51,6 +53,7 @@ module.exports = { headers[$1.toLowerCase()] = $2; } ); + return ret._success(buf, xhr.status, headers, options); }; diff --git a/js/util/crypto.js b/js/util/crypto.js index 3497207f6..cb438ea18 100644 --- a/js/util/crypto.js +++ b/js/util/crypto.js @@ -2,8 +2,9 @@ * Small module for some helpers that wrap sjcl with some good practices. */ var sjcl = require('sjcl'); -var log = require('../log.js'); var _ = require('lodash'); + +var log = require('../util/log.js'); var config = require('../../config'); var defaultSalt = (config && config.passphraseConfig && config.passphraseConfig.storageSalt) || 'mjuBtGybi/4='; diff --git a/js/log.js b/js/util/log.js similarity index 85% rename from js/log.js rename to js/util/log.js index 1d7c7c776..27a2f02aa 100644 --- a/js/log.js +++ b/js/util/log.js @@ -1,6 +1,11 @@ -var config = config || require('../config'); +var config = config || require('../../config'); var _ = require('lodash'); +var ls; +try { + var LS = require('../plugins/LocalStorage'); + ls = new LS(); +} catch(e) {}; /** * @desc @@ -119,13 +124,18 @@ Logger.prototype.setLevel = function(level) { var logger = new Logger('copay'); var error = new Error(); -var logLevel = config.logLevel; +var logLevel = config.logLevel || 'info'; -if (typeof localStorage !== "undefined" && localStorage.getItem) { - var localConfig = JSON.parse(localStorage.getItem("config")); - if (localConfig && localConfig.logLevel) - logLevel = localConfig.logLevel; +if (ls && ls.getItem) { + ls.getItem("config", function(err, value) { + if (err) return; + var localConfig = JSON.parse(value); + if (localConfig && localConfig.logLevel) + logLevel = localConfig.logLevel; + logger.setLevel(logLevel); + }); +} else { + logger.setLevel(logLevel); } -logger.setLevel(logLevel); module.exports = logger; diff --git a/karma.conf.js b/karma.conf.js index 650d409c1..a194c5a3a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -45,7 +45,6 @@ module.exports = function(config) { //App-specific Code 'js/app.js', - 'js/log.js', 'js/routes.js', 'js/services/*.js', 'js/directives.js', diff --git a/package.json b/package.json index e556cb370..108961d7e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "scripts": { "start": "node server.js", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter spec test", + "coverage": "./node_modules/.bin/istanbul cover -x lib/sjcl.js ./node_modules/.bin/_mocha -- --reporter spec test", "test": "sh test/run.sh", "dist": "node shell/scripts/dist.js", "shell": "node shell/scripts/launch.js", diff --git a/test/util.crypto.js b/test/util.crypto.js index 765e537ba..c6171e1ea 100644 --- a/test/util.crypto.js +++ b/test/util.crypto.js @@ -14,6 +14,19 @@ describe('crypto utils', function() { decrypted.should.equal(message); }); + + it('should decrypt what it encrypts (JSON)', function() { + + var key = 'My secret key'; + var message = {'hola': 'picho'}; + var encrypted = cryptoUtils.encrypt(key, message); + var decrypted = cryptoUtils.decrypt(key, encrypted); + + JSON.parse(decrypted).should.deep.equal(message); + }); + + + it('should return null if the provided key cant decrypt', function() { var key = 'My secret key'; var message = 'My secret message'; @@ -22,6 +35,17 @@ describe('crypto utils', function() { assert(decrypted === null); }); + + + + it('should sign a message', function() { + var key = 'My secret key'; + var message = 'My secret message'; + var signature = cryptoUtils.hmac(key, message); + signature.should.be.equal('6tpegxYl/Eig9k1Lla8b8G8OcdtOxyNbDsdyic1Yzh4='); + }); + + var tests = [ { @@ -55,6 +79,11 @@ describe('crypto utils', function() { phrase.should.equal(expected); }); }); + it('should generate a passphrase using default salt/iter', function() { + var phrase = cryptoUtils.kdf('Pwd123!@#$%^&*(){}[]\|/?.>,<=+-_`~åéþ䲤þçæ¶'); + var expected = 'ml+mMtjgcvL2pdfDwQqW2qONRNjZ3YD8KnGeV3aFjyOoM0ByOmoREw9zBvowC/ZXsfrezbRXX/W/XIzKOqdrXA=='; + phrase.should.equal(expected); + }); }); diff --git a/test/util.http.js b/test/util.http.js new file mode 100644 index 000000000..4990d41e7 --- /dev/null +++ b/test/util.http.js @@ -0,0 +1,89 @@ +'use strict'; + +var _ = require('lodash'); +var chai = chai || require('chai'); +var sinon = sinon || require('sinon'); +var should = chai.should(); +var httpUtils = require('../js/util/HTTP'); +describe('http utils', function() { + + var xhr; + beforeEach(function() { + xhr = { + open: sinon.stub(), + getAllResponseHeaders: sinon.stub().returns('Content-type: xx'), + setRequestHeader: sinon.stub().returns(), + send: function() { + var self = this; + setTimeout(function() { + self.response = 'test'; + self.error ? self.onerror() : self.onload(); + }, 10); + }, + }; + }); + + it('should get success', function(done) { + xhr.error = 0; + httpUtils.request({ + xhr: xhr, + method: 'GET', + url: 'http://test' + }).success(function(data, status) { + done(); + }).error(function(data, status) { + throw new Error(); + }); + }); + it('should get error', function(done) { + xhr.error = 1; + httpUtils.request({ + xhr: xhr, + method: 'GET', + url: 'http://test', + }).success(function(data, status) { + throw new Error(); + }).error(function(data, status) { + done(); + }); + }); + + it('should get with headers', function(done) { + xhr.error = 0; + httpUtils.request({ + xhr: xhr, + method: 'GET', + url: 'http://test', + headers: { + 'Content-Transfer-Encoding': 'a', + 'Content-Length': 1, + 'X-test': 2, + } + }).success(function(data, status) { + done(); + }); + }); + it('should get with body', function(done) { + xhr.error = 0; + httpUtils.request({ + xhr: xhr, + method: 'GET', + url: 'http://test', + body: 'hola', + responseType: 'type', + }).success(function(data, status) { + done(); + }); + }); + it('should get with default error', function() { + xhr.error = 1; + + var ret = httpUtils.request({ + xhr: xhr, + method: 'GET', + url: 'http://test', + }); + ret._error.should.throw() + }); + +}); diff --git a/test/util.log.js b/test/util.log.js new file mode 100644 index 000000000..13694c105 --- /dev/null +++ b/test/util.log.js @@ -0,0 +1,41 @@ +'use strict'; + +var _ = require('lodash'); +var chai = chai || require('chai'); +var sinon = sinon || require('sinon'); +var should = chai.should(); +var log = require('../js/util/log'); + +describe('log utils', function() { + afterEach(function() { + log.setLevel('info'); + }); + + it('should log fatal', function() { + if (console.warn.restore) + console.warn.restore(); + + sinon.stub(console,'warn'); + + log.setLevel('debug'); + log.warn('hola'); + + var arg = console.warn.getCall(0).args[0]; + arg.should.contain('util.log.js'); + arg.should.contain('hola'); + console.warn.restore(); + }); + + it('should not log debug', function() { + sinon.stub(console,'log'); + log.setLevel('info'); + log.debug('hola'); + console.log.called.should.equal(false); + console.log.restore(); + }); + + it('should log debug', function() { + log.getLevels().debug.should.equal(0); + log.getLevels().fatal.should.equal(5); + }); +}); diff --git a/util/build.js b/util/build.js index f3fb38c7b..5bbd0264e 100644 --- a/util/build.js +++ b/util/build.js @@ -81,6 +81,13 @@ var createBundle = function(opts) { b.require('./js/models/PluginManager', { expose: '../js/models/PluginManager' }); + b.require('./js/util/HTTP', { + expose: '../js/util/HTTP' + }); + b.require('./js/util/log', { + expose: '../js/util/log' + }); + if (!opts.disablePlugins) { b.require('./js/plugins/GoogleDrive', { expose: '../plugins/GoogleDrive' @@ -109,9 +116,6 @@ var createBundle = function(opts) { // The following 2 lines fix karma tests b.require('sjcl'); - b.require('./js/log', { - expose: '../log.js' - }); if (opts.debug) { //include dev dependencies