diff --git a/js/models/Storage.js b/js/models/Storage.js index 4d24928f7..4d842e431 100644 --- a/js/models/Storage.js +++ b/js/models/Storage.js @@ -99,17 +99,22 @@ Storage.prototype.setGlobal = function(k, v, cb) { // remove value for key Storage.prototype.removeGlobal = function(k, cb) { preconditions.checkArgument(cb); - this.storage.removeItem(k, cb); }; -Storage.prototype.getSessionId = function() { - var sessionId = this.sessionStorage.getItem('sessionId'); - if (!sessionId) { +Storage.prototype.getSessionId = function(cb) { + preconditions.checkArgument(cb); + var self = this; + + self.sessionStorage.getItem('sessionId', function(sessionId) { + if (sessionId) + return cb(sessionId); + sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex'); - this.sessionStorage.setItem('sessionId', sessionId); - } - return sessionId; + self.sessionStorage.setItem('sessionId', sessionId, function(){ + return cb(sessionId); + }); + }); }; Storage.prototype._key = function(walletId, k) { @@ -117,14 +122,15 @@ Storage.prototype._key = function(walletId, k) { }; // get value by key Storage.prototype.get = function(walletId, k, cb) { + preconditions.checkArgument(walletId, k, cb); this._read(this._key(walletId, k), cb); }; Storage.prototype._readHelper = function(walletId, k, cb) { var wk = this._key(walletId, k); - this._read(wk, function(v){ - return cb(v,k); + this._read(wk, function(v) { + return cb(v, k); }); }; @@ -133,15 +139,15 @@ Storage.prototype.getMany = function(walletId, keys, cb) { var self = this; var ret = {}; -console.log('[Storage.js.142:keys:]',keys); //TODO - var l = keys.length, i=0; + var l = keys.length, + i = 0; for (var ii in keys) { this._readHelper(walletId, keys[ii], function(v, k) { ret[k] = v; if (++i == l) { - return cb(ret); + return cb(ret); } }); } @@ -202,7 +208,8 @@ Storage.prototype.getWallets = function(cb) { var self = this; this.getWalletIds(function(ids) { - var l = ids.length, i=0; + var l = ids.length, + i = 0; if (!l) return cb([]); @@ -221,21 +228,29 @@ Storage.prototype.getWallets = function(cb) { }); }; -Storage.prototype.deleteWallet = function(walletId) { +Storage.prototype.deleteWallet = function(walletId, cb) { preconditions.checkArgument(walletId); + preconditions.checkArgument(cb); var toDelete = {}; toDelete['nameFor::' + walletId] = 1; - for (var i = 0; i < this.storage.length; i++) { - var key = this.storage.key(i); - var split = key.split('::'); - if (split.length == 2 && split[0] === walletId) { - toDelete[key] = 1; + this.storage.allKeys(function(allKeys) { + for (var key in allKeys) { + var split = key.split('::'); + if (split.length == 2 && split[0] === walletId) { + toDelete[key] = 1; + }; } - } + }); + + var l = toDelete.length, + i = 0; for (var i in toDelete) { - this.removeGlobal(i); + this.removeGlobal(i, function() { + if (++i == l) + return cb(); + }); } }; @@ -255,7 +270,6 @@ Storage.prototype.setFromObj = function(walletId, obj, cb) { var l = Object.keys(obj).length, i = 0; for (var k in obj) { - console.log('[Storage.js.247]', k, i, l); //TODO self.set(walletId, k, obj[k], function() { if (++i == l) { self.setName(walletId, obj.opts.name, cb); diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 18d5ed063..eb0c432af 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -182,6 +182,8 @@ WalletFactory.prototype.read = function(walletId, skipFields, cb) { * @return {Wallet} */ WalletFactory.prototype.create = function(opts, cb) { + preconditions.checkArgument(cb); + opts = opts || {}; opts.networkName = opts.networkName || 'testnet'; @@ -229,6 +231,7 @@ WalletFactory.prototype.create = function(opts, cb) { opts.totalCopayers = totalCopayers; opts.version = opts.version || this.version; +console.log('[WalletFactory.js.165]'); //TODO var w = new Wallet(opts); var self = this; w.store(function() { @@ -266,6 +269,7 @@ WalletFactory.prototype._checkVersion = function(inVersion) { * @return */ WalletFactory.prototype.open = function(walletId, passphrase, cb) { + preconditions.checkArgument(cb); var self = this, err; self.storage._setPassphrase(passphrase); @@ -334,6 +338,7 @@ WalletFactory.prototype.decodeSecret = function(secret) { * @param {walletCreationCallback} cb - a callback */ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphrase, privateHex, cb) { + preconditions.checkArgument(cb); var self = this; var decodedSecret = this.decodeSecret(secret); if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) { diff --git a/js/models/core/WalletLock.js b/js/models/core/WalletLock.js index 66e2458d2..fa449b855 100644 --- a/js/models/core/WalletLock.js +++ b/js/models/core/WalletLock.js @@ -6,13 +6,24 @@ function WalletLock(storage, walletId, timeoutMin) { preconditions.checkArgument(storage); preconditions.checkArgument(walletId); - this.sessionId = storage.getSessionId(); this.storage = storage; this.timeoutMin = timeoutMin || 5; this.key = WalletLock._keyFor(walletId); - // this._setLock(function() {}); } + +WalletLock.prototype.init = function(cb) { + preconditions.checkArgument(cb); + var self = this; + + self.storage.getSessionId(function(sid) { + preconditions.checkState(sid); + + self.sessionId = sid; + cb(); + }); +}; + WalletLock._keyFor = function(walletId) { return 'lock' + '::' + walletId; }; @@ -22,18 +33,27 @@ WalletLock.prototype._isLockedByOther = function(cb) { this.storage.getGlobal(this.key, function(json) { var wl = json ? JSON.parse(json) : null; - var t = wl ? (Date.now() - wl.expireTs) : false; - // is not locked? - if (!wl || t > 0 || wl.sessionId === self.sessionId) + if (!wl || !wl.expireTs) + return cb(false); + + var expiredSince = Date.now() - wl.expireTs; + if (expiredSince >= 0) + return cb(false); + + var isMyself = wl.sessionId === self.sessionId; + + if (isMyself) return cb(false); // Seconds remainding - return cb(parseInt(-t / 1000.)); + return cb(parseInt(-expiredSince / 1000)); }); }; WalletLock.prototype._setLock = function(cb) { + preconditions.checkArgument(cb); + preconditions.checkState(this.sessionId); this.storage.setGlobal(this.key, { sessionId: this.sessionId, @@ -44,20 +64,33 @@ WalletLock.prototype._setLock = function(cb) { }; -WalletLock.prototype.keepAlive = function(cb) { +WalletLock.prototype._doKeepAlive = function(cb) { + preconditions.checkArgument(cb); preconditions.checkState(this.sessionId); + var self = this; this._isLockedByOther(function(t) { - if (t) - return cb(new Error('Wallet is already open. Close it to proceed or wait ' + t + ' seconds if you close it already')); + return cb(new Error('LOCKED: Wallet is locked for ' + t + ' srcs')); self._setLock(cb); }); }; + +WalletLock.prototype.keepAlive = function(cb) { + var self = this; + + if (!self.sessionId) { + return self.init(self._doKeepAlive.bind(self, cb)); + }; + + return this._doKeepAlive(cb); +}; + + WalletLock.prototype.release = function(cb) { this.storage.removeGlobal(this.key, cb); }; diff --git a/test/mocks/FakeLocalStorage.js b/test/mocks/FakeLocalStorage.js index a276abaec..fba5e7dfd 100644 --- a/test/mocks/FakeLocalStorage.js +++ b/test/mocks/FakeLocalStorage.js @@ -5,8 +5,9 @@ function LocalStorage(opts) { FakeLocalStorage = {}; FakeLocalStorage.length = 0; -FakeLocalStorage.removeItem = function(key) { +FakeLocalStorage.removeItem = function(key,cb) { delete ls[key]; + cb(); }; FakeLocalStorage.getItem = function(k,cb) { diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js index 9aea4d2ea..39a4b2ade 100644 --- a/test/mocks/FakeStorage.js +++ b/test/mocks/FakeStorage.js @@ -11,57 +11,65 @@ FakeStorage.prototype._setPassphrase = function(password) { this.storage.passphrase = password; }; -FakeStorage.prototype.setGlobal = function(id, v) { +FakeStorage.prototype.setGlobal = function(id, v, cb) { this.storage[id] = typeof v === 'object' ? JSON.stringify(v) : v; + cb(); }; -FakeStorage.prototype.getGlobal = function(id) { - return this.storage[id]; +FakeStorage.prototype.getGlobal = function(id, cb) { + return cb(this.storage[id]); }; -FakeStorage.prototype.setLastOpened = function(val) { +FakeStorage.prototype.setLastOpened = function(val, cb) { this.storage['lastOpened'] = val; + return cb(); }; -FakeStorage.prototype.getLastOpened = function() { - return this.storage['lastOpened']; +FakeStorage.prototype.getLastOpened = function(cb) { + return cb(this.storage['lastOpened']); }; FakeStorage.prototype.setLock = function(id) { this.storage[id + '::lock'] = true; + return cb(); } -FakeStorage.prototype.getLock = function(id) { - return this.storage[id + '::lock']; +FakeStorage.prototype.getLock = function(id,cb) { + return cb(this.storage[id + '::lock']); } -FakeStorage.prototype.getSessionId = function() { - return this.sessionId || 'aSessionId'; +FakeStorage.prototype.getSessionId = function(cb) { + this.sessionId = this.sessionId || 'aSessionId'; + return cb(this.sessionId); }; -FakeStorage.prototype.removeLock = function(id) { +FakeStorage.prototype.removeLock = function(id,cb) { delete this.storage[id + '::lock']; + cb(); } -FakeStorage.prototype.removeGlobal = function(id) { +FakeStorage.prototype.removeGlobal = function(id,cb) { delete this.storage[id]; + cb(); }; -FakeStorage.prototype.set = function(wid, id, payload) { +FakeStorage.prototype.set = function(wid, id, payload, cb) { this.storage[wid + '::' + id] = payload; + if (cb) return cb(); }; -FakeStorage.prototype.get = function(wid, id) { - return this.storage[wid + '::' + id]; +FakeStorage.prototype.get = function(wid, id, cb) { + return cb(this.storage[wid + '::' + id]); }; -FakeStorage.prototype.clear = function() { +FakeStorage.prototype.clear = function(cb) { delete this['storage']; + cb(); }; -FakeStorage.prototype.getWalletIds = function() { +FakeStorage.prototype.getWalletIds = function(cb) { var walletIds = []; var uniq = {}; @@ -79,10 +87,10 @@ FakeStorage.prototype.getWalletIds = function() { } } } - return walletIds; + return cb(walletIds); }; -FakeStorage.prototype.deleteWallet = function(walletId) { +FakeStorage.prototype.deleteWallet = function(walletId, cb) { var toDelete = {}; toDelete['nameFor::' + walletId] = 1; @@ -98,17 +106,17 @@ FakeStorage.prototype.deleteWallet = function(walletId) { }; -FakeStorage.prototype.getName = function(walletId) { - return this.getGlobal('nameFor::' + walletId); +FakeStorage.prototype.getName = function(walletId,cb) { + return this.getGlobal('nameFor::' + walletId,cb); }; -FakeStorage.prototype.setName = function(walletId, name) { - this.setGlobal('nameFor::' + walletId, name); +FakeStorage.prototype.setName = function(walletId, name, cb) { + this.setGlobal('nameFor::' + walletId, name, cb); }; -FakeStorage.prototype.getWallets = function() { +FakeStorage.prototype.getWallets = function(cb) { var wallets = []; var ids = this.getWalletIds(); @@ -118,14 +126,14 @@ FakeStorage.prototype.getWallets = function() { name: this.getName(ids[i]), }); } - return wallets; + return cb(wallets); }; -FakeStorage.prototype.setFromObj = function(walletId, obj) { +FakeStorage.prototype.setFromObj = function(walletId, obj, cb) { for (var k in obj) { this.set(walletId, k, obj[k]); } - this.setName(walletId, obj.opts.name); + this.setName(walletId, obj.opts.name, cb); }; module.exports = FakeStorage; diff --git a/test/test.Storage.js b/test/test.Storage.js index fd03c8cee..0363108ab 100644 --- a/test/test.Storage.js +++ b/test/test.Storage.js @@ -39,15 +39,13 @@ describe('Storage model', function() { s._write(fakeWallet + timeStamp, 'value', function() { s._read(fakeWallet + timeStamp, function(v) { v.should.equal('value'); - localMock.removeItem(fakeWallet + timeStamp); - done(); + localMock.removeItem(fakeWallet + timeStamp, done); }); }); }); it('should be able to set a value', function(done) { s.set(fakeWallet, timeStamp, 1, function() { - localMock.removeItem(fakeWallet + '::' + timeStamp); - done(); + localMock.removeItem(fakeWallet + '::' + timeStamp, done); }); }); var getSetData = [ @@ -68,11 +66,11 @@ describe('Storage model', function() { null ]; getSetData.forEach(function(obj) { - it('should be able to set a value and get it for ' + JSON.stringify(obj), function() { + it('should be able to set a value and get it for ' + JSON.stringify(obj), function(done) { s.set(fakeWallet, timeStamp, obj, function() { s.get(fakeWallet, timeStamp, function(obj2) { JSON.stringify(obj2).should.equal(JSON.stringify(obj)); - localMock.removeItem(fakeWallet + '::' + timeStamp); + localMock.removeItem(fakeWallet + '::' + timeStamp, done); }); }); }); @@ -91,15 +89,13 @@ describe('Storage model', function() { }; var encrypted = storage.export(obj); encrypted.length.should.be.greaterThan(10); - localMock.removeItem(fakeWallet + '::' + timeStamp); - done(); - + localMock.removeItem(fakeWallet + '::' + timeStamp, done); }); }); }); - describe('#remove', function(done) { - it('should remove an item', function() { + describe('#remove', function() { + it('should remove an item', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, @@ -109,8 +105,10 @@ describe('Storage model', function() { s.get('1', 'hola', function(v) { v.should.equal('juan'); s.remove('1', 'hola', function() { - should.not.exist(s.get('1', 'hola')); - done(); + s.get('1', 'hola', function(v) { + should.not.exist(v); + done(); + }); }); }) }) @@ -169,62 +167,75 @@ describe('Storage model', function() { if (is_browser) { describe('#getSessionId', function() { - it('should get SessionId', function() { + it('should get SessionId', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, password: 'password' }); - var sid = s.getSessionId(); - should.exist(sid); - var sid2 = s.getSessionId(); - sid2.should.equal(sid); + s.getSessionId(function(sid) { + should.exist(sid); + s.getSessionId(function(sid2) { + sid2.should.equal(sid); + done(); + }); + }); }); }); } describe('#getWallets', function() { - it('should retreive wallets from storage', function() { + it('should retreive wallets from storage', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, password: 'password' }); - s.set('1', "hola", 'juan'); - s.set('2', "hola", 'juan'); - s.setName(1, 'hola'); - s.getWallets()[0].should.deep.equal({ - id: '1', - name: 'hola', - }); - s.getWallets()[1].should.deep.equal({ - id: '2', - name: undefined + s.set('1', "hola", 'juan', function() { + s.set('2', "hola", 'juan', function() { + s.setName(1, 'hola', function() { + s.getWallets(function(ws) { + ws[0].should.deep.equal({ + id: '1', + name: 'hola', + }); + ws[1].should.deep.equal({ + id: '2', + name: undefined + }); + done(); + }); + }); + }); }); }); }); - describe('#deleteWallet', function() { + describe('#deleteWallet', function(done) { it('should delete a wallet', function() { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, password: 'password' }); - s.set('1', "hola", 'juan'); - s.set('2', "hola", 'juan'); - s.setName(1, 'hola'); - - s.deleteWallet('1'); - s.getWallets().length.should.equal(1); - s.getWallets()[0].should.deep.equal({ - id: '2', - name: undefined + s.set('1', "hola", 'juan', function() { + s.set('2', "hola", 'juan', function() { + s.deleteWallet('1', function() { + s.getWallets(function(ws) { + s.getWallets().length.should.equal(1); + ws[0].should.deep.equal({ + id: '2', + name: undefined + }); + done(); + }); + }); + }); }); }); }); describe('#setFromObj', function() { - it('set localstorage from an object', function() { + it('set localstorage from an object', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, @@ -235,42 +246,57 @@ describe('Storage model', function() { 'opts': { 'name': 'nameid1' }, + }, function() { + s.get('id1', 'key', function(v) { + v.should.equal('val'); + done(); + }); }); - - s.get('id1', 'key').should.equal('val'); - }); }); describe('#globals', function() { - it('should set, get and remove keys', function() { + it('should set, get and remove keys', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, password: 'password' }); + s.setGlobal('a', { b: 1 + }, function() { + s.getGlobal('a', function(v) { + + JSON.parse(v).should.deep.equal({ + b: 1 + }); + s.removeGlobal('a', function() { + s.getGlobal('a', function(v) { + should.not.exist(v); + done(); + }); + }); + }); }); - JSON.parse(s.getGlobal('a')).should.deep.equal({ - b: 1 - }); - s.removeGlobal('a'); - should.not.exist(s.getGlobal('a')); }); }); describe('session storage', function() { - it('should get a session ID', function() { + it('should get a session ID', function(done) { var s = new Storage({ storage: localMock, sessionStorage: sessionMock, password: 'password' }); - s.getSessionId().length.should.equal(16); - (new Buffer(s.getSessionId(), 'hex')).length.should.equal(8); + s.getSessionId(function(s) { + should.exist(s); + s.length.should.equal(16); + (new Buffer(s, 'hex')).length.should.equal(8); + done(); + }); }); }); }); diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index 8712c4121..f736c339f 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -117,16 +117,17 @@ describe('WalletFactory model', function() { wf.version.should.equal('0.0.1'); }); - it('#_checkRead should return false', function() { - var wf = new WalletFactory(config); - wf._checkRead('dummy').should.equal(false); - wf.read('dummy').should.equal(false); - }); - it('should be able to create wallets', function() { + it('should be able to create wallets', function(done) { var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create(); - should.exist(w); + wf.create(null, function(err, w) { + +console.log('[test.WalletFactory.js.123]'); //TODO + should.not.exist(err); + should.exist(w); + w.should.be.instanceof('WalletFactory'); + done(); + }); }); it('should be able to create wallets with given pk', function() { diff --git a/test/test.WalletLock.js b/test/test.WalletLock.js index f3ccf051d..e8e418a96 100644 --- a/test/test.WalletLock.js +++ b/test/test.WalletLock.js @@ -15,8 +15,11 @@ var WalletLock = copay.WalletLock; var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); +var storage; describe('WalletLock model', function() { - var storage = new Storage(); + beforeEach(function() { + storage = new Storage(); + }); it('should fail with missing args', function() { (function() { @@ -36,45 +39,70 @@ describe('WalletLock model', function() { should.exist(w); }); - it('should NOT fail if locked already', function() { - var w = new WalletLock(storage, 'walletId'); - storage.sessionId = 'xxx'; - var w2= new WalletLock(storage, 'walletId'); - should.exist(w2); + + it('should generate a sessionId with init', function(done) { + var w = new WalletLock(storage, 'id'); + var spy = sinon.spy(storage, 'getSessionId'); + w.init(function() { + spy.calledOnce.should.equal(true); + done(); + }); }); - it('should change status of previously openned wallet', function() { + it('#keepAlive should call getsessionId if not called before', function(done) { + var w = new WalletLock(storage, 'id'); + var spy = sinon.spy(storage, 'getSessionId'); + w.keepAlive(function() { + spy.calledOnce.should.equal(true); + done(); + }); + }); + + it('should NOT fail if locked already by me', function(done) { + var w = new WalletLock(storage, 'walletId2'); + w.keepAlive(function() { + var w2 = new WalletLock(storage, 'walletId2'); + w2.init(function() { + w2.keepAlive(function() { + w.sessionId.should.equal(w2.sessionId); + should.exist(w2); + done(); + }); + }); + }) + }); + + it('should FAIL if locked by someone else', function(done) { storage.sessionId = 'session1'; var w = new WalletLock(storage, 'walletId'); - storage.sessionId = 'xxx'; - var w2= new WalletLock(storage, 'walletId'); - w2.keepAlive(); - (function() {w.keepAlive();}).should.throw('already open'); - - }); - - - it('should not fail if locked by me', function() { - var s = new Storage(); - var w = new WalletLock(s, 'walletId'); - var w2 = new WalletLock(s, 'walletId') - w2.keepAlive(); - should.exist(w2); - }); - - it('should not fail if expired', function() { - var s = new Storage(); - var w = new WalletLock(s, 'walletId'); - var k = Object.keys(s.storage)[0]; - var v = JSON.parse(s.storage[k]); - v.expireTs = Date.now() - 60 * 6 * 1000; - s.storage[k] = JSON.stringify(v); - - s.sessionId = 'xxx'; - var w2 = new WalletLock(s, 'walletId') - should.exist(w2); - }); - - + w.keepAlive(function() { + storage.sessionId = 'session2'; + var w2 = new WalletLock(storage, 'walletId'); + w2.keepAlive(function(locked) { + w2.sessionId.should.equal('session2'); + should.exist(locked); + locked.message.should.contain('LOCKED'); + done(); + }); + }); + }) + it('should FAIL if locked by someone else but expired', function(done) { + storage.sessionId = 'session1'; + var w = new WalletLock(storage, 'walletId'); + w.keepAlive(function() { + storage.sessionId = 'session2'; + var json = JSON.parse(storage.storage['lock::walletId']); + json.expireTs -= 3600 * 1000; + storage.storage['lock::walletId'] = JSON.stringify(json); + var w2 = new WalletLock(storage, 'walletId'); + w2.keepAlive(function(locked) { + w2.sessionId.should.equal('session2'); + should.not.exist(locked); + done(); + }); + }); + }) }); + +