From 43e527957088008b85398bf07ca235d3f8dd749c Mon Sep 17 00:00:00 2001 From: Gordon Hall Date: Tue, 10 Jun 2014 10:39:13 -0400 Subject: [PATCH 01/44] fix copy/paste bug on osx/linux/win --- js/shell.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/js/shell.js b/js/shell.js index 4fe83d25c..1651925a7 100644 --- a/js/shell.js +++ b/js/shell.js @@ -38,20 +38,10 @@ var ipc = require('ipc'); var clipb = require('clipboard'); - // atom shell forces to implement the clipboard on our own - thanks obama. + // atom shell forces to implement the clipboard (on osx) on our own - thanks obama. Mousetrap.stopCallback = function() { return false }; - Mousetrap.bind('ctrl+c', function(e) { - clipb.writeText(window.getSelection().toString()); - }); - - Mousetrap.bind('ctrl+v', function(e) { - if (document.activeElement) { - document.activeElement.value = clipb.readText(); - } - }); - Mousetrap.bind('command+c', function(e) { clipb.writeText(window.getSelection().toString()); }); @@ -59,6 +49,7 @@ Mousetrap.bind('command+v', function(e) { if (document.activeElement) { document.activeElement.value = clipb.readText(); + document.activeElement.dispatchEvent(new Event('change')); } }); From 302d5448a68aa106c0f552a5d44a9731d5a9288e Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 3 Jul 2014 10:49:28 -0300 Subject: [PATCH 02/44] change backup to settings --- index.html | 2 +- js/controllers/backup.js | 2 +- js/controllers/header.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 339e7e7ce..07de1e7a0 100644 --- a/index.html +++ b/index.html @@ -807,7 +807,7 @@
-
Delete this wallet from this computer
+
Delete this wallet from this computer
diff --git a/js/controllers/backup.js b/js/controllers/backup.js index 7981abb42..c7fa10421 100644 --- a/js/controllers/backup.js +++ b/js/controllers/backup.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('BackupController', function($scope, $rootScope, $location, $window, $timeout, $modal, backupService, walletFactory, controllerUtils) { - $scope.title = 'Backup'; + $scope.title = 'Settings'; $scope.download = function() { backupService.download($rootScope.wallet); diff --git a/js/controllers/header.js b/js/controllers/header.js index a638f7a2c..3b340d113 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -15,8 +15,8 @@ angular.module('copayApp.controllers').controller('HeaderController', 'icon': 'fi-arrow-right', 'link': '#/send' }, { - 'title': 'Backup', - 'icon': 'fi-archive', + 'title': 'Settings', + 'icon': 'fi-wrench', 'link': '#/backup' }]; From 30a259aeb8fc27fb8d2062931928e99723ac9fca Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 3 Jul 2014 10:51:27 -0300 Subject: [PATCH 03/44] fix test --- test/unit/controllers/controllersSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 8525dc782..a607642e5 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -42,7 +42,7 @@ describe("Unit: Controllers", function() { })); it('Should have a Backup controller', function() { - expect(scope.title).equal('Backup'); + expect(scope.title).equal('Settings'); }); it('Backup controller #download', function() { From a7d484c944f1a0cd9b59d5f5da3b01a2af399b9a Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 30 Jun 2014 18:41:17 -0300 Subject: [PATCH 04/44] basic URI handling --- index.html | 1 + js/routes.js | 1 - js/services/controllerUtils.js | 3 ++- js/services/uriHandler.js | 11 +++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 js/services/uriHandler.js diff --git a/index.html b/index.html index 339e7e7ce..b86275c94 100644 --- a/index.html +++ b/index.html @@ -926,6 +926,7 @@ on supported browsers please check http://www.w + diff --git a/js/routes.js b/js/routes.js index a4218112c..599f48677 100644 --- a/js/routes.js +++ b/js/routes.js @@ -64,7 +64,6 @@ angular }) .run(function($rootScope, $location) { $rootScope.$on('$routeChangeStart', function(event, next, current) { - if (!util.supports.data) { $location.path('unsupported'); } else { diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 342b33cdf..bf2f387a7 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video) { + .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) { var root = {}; root.getVideoMutedStatus = function(copayer) { @@ -64,6 +64,7 @@ angular.module('copayApp.services') }; root.setupRootVariables = function() { + uriHandler.register(); $rootScope.unitName = config.unitName; $rootScope.txAlertCount = 0; $rootScope.insightError = 0; diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js new file mode 100644 index 000000000..564bfb5a8 --- /dev/null +++ b/js/services/uriHandler.js @@ -0,0 +1,11 @@ +'use strict'; + +var UriHandler = function() {}; + +UriHandler.prototype.register = function() { + navigator.registerProtocolHandler('bitcoin', + 'uri=%s', + 'Copay'); +}; + +angular.module('copayApp.services').value('uriHandler', new UriHandler()); From 16293b035d21216c4f3b264cd5ef1070771ddfd0 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 30 Jun 2014 18:48:48 -0300 Subject: [PATCH 05/44] add tests --- test/unit/services/servicesSpec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index badab5154..0358d1b74 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -181,3 +181,20 @@ describe("Unit: isMobile Service", function() { isMobile.any().should.equal(true); })); }); +describe("Unit: video service", function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should contain a video service', inject(function(video) { + should.exist(video); + })); +}); +describe("Unit: uriHandler service", function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should contain a uriHandler service', inject(function(uriHandler) { + should.exist(uriHandler); + })); + it('should register', inject(function(uriHandler) { + (function() { + uriHandler.register(); + }).should.not.throw(); + })); +}); From 433f1570de2938af1d82065938e253e7eebb1c42 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 1 Jul 2014 19:35:15 -0300 Subject: [PATCH 06/44] add first uri handler --- index.html | 16 ++++++++++++++++ js/controllers/uriPayment.js | 20 ++++++++++++++++++++ js/routes.js | 7 +++---- js/services/uriHandler.js | 6 ++++-- 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 js/controllers/uriPayment.js diff --git a/index.html b/index.html index b86275c94..9ca64a66d 100644 --- a/index.html +++ b/index.html @@ -884,6 +884,21 @@ on supported browsers please check http://www.w + + + + diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js new file mode 100644 index 000000000..f1004eca6 --- /dev/null +++ b/js/controllers/uriPayment.js @@ -0,0 +1,20 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams) { + var data = $routeParams.data; + var splitDots = data.split(':'); + $scope.protocol = splitDots[0]; + data = splitDots[1]; + var splitQuestion = data.split('?'); + $scope.address = splitQuestion[0]; + data = decodeURIComponent(splitQuestion[1]); + var search = splitQuestion[1]; + data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', + function(key, value) { + return key === "" ? value : decodeURIComponent(value); + }); + $scope.amount = data.amount; + $scope.message = data.message; + + +}); diff --git a/js/routes.js b/js/routes.js index 599f48677..e4fe14cff 100644 --- a/js/routes.js +++ b/js/routes.js @@ -26,10 +26,6 @@ angular templateUrl: 'addresses.html', validate: true }) - .when('/join/:id', { - templateUrl: 'join.html', - validate: true - }) .when('/transactions', { templateUrl: 'transactions.html', validate: true @@ -49,6 +45,9 @@ angular .when('/unsupported', { templateUrl: 'unsupported.html' }) + .when('/uri_payment/:data', { + templateUrl: 'uri_payment.html' + }) .otherwise({ templateUrl: '404.html' }); diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js index 564bfb5a8..413672f3b 100644 --- a/js/services/uriHandler.js +++ b/js/services/uriHandler.js @@ -3,9 +3,11 @@ var UriHandler = function() {}; UriHandler.prototype.register = function() { + var base = window.location.origin + '/'; + var url = base + '#/uri_payment/%s'; + console.log(url); navigator.registerProtocolHandler('bitcoin', - 'uri=%s', - 'Copay'); + url, 'Copay'); }; angular.module('copayApp.services').value('uriHandler', new UriHandler()); From 8b25b5932f9e86a57087dc09664713ed0fd5f67d Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 1 Jul 2014 19:49:05 -0300 Subject: [PATCH 07/44] add tests and fix some minor issues --- js/controllers/uriPayment.js | 6 ++--- js/services/uriHandler.js | 1 - test/unit/controllers/controllersSpec.js | 28 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js index f1004eca6..458a9e596 100644 --- a/js/controllers/uriPayment.js +++ b/js/controllers/uriPayment.js @@ -1,20 +1,18 @@ 'use strict'; angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams) { - var data = $routeParams.data; + var data = decodeURIComponent($routeParams.data); var splitDots = data.split(':'); $scope.protocol = splitDots[0]; data = splitDots[1]; var splitQuestion = data.split('?'); $scope.address = splitQuestion[0]; - data = decodeURIComponent(splitQuestion[1]); var search = splitQuestion[1]; data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', function(key, value) { return key === "" ? value : decodeURIComponent(value); }); - $scope.amount = data.amount; + $scope.amount = parseInt(data.amount); $scope.message = data.message; - }); diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js index 413672f3b..ef50e1b5a 100644 --- a/js/services/uriHandler.js +++ b/js/services/uriHandler.js @@ -5,7 +5,6 @@ var UriHandler = function() {}; UriHandler.prototype.register = function() { var base = window.location.origin + '/'; var url = base + '#/uri_payment/%s'; - console.log(url); navigator.registerProtocolHandler('bitcoin', url, 'Copay'); }; diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 8525dc782..96a30f4be 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -300,7 +300,7 @@ describe("Unit: Controllers", function() { '
' + '' + '
' - ); + ); scope.model = { amount: null }; @@ -368,4 +368,30 @@ describe("Unit: Controllers", function() { }); }); + describe('UriPayment Controller', function() { + var what; + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + var routeParams = { + data: 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=1&message=a%20bitcoin%20donation' + }; + what = $controller('UriPaymentController', { + $scope: scope, + $routeParams: routeParams + }); + })); + + it('should exist', function() { + should.exist(what); + }); + + it('should parse url correctly', function() { + should.exist(what); + scope.protocol.should.equal('bitcoin'); + scope.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); + scope.amount.should.equal(1); + scope.message.should.equal('a bitcoin donation'); + }); + }); + }); From 8acaca75fb899ec798719482676d27d97b0a5841 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 2 Jul 2014 17:35:37 -0300 Subject: [PATCH 08/44] autocomplete send form after uri handled --- js/controllers/header.js | 4 +++- js/controllers/send.js | 10 +++++++++- js/controllers/signin.js | 4 ++++ js/controllers/uriPayment.js | 16 ++++++++++++++-- js/services/controllerUtils.js | 6 +++++- test/unit/controllers/controllersSpec.js | 4 ++-- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/js/controllers/header.js b/js/controllers/header.js index a638f7a2c..841e9ac9b 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -71,7 +71,9 @@ angular.module('copayApp.controllers').controller('HeaderController', $rootScope.$watch('txAlertCount', function(txAlertCount) { if (txAlertCount && txAlertCount > 0) { - notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); + notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? + 'You have a pending transaction proposal' : + 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); } }); diff --git a/js/controllers/send.js b/js/controllers/send.js index 5d94e90fd..0ab3d3858 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -23,6 +23,13 @@ angular.module('copayApp.controllers').controller('SendController', return flag; }; + if ($rootScope.pendingPayment) { + var pp = $rootScope.pendingPayment; + $scope.address = pp.address; + var amount = pp.amount / config.unitToSatoshi * 100000000; + $scope.amount = amount; + } + // Detect protocol $scope.isHttp = ($window.location.protocol.indexOf('http') === 0); @@ -61,6 +68,7 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loading = false; }); } + $rootScope.pendingPayment = null; }); // reset fields @@ -265,7 +273,7 @@ angular.module('copayApp.controllers').controller('SendController', }; $scope.topAmount = function(form) { - $scope.amount = $scope.getAvailableAmount(); + $scope.amount = $scope.getAvailableAmount(); form.amount.$pristine = false; }; }); diff --git a/js/controllers/signin.js b/js/controllers/signin.js index b2e6ef0c8..6503d46ab 100644 --- a/js/controllers/signin.js +++ b/js/controllers/signin.js @@ -13,6 +13,10 @@ angular.module('copayApp.controllers').controller('SigninController', $scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null; $scope.openPassword = ''; + if ($rootScope.pendingPayment) { + notification.info('Login Required', 'Please open wallet to complete payment'); + } + $scope.open = function(form) { if (form && form.$invalid) { notification.error('Error', 'Please enter the required fields'); diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js index 458a9e596..ccc701205 100644 --- a/js/controllers/uriPayment.js +++ b/js/controllers/uriPayment.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams) { +angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) { var data = decodeURIComponent($routeParams.data); var splitDots = data.split(':'); $scope.protocol = splitDots[0]; @@ -12,7 +12,19 @@ angular.module('copayApp.controllers').controller('UriPaymentController', functi function(key, value) { return key === "" ? value : decodeURIComponent(value); }); - $scope.amount = parseInt(data.amount); + $scope.amount = parseFloat(data.amount); $scope.message = data.message; + $rootScope.pendingPayment = { + protocol: $scope.protocol, + address: $scope.address, + amount: $scope.amount, + message: $scope.message + }; + + $timeout(function() { + $location.path('signin'); + }, 1000); + + }); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index bf2f387a7..aa386b618 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -103,7 +103,11 @@ angular.module('copayApp.services') }); w.on('ready', function(myPeerID) { $rootScope.wallet = w; - $location.path('addresses'); + if ($rootScope.pendingPayment) { + $location.path('send'); + } else { + $location.path('addresses'); + } if (!config.disableVideo) video.setOwnPeer(myPeerID, w, handlePeerVideo); }); diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 96a30f4be..046ea0704 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -373,7 +373,7 @@ describe("Unit: Controllers", function() { beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); var routeParams = { - data: 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=1&message=a%20bitcoin%20donation' + data: 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation' }; what = $controller('UriPaymentController', { $scope: scope, @@ -389,7 +389,7 @@ describe("Unit: Controllers", function() { should.exist(what); scope.protocol.should.equal('bitcoin'); scope.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); - scope.amount.should.equal(1); + scope.amount.should.equal(0.1); scope.message.should.equal('a bitcoin donation'); }); }); From 5ba388e9114107aec3e6d4c18e1d3722ad8c6613 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 3 Jul 2014 11:37:26 -0300 Subject: [PATCH 09/44] add testnet in footer / add links --- index.html | 21 +++++++++++++++++++-- js/controllers/footer.js | 2 ++ js/controllers/settings.js | 2 +- js/models/core/Wallet.js | 1 + js/models/network/WebRTC.js | 1 - 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 339e7e7ce..cd3169467 100644 --- a/index.html +++ b/index.html @@ -156,10 +156,20 @@
@@ -314,70 +316,67 @@ Creating wallet... -
+

Create new wallet

-
-
- -
-
-
-
- -
-
- -
+
+ +
+
+
+
+ +
+
+
-
-
-
-
- -
-
- -
+
+
+
+
+
+ +
+
+
-
-
-
-
- -
-
+
+
+
+
+
-
-
- Go back +
+
+
+ Go back +
+
+ +
-
- -
-
From ff9399f0216ddfdcd6801183e96f98b74c2e3738 Mon Sep 17 00:00:00 2001 From: Bechi Date: Thu, 3 Jul 2014 12:50:32 -0300 Subject: [PATCH 14/44] line comment --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 55a007c4a..68c07a745 100644 --- a/index.html +++ b/index.html @@ -141,7 +141,7 @@ ng-disabled="!$root.wallet.publicKeyRing.isComplete()"> Backup keys and continue - Skip Backup +
From 1b780d268b0ce806fa853d663cc69f5bd7744d17 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 3 Jul 2014 12:52:28 -0300 Subject: [PATCH 15/44] get URI parsing into copay core --- js/controllers/uriPayment.js | 23 +++++------------------ js/models/core/Structure.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/js/controllers/uriPayment.js b/js/controllers/uriPayment.js index ccc701205..9cbc383c6 100644 --- a/js/controllers/uriPayment.js +++ b/js/controllers/uriPayment.js @@ -2,25 +2,12 @@ angular.module('copayApp.controllers').controller('UriPaymentController', function($rootScope, $scope, $routeParams, $timeout, $location) { var data = decodeURIComponent($routeParams.data); - var splitDots = data.split(':'); - $scope.protocol = splitDots[0]; - data = splitDots[1]; - var splitQuestion = data.split('?'); - $scope.address = splitQuestion[0]; - var search = splitQuestion[1]; - data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', - function(key, value) { - return key === "" ? value : decodeURIComponent(value); - }); - $scope.amount = parseFloat(data.amount); - $scope.message = data.message; + $rootScope.pendingPayment = copay.Structure.parseBitcoinURI($routeParams.data); - $rootScope.pendingPayment = { - protocol: $scope.protocol, - address: $scope.address, - amount: $scope.amount, - message: $scope.message - }; + $scope.protocol = $rootScope.pendingPayment.protocol; + $scope.address = $rootScope.pendingPayment.address; + $scope.amount = $rootScope.pendingPayment.amount; + $scope.message = $rootScope.pendingPayment.message; $timeout(function() { $location.path('signin'); diff --git a/js/models/core/Structure.js b/js/models/core/Structure.js index 88543f966..aafee4c0f 100644 --- a/js/models/core/Structure.js +++ b/js/models/core/Structure.js @@ -50,4 +50,23 @@ Structure.MAX_NON_HARDENED = MAX_NON_HARDENED; Structure.SHARED_INDEX = SHARED_INDEX; Structure.ID_INDEX = ID_INDEX; +Structure.parseBitcoinURI = function(uri) { + var ret = {}; + var data = decodeURIComponent(uri); + var splitDots = data.split(':'); + ret.protocol = splitDots[0]; + data = splitDots[1]; + var splitQuestion = data.split('?'); + ret.address = splitQuestion[0]; + var search = splitQuestion[1]; + data = JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}', + function(key, value) { + return key === "" ? value : decodeURIComponent(value); + }); + ret.amount = parseFloat(data.amount); + ret.message = data.message; + + return ret; +}; + module.exports = require('soop')(Structure); From 4fdc5c4b5163be4731d6852941cf2bef70046017 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 3 Jul 2014 13:13:45 -0300 Subject: [PATCH 16/44] add Structure test --- test/test.Structure.js | 43 +++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/test/test.Structure.js b/test/test.Structure.js index 04b224668..3541c0f9c 100644 --- a/test/test.Structure.js +++ b/test/test.Structure.js @@ -39,13 +39,34 @@ describe('Structure model', function() { }); [ - ['m/45\'/0/0/0', {index: 0, isChange: false}], - ['m/45\'/0/0/1', {index: 1, isChange: false}], - ['m/45\'/0/0/2', {index: 2, isChange: false}], - ['m/45\'/0/1/0', {index: 0, isChange: true}], - ['m/45\'/0/1/1', {index: 1, isChange: true}], - ['m/45\'/0/1/2', {index: 2, isChange: true}], - ['m/45\'/0/0/900', {index: 900, isChange: false}], + ['m/45\'/0/0/0', { + index: 0, + isChange: false + }], + ['m/45\'/0/0/1', { + index: 1, + isChange: false + }], + ['m/45\'/0/0/2', { + index: 2, + isChange: false + }], + ['m/45\'/0/1/0', { + index: 0, + isChange: true + }], + ['m/45\'/0/1/1', { + index: 1, + isChange: true + }], + ['m/45\'/0/1/2', { + index: 2, + isChange: true + }], + ['m/45\'/0/0/900', { + index: 900, + isChange: false + }], ].forEach(function(datum) { var path = datum[0]; var result = datum[1]; @@ -55,5 +76,13 @@ describe('Structure model', function() { i.isChange.should.equal(result.isChange); }); }); + it('should get the correct result for bitcoin uri', function() { + var uri = 'bitcoin:19mP9FKrXqL46Si58pHdhGKow88SUPy1V8%3Famount=0.1&message=a%20bitcoin%20donation'; + var result = Structure.parseBitcoinURI(uri); + result.address.should.equal('19mP9FKrXqL46Si58pHdhGKow88SUPy1V8'); + result.amount.should.equal(0.1); + result.message.should.equal('a bitcoin donation'); + result.protocol.should.equal('bitcoin'); + }); }); From 2e56a782bd3dfccdb6d1cb3a12ee32fa96fb6975 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Fri, 27 Jun 2014 15:06:39 -0300 Subject: [PATCH 17/44] Add cosigner index to AddressIndex --- js/models/core/AddressIndex.js | 12 +++++++++--- test/test.AddressIndex.js | 21 ++++++++++++++++++++- test/test.WalletFactory.js | 3 +-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index 7920b44a2..f5532905c 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -1,11 +1,12 @@ 'use strict'; - var imports = require('soop').imports(); +var preconditions = require('preconditions').singleton(); function AddressIndex(opts) { opts = opts || {}; this.walletId = opts.walletId; + this.cosigner = opts.cosigner || 0; this.changeIndex = opts.changeIndex || 0; this.receiveIndex = opts.receiveIndex || 0; @@ -22,8 +23,9 @@ AddressIndex.fromObj = function(data) { AddressIndex.prototype.toObj = function() { return { walletId: this.walletId, + cosigner: this.cosigner, changeIndex: this.changeIndex, - receiveIndex: this.receiveIndex, + receiveIndex: this.receiveIndex }; }; @@ -34,10 +36,10 @@ AddressIndex.prototype.checkRange = function(index, isChange) { } }; - AddressIndex.prototype.getChangeIndex = function() { return this.changeIndex; }; + AddressIndex.prototype.getReceiveIndex = function() { return this.receiveIndex; }; @@ -51,6 +53,10 @@ AddressIndex.prototype.increment = function(isChange) { }; AddressIndex.prototype.merge = function(inAddressIndex) { + preconditions.shouldBeObject(inAddressIndex) + .checkArgument(this.walletId == inAddressIndex.walletId) + .checkArgument(this.cosigner == inAddressIndex.cosigner); + var hasChanged = false; // Indexes diff --git a/test/test.AddressIndex.js b/test/test.AddressIndex.js index 7b1acc513..ccb08ff96 100644 --- a/test/test.AddressIndex.js +++ b/test/test.AddressIndex.js @@ -23,6 +23,7 @@ var createAI = function() { should.exist(i); i.walletId = '1234567'; + i.cosigner = 1; return i; }; @@ -34,7 +35,7 @@ describe('AddressIndex model', function() { should.exist(i); }); - it('show be able to tostore and read', function() { + it('show be able to store and read', function() { var i = createAI(); var changeN = 2; var addressN = 2; @@ -50,6 +51,7 @@ describe('AddressIndex model', function() { var i2 = AddressIndex.fromObj(data); i2.walletId.should.equal(i.walletId); + i2.cosigner.should.equal(i.cosigner); i2.getChangeIndex().should.equal(changeN); i2.getReceiveIndex().should.equal(addressN); @@ -75,6 +77,7 @@ describe('AddressIndex model', function() { j.increment(false); var j2 = new AddressIndex({ walletId: j.walletId, + cosigner: j.cosigner, }); j2.merge(j).should.equal(true); j2.changeIndex.should.equal(15); @@ -83,4 +86,20 @@ describe('AddressIndex model', function() { j2.merge(j).should.equal(false); }); + it('#merge should fail with different walletId', function() { + var j1 = new AddressIndex({ walletId: '1234' }); + var j2 = new AddressIndex({ walletId: '4321' }); + + var merge = function() { j2.merge(j1); }; + merge.should.throw(Error); + }) + + it('#merge should fail with different cosigner index', function() { + var j1 = new AddressIndex({ walletId: '1234', cosigner: 2 }); + var j2 = new AddressIndex({ walletId: '1234', cosigner: 3 }); + + var merge = function() { j2.merge(j1); }; + merge.should.throw(Error); + }) + }); diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index 3042e5775..d240acace 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -10,7 +10,7 @@ var FakeBlockchain = require('./mocks/FakeBlockchain'); var FakeStorage = require('./mocks/FakeStorage'); var WalletFactory = require('../js/models/core/WalletFactory'); -var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; +var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"cosigner":2,"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; describe('WalletFactory model', function() { var config = { @@ -82,7 +82,6 @@ describe('WalletFactory model', function() { }); it('#fromObj #toObj round trip', function() { - var wf = new WalletFactory(config, '0.0.5'); var w = wf.fromObj(JSON.parse(o)); From e9f20b5de6fa05952d2770ea280643b1e94b2738 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 1 Jul 2014 09:41:28 -0300 Subject: [PATCH 18/44] Change PublicKeyRing index to array of AddressIndex --- js/models/core/AddressIndex.js | 14 ++++---- js/models/core/PublicKeyRing.js | 59 +++++++++++++++++++++++++-------- js/models/core/Wallet.js | 17 +++++----- test/test.AddressIndex.js | 11 ------ test/test.PublicKeyRing.js | 17 ++++++---- test/test.TxProposals.js | 4 +-- test/test.Wallet.js | 27 ++++++++------- test/test.WalletFactory.js | 18 +++++++++- 8 files changed, 105 insertions(+), 62 deletions(-) diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index f5532905c..bfc9b6464 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -2,27 +2,28 @@ var imports = require('soop').imports(); var preconditions = require('preconditions').singleton(); +var Structure = require('./Structure'); function AddressIndex(opts) { opts = opts || {}; - this.walletId = opts.walletId; - this.cosigner = opts.cosigner || 0; - + this.cosigner = opts.cosigner || Structure.SHARED_INDEX; this.changeIndex = opts.changeIndex || 0; this.receiveIndex = opts.receiveIndex || 0; } +AddressIndex.fromList = function(indexes) { + return indexes.map(function(i) { return AddressIndex.fromObj(i); }); +} + AddressIndex.fromObj = function(data) { if (data instanceof AddressIndex) { throw new Error('bad data format: Did you use .toObj()?'); } - var ret = new AddressIndex(data); - return ret; + return new AddressIndex(data); }; AddressIndex.prototype.toObj = function() { return { - walletId: this.walletId, cosigner: this.cosigner, changeIndex: this.changeIndex, receiveIndex: this.receiveIndex @@ -54,7 +55,6 @@ AddressIndex.prototype.increment = function(isChange) { AddressIndex.prototype.merge = function(inAddressIndex) { preconditions.shouldBeObject(inAddressIndex) - .checkArgument(this.walletId == inAddressIndex.walletId) .checkArgument(this.cosigner == inAddressIndex.cosigner); var hasChanged = false; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index e05fa8049..f9e608afb 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -24,7 +24,7 @@ function PublicKeyRing(opts) { this.copayersHK = opts.copayersHK || []; - this.indexes = AddressIndex.fromObj(opts.indexes) || new AddressIndex(opts); + this.indexes = opts.indexes ? AddressIndex.fromList(opts.indexes) : [new AddressIndex()]; this.publicKeysCache = opts.publicKeysCache || {}; this.nicknameFor = opts.nicknameFor || {}; @@ -36,6 +36,13 @@ PublicKeyRing.fromObj = function(data) { if (data instanceof PublicKeyRing) { throw new Error('bad data format: Did you use .toObj()?'); } + + // Support old indexes schema + if (!Array.isArray(data.indexes)) { + data.indexes.cosigner = Structure.SHARED_INDEX; + data.indexes = [data.indexes]; + } + var ret = new PublicKeyRing(data); for (var k in data.copayersExtPubKeys) { @@ -51,7 +58,7 @@ PublicKeyRing.prototype.toObj = function() { networkName: this.network.name, requiredCopayers: this.requiredCopayers, totalCopayers: this.totalCopayers, - indexes: this.indexes.toObj(), + indexes: this.getIndexesObj(), copayersExtPubKeys: this.copayersHK.map(function(b) { return b.extendedPublicKeyString(); @@ -61,6 +68,10 @@ PublicKeyRing.prototype.toObj = function() { }; }; +PublicKeyRing.prototype.getIndexesObj = function(i) { + return this.indexes.map(function(i) { return i.toObj(); }); +} + PublicKeyRing.prototype.getCopayerId = function(i) { preconditions.checkArgument(typeof i !== 'undefined'); return this.copayerIds[i]; @@ -176,6 +187,16 @@ PublicKeyRing.prototype.getAddress = function(index, isChange) { return address; }; +PublicKeyRing.prototype.getSharedIndex = function() { + return this.getIndex(Structure.SHARED_INDEX); +}; + +PublicKeyRing.prototype.getIndex = function(cosigner) { + var index = this.indexes.filter(function(i) { return i.cosigner == cosigner }); + if (index.length != 1) throw new Error('no index for cosigner'); + return index[0]; +}; + PublicKeyRing.prototype.pathForAddress = function(address) { var path = this.addressToPath[address]; if (!path) throw new Error('Couldn\'t find path for address ' + address); @@ -191,9 +212,10 @@ PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange) { //generate a new address, update index. PublicKeyRing.prototype.generateAddress = function(isChange) { isChange = !!isChange; - var index = isChange ? this.indexes.getChangeIndex() : this.indexes.getReceiveIndex(); + var shared = this.getIndex(Structure.SHARED_INDEX); + var index = isChange ? shared.getChangeIndex() : shared.getReceiveIndex(); var ret = this.getAddress(index, isChange); - this.indexes.increment(isChange); + shared.increment(isChange); return ret; }; @@ -206,9 +228,10 @@ PublicKeyRing.prototype.getAddresses = function(opts) { PublicKeyRing.prototype.getAddressesInfo = function(opts) { opts = opts || {}; + var shared = this.getIndex(Structure.SHARED_INDEX); var ret = []; if (!opts.excludeChange) { - for (var i = 0; i < this.indexes.getChangeIndex(); i++) { + for (var i = 0; i < shared.getChangeIndex(); i++) { var a = this.getAddress(i, true); ret.unshift({ address: this.getAddress(i, true), @@ -219,7 +242,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) { } if (!opts.excludeMain) { - for (var i = 0; i < this.indexes.getReceiveIndex(); i++) { + for (var i = 0; i < shared.getReceiveIndex(); i++) { var a = this.getAddress(i, false); ret.unshift({ address: a, @@ -303,17 +326,25 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) { }; PublicKeyRing.prototype.merge = function(inPKR, ignoreId) { - var hasChanged = false; - this._checkInPKR(inPKR, ignoreId); - if (this.indexes.merge(inPKR.indexes)) - hasChanged = true; + var hasChanged = false; + hasChanged |= this.mergeIndexes(inPKR.indexes); + hasChanged |= this._mergePubkeys(inPKR); - if (this._mergePubkeys(inPKR)) - hasChanged = true; - - return hasChanged; + return !!hasChanged; }; +PublicKeyRing.prototype.mergeIndexes = function(indexes) { + var self = this; + var hasChanged = false; + + indexes.forEach(function(theirs) { + var mine = self.getIndex(theirs.cosigner); + hasChanged |= mine.merge(theirs); + }); + + return !!hasChanged +} + module.exports = require('soop')(PublicKeyRing); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index fec67735b..20e5d1b1f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -81,8 +81,8 @@ Wallet.prototype.connectToAll = function() { Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { this.log('RECV INDEXES:', data); - var inIndexes = AddressIndex.fromObj(data.indexes); - var hasChanged = this.publicKeyRing.indexes.merge(inIndexes); + var inIndexes = AddressIndex.fromList(data.indexes); + var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes); if (hasChanged) { this.emit('publicKeyRingUpdated'); this.store(); @@ -439,11 +439,11 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { }); }; Wallet.prototype.sendIndexes = function(recipients) { - this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.indexes.toObj()); + this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.getIndexesObj()); this.network.send(recipients, { type: 'indexes', - indexes: this.publicKeyRing.indexes.toObj(), + indexes: this.publicKeyRing.getIndexesObj(), walletId: this.id, }); }; @@ -752,20 +752,21 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos return ntxid; }; +// TODO: Updetear todos los indices Wallet.prototype.updateIndexes = function(callback) { var self = this; - var start = self.publicKeyRing.indexes.changeIndex; + var start = self.publicKeyRing.getSharedIndex().changeIndex; self.log('Updating indexes...'); self.indexDiscovery(start, true, 20, function(err, changeIndex) { if (err) return callback(err); if (changeIndex != -1) - self.publicKeyRing.indexes.changeIndex = changeIndex + 1; + self.publicKeyRing.getSharedIndex().changeIndex = changeIndex + 1; - start = self.publicKeyRing.indexes.receiveIndex; + start = self.publicKeyRing.getSharedIndex().receiveIndex; self.indexDiscovery(start, false, 20, function(err, receiveIndex) { if (err) return callback(err); if (receiveIndex != -1) - self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1; + self.publicKeyRing.getSharedIndex().receiveIndex = receiveIndex + 1; self.log('Indexes updated'); self.emit('publicKeyRingUpdated'); diff --git a/test/test.AddressIndex.js b/test/test.AddressIndex.js index ccb08ff96..767041529 100644 --- a/test/test.AddressIndex.js +++ b/test/test.AddressIndex.js @@ -22,7 +22,6 @@ var createAI = function() { var i = new AddressIndex(); should.exist(i); - i.walletId = '1234567'; i.cosigner = 1; return i; @@ -50,7 +49,6 @@ describe('AddressIndex model', function() { should.exist(data); var i2 = AddressIndex.fromObj(data); - i2.walletId.should.equal(i.walletId); i2.cosigner.should.equal(i.cosigner); i2.getChangeIndex().should.equal(changeN); @@ -76,7 +74,6 @@ describe('AddressIndex model', function() { for (var i = 0; i < 7; i++) j.increment(false); var j2 = new AddressIndex({ - walletId: j.walletId, cosigner: j.cosigner, }); j2.merge(j).should.equal(true); @@ -86,14 +83,6 @@ describe('AddressIndex model', function() { j2.merge(j).should.equal(false); }); - it('#merge should fail with different walletId', function() { - var j1 = new AddressIndex({ walletId: '1234' }); - var j2 = new AddressIndex({ walletId: '4321' }); - - var merge = function() { j2.merge(j1); }; - merge.should.throw(Error); - }) - it('#merge should fail with different cosigner index', function() { var j1 = new AddressIndex({ walletId: '1234', cosigner: 2 }); var j2 = new AddressIndex({ walletId: '1234', cosigner: 3 }); diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index b2405a161..c60aa8b01 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -5,6 +5,9 @@ var should = chai.should(); var bitcore = bitcore || require('bitcore'); var Address = bitcore.Address; var buffertools = bitcore.buffertools; + +var Structure = require('../js/models/core/Structure'); + try { var copay = require('copay'); //browser } catch (e) { @@ -85,7 +88,7 @@ describe('PublicKeyRing model', function() { } }); - it('show be able to tostore and read', function() { + it('show be able to to store and read', function() { var k = createW(); var w = k.w; var copayers = k.copayers; @@ -112,8 +115,8 @@ describe('PublicKeyRing model', function() { }).should.throw(); } - w2.indexes.getChangeIndex().should.equal(changeN); - w2.indexes.getReceiveIndex().should.equal(addressN); + w2.getSharedIndex().getChangeIndex().should.equal(changeN); + w2.getSharedIndex().getReceiveIndex().should.equal(addressN); }); @@ -168,8 +171,8 @@ describe('PublicKeyRing model', function() { for (var i = 0; i < 2; i++) w.generateAddress(false); - w.indexes.getChangeIndex().should.equal(3); - w.indexes.getReceiveIndex().should.equal(2); + w.getSharedIndex().getChangeIndex().should.equal(3); + w.getSharedIndex().getReceiveIndex().should.equal(2); }); it('#merge index tests', function() { @@ -188,8 +191,8 @@ describe('PublicKeyRing model', function() { w2.merge(w).should.equal(true); w2.requiredCopayers.should.equal(3); w2.totalCopayers.should.equal(5); - w2.indexes.getChangeIndex().should.equal(2); - w2.indexes.getReceiveIndex().should.equal(3); + w2.getSharedIndex().getChangeIndex().should.equal(2); + w2.getSharedIndex().getReceiveIndex().should.equal(3); // w2.merge(w).should.equal(false); diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index 30615a990..b0aa3a622 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -102,8 +102,8 @@ describe('TxProposals model', function() { var b = w.txps[ntxid].builder; var tx = b.build(); tx.isComplete().should.equal(false); - b.sign(priv2.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex())); - b.sign(priv3.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex())); + b.sign(priv2.getAll(pkr.getSharedIndex().getReceiveIndex(), pkr.getSharedIndex().getChangeIndex())); + b.sign(priv3.getAll(pkr.getSharedIndex().getReceiveIndex(), pkr.getSharedIndex().getChangeIndex())); tx = b.build(); tx.isComplete().should.equal(true); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 20dd4b6ba..3e392833c 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -9,6 +9,7 @@ try { var copay = require('../copay'); //node } var Wallet = require('../js/models/core/Wallet'); +var Structure = require('../js/models/core/Structure'); var Storage = require('./mocks/FakeStorage'); var Network = require('./mocks/FakeNetwork'); var Blockchain = require('./mocks/FakeBlockchain'); @@ -341,13 +342,15 @@ describe('Wallet model', function() { it('handle network indexes correctly', function() { var w = createW(); var aiObj = { - walletId: w.id, - changeIndex: 3, - receiveIndex: 2 + indexes: [{ + cosigner: Structure.SHARED_INDEX, + changeIndex: 3, + receiveIndex: 2 + }] }; w._handleIndexes('senderID', aiObj, true); - w.publicKeyRing.indexes.getReceiveIndex(2); - w.publicKeyRing.indexes.getChangeIndex(3); + w.publicKeyRing.getSharedIndex().getReceiveIndex(2); + w.publicKeyRing.getSharedIndex().getChangeIndex(3); }); it('handle network pubKeyRings correctly', function() { @@ -363,19 +366,19 @@ describe('Wallet model', function() { networkName: w.networkName, requiredCopayers: w.requiredCopayers, totalCopayers: w.totalCopayers, - indexes: { - walletId: undefined, + indexes: [{ + cosigner: Structure.SHARED_INDEX, changeIndex: 2, receiveIndex: 3 - }, + }], copayersExtPubKeys: cepk, nicknameFor: {}, }; w._handlePublicKeyRing('senderID', { publicKeyRing: pkrObj }, true); - w.publicKeyRing.indexes.getReceiveIndex(2); - w.publicKeyRing.indexes.getChangeIndex(3); + w.publicKeyRing.getSharedIndex().getReceiveIndex(2); + w.publicKeyRing.getSharedIndex().getChangeIndex(3); for (var i = 0; i < w.requiredCopayers; i++) { w.publicKeyRing.toObj().copayersExtPubKeys[i].should.equal(cepk[i]); } @@ -729,8 +732,8 @@ describe('Wallet model', function() { }); w.updateIndexes(function(err) { - w.publicKeyRing.indexes.receiveIndex.should.equal(15); - w.publicKeyRing.indexes.changeIndex.should.equal(15); + w.publicKeyRing.getSharedIndex().receiveIndex.should.equal(15); + w.publicKeyRing.getSharedIndex().changeIndex.should.equal(15); done(); }); }); diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index d240acace..f57b740c7 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -10,7 +10,7 @@ var FakeBlockchain = require('./mocks/FakeBlockchain'); var FakeStorage = require('./mocks/FakeStorage'); var WalletFactory = require('../js/models/core/WalletFactory'); -var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"cosigner":2,"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; +var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; describe('WalletFactory model', function() { var config = { @@ -94,6 +94,22 @@ describe('WalletFactory model', function() { JSON.stringify(w.toObj()).should.equal(o); }); + it('support old index schema: #fromObj #toObj round trip', function() { + var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; + var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; + + var wf = new WalletFactory(config, '0.0.5'); + var w = wf.fromObj(JSON.parse(o)); + + should.exist(w); + w.id.should.equal("dbfe10c3fae71cea"); + should.exist(w.publicKeyRing.getCopayerId); + should.exist(w.txProposals.toObj); + should.exist(w.privateKey.toObj); + + JSON.stringify(w.toObj()).should.equal(o2); + }); + it('should create wallet from encrypted object', function() { var wf = new WalletFactory(config, '0.0.1'); var walletObj = JSON.parse(o); From b02cb1798992d69ab08499e76ff6f8e7d31708a5 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Tue, 1 Jul 2014 12:49:50 -0300 Subject: [PATCH 19/44] Create indexes for all copayers --- js/models/core/AddressIndex.js | 15 ++++++++++++++- js/models/core/PublicKeyRing.js | 3 ++- test/test.AddressIndex.js | 13 +++++++++++++ test/test.PublicKeyRing.js | 23 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index bfc9b6464..484865b87 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -6,9 +6,22 @@ var Structure = require('./Structure'); function AddressIndex(opts) { opts = opts || {}; - this.cosigner = opts.cosigner || Structure.SHARED_INDEX; + this.cosigner = opts.cosigner this.changeIndex = opts.changeIndex || 0; this.receiveIndex = opts.receiveIndex || 0; + + if (typeof this.cosigner === 'undefined') { + this.cosigner = Structure.SHARED_INDEX; + } +} + +AddressIndex.init = function(totalCopayers) { + preconditions.shouldBeNumber(totalCopayers); + var indexes = [new AddressIndex()]; + for (var i = 0 ; i < totalCopayers ; i++) { + indexes.push(new AddressIndex({cosigner: i})); + } + return indexes; } AddressIndex.fromList = function(indexes) { diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index f9e608afb..627706128 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -24,7 +24,8 @@ function PublicKeyRing(opts) { this.copayersHK = opts.copayersHK || []; - this.indexes = opts.indexes ? AddressIndex.fromList(opts.indexes) : [new AddressIndex()]; + this.indexes = opts.indexes ? AddressIndex.fromList(opts.indexes) + : AddressIndex.init(this.totalCopayers); this.publicKeysCache = opts.publicKeysCache || {}; this.nicknameFor = opts.nicknameFor || {}; diff --git a/test/test.AddressIndex.js b/test/test.AddressIndex.js index 767041529..3d87d9868 100644 --- a/test/test.AddressIndex.js +++ b/test/test.AddressIndex.js @@ -12,6 +12,7 @@ try { } var PublicKeyRing = copay.PublicKeyRing; var AddressIndex = copay.AddressIndex; +var Structure = copay.Structure; var config = { @@ -34,6 +35,18 @@ describe('AddressIndex model', function() { should.exist(i); }); + it('should init indexes', function() { + var is = AddressIndex.init(2); + should.exist(is); + is.length.should.equal(3); + + var cosigners = is.map(function(i) { return i.cosigner; }); + cosigners.indexOf(Structure.SHARED_INDEX).should.not.equal(-1); + cosigners.indexOf(0).should.not.equal(-1); + cosigners.indexOf(1).should.not.equal(-1); + cosigners.indexOf(2).should.equal(-1); + }); + it('show be able to store and read', function() { var i = createAI(); var changeN = 2; diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index c60aa8b01..0bb2523a5 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -383,6 +383,29 @@ describe('PublicKeyRing model', function() { }); + it('#getIndex should return the right one', function() { + var config = { + networkName: 'livenet', + }; + var p = new PublicKeyRing(config); + var i = p.getIndex(Structure.SHARED_INDEX); + should.exist(i); + i.cosigner.should.equal(Structure.SHARED_INDEX); + var shared = p.getSharedIndex(); + shared.should.equal(i); + }); + + it('#getIndex should throw error', function() { + var config = { + networkName: 'livenet', + }; + var p = new PublicKeyRing(config); + + (function badCosigner() { + return p.getIndex(54); + }).should.throw(); + }); + it('#getRedeemScriptMap check tests', function() { var k = createW(); var w = k.w; From 04b6aa40036d4a4b3eca389451b226983e88ba8a Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 3 Jul 2014 11:18:01 -0300 Subject: [PATCH 20/44] PublicKeyRing handles one index for each cosigner --- js/models/core/PrivateKey.js | 11 +++-- js/models/core/PublicKeyRing.js | 72 +++++++++++++++++++--------- js/models/core/Structure.js | 1 + js/models/core/Wallet.js | 4 +- test/test.PublicKeyRing.js | 39 ++++++++------- test/test.TxProposals.js | 85 ++++++++++++++++++++------------- test/test.Wallet.js | 18 +++---- 7 files changed, 140 insertions(+), 90 deletions(-) diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js index b1149f28b..9abd30e95 100644 --- a/js/models/core/PrivateKey.js +++ b/js/models/core/PrivateKey.js @@ -16,6 +16,7 @@ function PrivateKey(opts) { var init = opts.extendedPrivateKeyString || this.network.name; this.bip = opts.HK || new HK(init); this.privateKeyCache = opts.privateKeyCache || {}; + this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex'); }; PrivateKey.prototype.getId = function() { @@ -101,21 +102,21 @@ PrivateKey.prototype.getForPath = function(path) { return wk; }; -PrivateKey.prototype.get = function(index, isChange) { - var path = Structure.FullBranch(index, isChange); +PrivateKey.prototype.get = function(index, isChange, cosigner) { + var path = Structure.FullBranch(index, isChange, cosigner); return this.getForPath(path); }; -PrivateKey.prototype.getAll = function(receiveIndex, changeIndex) { +PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) { if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined') throw new Error('Invalid parameters'); var ret = []; for (var i = 0; i < receiveIndex; i++) { - ret.push(this.get(i, false)); + ret.push(this.get(i, false, cosigner)); } for (var i = 0; i < changeIndex; i++) { - ret.push(this.get(i, true)); + ret.push(this.get(i, true, cosigner)); } return ret; }; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 627706128..b82c76365 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -148,10 +148,10 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) { return newEpk; }; -PublicKeyRing.prototype.getPubKeys = function(index, isChange) { +PublicKeyRing.prototype.getPubKeys = function(index, isChange, cosigner) { this._checkKeys(); - var path = Structure.Branch(index, isChange); + var path = Structure.Branch(index, isChange, cosigner); var pubKeys = this.publicKeysCache[path]; if (!pubKeys) { pubKeys = []; @@ -174,17 +174,19 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) { }; // TODO this could be cached -PublicKeyRing.prototype.getRedeemScript = function(index, isChange) { - var pubKeys = this.getPubKeys(index, isChange); +PublicKeyRing.prototype.getRedeemScript = function(index, isChange, cosigner) { + var pubKeys = this.getPubKeys(index, isChange, cosigner); var script = Script.createMultisig(this.requiredCopayers, pubKeys); return script; }; // TODO this could be cached -PublicKeyRing.prototype.getAddress = function(index, isChange) { - var script = this.getRedeemScript(index, isChange); +PublicKeyRing.prototype.getAddress = function(index, isChange, id) { + var cosigner = typeof id === 'string' ? this.getCosigner(id) : id; + + var script = this.getRedeemScript(index, isChange, cosigner); var address = Address.fromScript(script, this.network.name); - this.addressToPath[address.toString()] = Structure.FullBranch(index, isChange); + this.addressToPath[address.toString()] = Structure.FullBranch(index, isChange, cosigner); return address; }; @@ -192,7 +194,10 @@ PublicKeyRing.prototype.getSharedIndex = function() { return this.getIndex(Structure.SHARED_INDEX); }; -PublicKeyRing.prototype.getIndex = function(cosigner) { +// Overloaded to receive a PubkeyString or a consigner index +PublicKeyRing.prototype.getIndex = function(id) { + var cosigner = typeof id === 'string' ? this.getCosigner(id) : id; + var index = this.indexes.filter(function(i) { return i.cosigner == cosigner }); if (index.length != 1) throw new Error('no index for cosigner'); return index[0]; @@ -205,18 +210,20 @@ PublicKeyRing.prototype.pathForAddress = function(address) { }; // TODO this could be cached -PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange) { - var addr = this.getAddress(index, isChange); +PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) { + var cosigner = this.getCosigner(pubkey); + var addr = this.getAddress(index, isChange, cosigner); return Script.createP2SH(addr.payload()).getBuffer().toString('hex'); }; //generate a new address, update index. -PublicKeyRing.prototype.generateAddress = function(isChange) { +PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) { isChange = !!isChange; - var shared = this.getIndex(Structure.SHARED_INDEX); - var index = isChange ? shared.getChangeIndex() : shared.getReceiveIndex(); - var ret = this.getAddress(index, isChange); - shared.increment(isChange); + var cosigner = this.getCosigner(pubkey); + var addrIndex = this.getIndex(cosigner); + var index = isChange ? addrIndex.getChangeIndex() : addrIndex.getReceiveIndex(); + var ret = this.getAddress(index, isChange, cosigner); + addrIndex.increment(isChange); return ret; }; @@ -226,16 +233,37 @@ PublicKeyRing.prototype.getAddresses = function(opts) { }); }; +PublicKeyRing.prototype.getCosigner = function(pubKey) { + preconditions.checkArgument(pubKey); + var sorted = this.copayersHK.map(function(h, i){ + return h.eckey.public.toString('hex'); + }).sort(function(h1, h2){ return h1.localeCompare(h2); }); + + var index = sorted.indexOf(pubKey); + if (index == -1) throw new Error('no public key in ring'); + + return index; +} + + PublicKeyRing.prototype.getAddressesInfo = function(opts) { + var ret = []; + var self = this; + this.indexes.forEach(function(index) { + ret = ret.concat(self.getAddressesInfoForIndex(index, opts)); + }); + return ret; +} + +PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts) { opts = opts || {}; - var shared = this.getIndex(Structure.SHARED_INDEX); var ret = []; if (!opts.excludeChange) { - for (var i = 0; i < shared.getChangeIndex(); i++) { - var a = this.getAddress(i, true); + for (var i = 0; i < index.changeIndex; i++) { + var a = this.getAddress(i, true, index.cosigner); ret.unshift({ - address: this.getAddress(i, true), + address: a, addressStr: a.toString(), isChange: true }); @@ -243,8 +271,8 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) { } if (!opts.excludeMain) { - for (var i = 0; i < shared.getReceiveIndex(); i++) { - var a = this.getAddress(i, false); + for (var i = 0; i < index.receiveIndex; i++) { + var a = this.getAddress(i, false, index.cosigner); ret.unshift({ address: a, addressStr: a.toString(), @@ -259,7 +287,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) { // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { var p = Structure.indicesForPath(path); - var script = this.getRedeemScript(p.index, p.isChange); + var script = this.getRedeemScript(p.index, p.isChange, p.cosigner); map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex'); }; diff --git a/js/models/core/Structure.js b/js/models/core/Structure.js index aafee4c0f..ff47c43f0 100644 --- a/js/models/core/Structure.js +++ b/js/models/core/Structure.js @@ -40,6 +40,7 @@ Structure.indicesForPath = function(path) { return { isChange: s[3] === '1', index: parseInt(s[4]), + cosigner: parseInt(s[2]) }; }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 20e5d1b1f..800ef5d8e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -51,6 +51,7 @@ function Wallet(opts) { this.registeredPeerIds = []; this.addressBook = opts.addressBook || {}; this.backupOffered = opts.backupOffered || false; + this.publicKey = this.privateKey.publicHex; } Wallet.parent = EventEmitter; @@ -462,7 +463,7 @@ Wallet.prototype.getName = function() { }; Wallet.prototype._doGenerateAddress = function(isChange) { - return this.publicKeyRing.generateAddress(isChange); + return this.publicKeyRing.generateAddress(isChange, this.publicKey); }; @@ -516,7 +517,6 @@ Wallet.prototype.sign = function(ntxid, cb) { if (cb) cb(false); } - var pkr = self.publicKeyRing; var keys = self.privateKey.getForPaths(txp.inputChainPaths); var b = txp.builder; diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index 0bb2523a5..11051de07 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -36,7 +36,8 @@ var createW = function(networkName) { return { w: w, - copayers: copayers + copayers: copayers, + pub: w.copayersHK[0].eckey.public.toString('hex') }; }; @@ -88,7 +89,7 @@ describe('PublicKeyRing model', function() { } }); - it('show be able to to store and read', function() { + it('should be able to to store and read', function() { var k = createW(); var w = k.w; var copayers = k.copayers; @@ -96,10 +97,10 @@ describe('PublicKeyRing model', function() { var addressN = 2; var start = new Date().getTime(); for (var i = 0; i < changeN; i++) { - w.generateAddress(true); + w.generateAddress(true, k.pub); } for (var i = 0; i < addressN; i++) { - w.generateAddress(false); + w.generateAddress(false, k.pub); } var data = w.toObj(); @@ -115,8 +116,8 @@ describe('PublicKeyRing model', function() { }).should.throw(); } - w2.getSharedIndex().getChangeIndex().should.equal(changeN); - w2.getSharedIndex().getReceiveIndex().should.equal(addressN); + w2.getIndex(k.pub).getChangeIndex().should.equal(changeN); + w2.getIndex(k.pub).getReceiveIndex().should.equal(addressN); }); @@ -126,7 +127,7 @@ describe('PublicKeyRing model', function() { [true, false].forEach(function(isChange){ for (var i = 0; i < 2; i++) { - var a = w.generateAddress(isChange); + var a = w.generateAddress(isChange, k.pub); a.isValid().should.equal(true); a.isScript().should.equal(true); a.network().name.should.equal('livenet'); @@ -148,7 +149,7 @@ describe('PublicKeyRing model', function() { [true, false].forEach(function(isChange){ for (var i = 0; i < 2; i++) { - w.generateAddress(isChange); + w.generateAddress(isChange, k.pub); } }); @@ -167,12 +168,12 @@ describe('PublicKeyRing model', function() { var w = k.w; for (var i = 0; i < 3; i++) - w.generateAddress(true); + w.generateAddress(true, k.pub); for (var i = 0; i < 2; i++) - w.generateAddress(false); + w.generateAddress(false, k.pub); - w.getSharedIndex().getChangeIndex().should.equal(3); - w.getSharedIndex().getReceiveIndex().should.equal(2); + w.getIndex(k.pub).getChangeIndex().should.equal(3); + w.getIndex(k.pub).getReceiveIndex().should.equal(2); }); it('#merge index tests', function() { @@ -180,9 +181,9 @@ describe('PublicKeyRing model', function() { var w = k.w; for (var i = 0; i < 2; i++) - w.generateAddress(true); + w.generateAddress(true, k.pub); for (var i = 0; i < 3; i++) - w.generateAddress(false); + w.generateAddress(false, k.pub); var w2 = new PublicKeyRing({ networkName: 'livenet', @@ -191,8 +192,8 @@ describe('PublicKeyRing model', function() { w2.merge(w).should.equal(true); w2.requiredCopayers.should.equal(3); w2.totalCopayers.should.equal(5); - w2.getSharedIndex().getChangeIndex().should.equal(2); - w2.getSharedIndex().getReceiveIndex().should.equal(3); + w2.getIndex(k.pub).getChangeIndex().should.equal(2); + w2.getIndex(k.pub).getReceiveIndex().should.equal(3); // w2.merge(w).should.equal(false); @@ -391,8 +392,6 @@ describe('PublicKeyRing model', function() { var i = p.getIndex(Structure.SHARED_INDEX); should.exist(i); i.cosigner.should.equal(Structure.SHARED_INDEX); - var shared = p.getSharedIndex(); - shared.should.equal(i); }); it('#getIndex should throw error', function() { @@ -412,9 +411,9 @@ describe('PublicKeyRing model', function() { var amount = 2; for (var i = 0; i < amount; i++) - w.generateAddress(true); + w.generateAddress(true, k.pub); for (var i = 0; i < amount; i++) - w.generateAddress(false); + w.generateAddress(false, k.pub); var m = w.getRedeemScriptMap([ 'm/45\'/2147483647/1/0', diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index b0aa3a622..de069446d 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -51,12 +51,15 @@ var createPKR = function(bip32s) { w.addCopayer(); } } - w.generateAddress(false); - w.generateAddress(false); - w.generateAddress(false); - w.generateAddress(true); - w.generateAddress(true); - w.generateAddress(true); + + var pubkey = bip32s[0].publicHex; + + w.generateAddress(false, pubkey); + w.generateAddress(false, pubkey); + w.generateAddress(false, pubkey); + w.generateAddress(true, pubkey); + w.generateAddress(true, pubkey); + w.generateAddress(true, pubkey); return w; }; @@ -77,19 +80,22 @@ describe('TxProposals model', function() { var priv = new PrivateKey(config); var priv2 = new PrivateKey(config); var priv3 = new PrivateKey(config); + var pub = priv.publicHex; + var ts = Date.now(); var pkr = createPKR([priv, priv2, priv3]); var opts = { remainderOut: { - address: pkr.generateAddress(true).toString() + address: pkr.generateAddress(true, pub).toString() } }; var w = new TxProposals({ networkName: config.networkName, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -102,8 +108,10 @@ describe('TxProposals model', function() { var b = w.txps[ntxid].builder; var tx = b.build(); tx.isComplete().should.equal(false); - b.sign(priv2.getAll(pkr.getSharedIndex().getReceiveIndex(), pkr.getSharedIndex().getChangeIndex())); - b.sign(priv3.getAll(pkr.getSharedIndex().getReceiveIndex(), pkr.getSharedIndex().getChangeIndex())); + + var ringIndex = pkr.getIndex(pub); + b.sign(priv2.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.cosigner)); + b.sign(priv3.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.cosigner)); tx = b.build(); tx.isComplete().should.equal(true); @@ -132,6 +140,7 @@ describe('TxProposals model', function() { opts = opts || {}; var amountSat = bitcore.Bignum(amountSatStr); + var pub = priv.publicHex; if (!pkr.isComplete()) { throw new Error('publicKeyRing is not complete'); @@ -139,7 +148,7 @@ describe('TxProposals model', function() { if (!opts.remainderOut) { opts.remainderOut = { - address: pkr.generateAddress(true).toString() + address: pkr.generateAddress(true, pub).toString() }; }; @@ -181,14 +190,16 @@ describe('TxProposals model', function() { it('#getUsedUnspend', function() { var priv = new PrivateKey(config); + var pub = priv.publicHex; + var w = new TxProposals({ networkName: config.networkName, }); var start = new Date().getTime(); var pkr = createPKR([priv]); var ts = Date.now(); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -204,6 +215,8 @@ describe('TxProposals model', function() { it('#merge with self', function() { var priv = new PrivateKey(config); + var pub = priv.publicHex; + var w = new TxProposals({ networkName: config.networkName, }); @@ -211,8 +224,8 @@ describe('TxProposals model', function() { var pkr = createPKR([priv]); var ts = Date.now(); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -246,11 +259,13 @@ describe('TxProposals model', function() { it('#merge, merge signatures case 1', function() { var priv2 = new PrivateKey(config); var priv = new PrivateKey(config); + var pub = priv.publicHex; + var ts = Date.now(); var pkr = createPKR([priv]); var opts = { remainderOut: { - address: pkr.generateAddress(true).toString() + address: pkr.generateAddress(true, pub).toString() } }; @@ -258,8 +273,8 @@ describe('TxProposals model', function() { var w = new TxProposals({ networkName: config.networkName, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -282,8 +297,8 @@ describe('TxProposals model', function() { networkName: config.networkName, publicKeyRing: w.publicKeyRing, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w2.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -346,6 +361,7 @@ describe('TxProposals model', function() { var priv = PrivateKey.fromObj(o1); var priv2 = PrivateKey.fromObj(o2); var priv3 = PrivateKey.fromObj(o3); + var pub = priv.publicHex; var ts = Date.now(); var pkr = createPKR([priv, priv2]); @@ -354,9 +370,9 @@ describe('TxProposals model', function() { address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr' } }; - var addressToSign = pkr.generateAddress(false); + var addressToSign = pkr.generateAddress(false, pub); unspentTest[0].address = addressToSign.toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); var tx, txb; var w = new TxProposals({ @@ -459,19 +475,22 @@ describe('TxProposals model', function() { var priv = new PrivateKey(config); var priv2 = new PrivateKey(config); var priv3 = new PrivateKey(config); + var pub = priv.publicHex; + + var ts = Date.now(); var pkr = createPKR([priv, priv2, priv3]); var opts = { remainderOut: { - address: pkr.generateAddress(true).toString() + address: pkr.generateAddress(true, pub).toString() } }; var w = new TxProposals({ networkName: config.networkName, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -491,8 +510,8 @@ describe('TxProposals model', function() { var w2 = new TxProposals({ networkName: config.networkName, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w2.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -511,8 +530,8 @@ describe('TxProposals model', function() { var w3 = new TxProposals({ networkName: config.networkName, }); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w3.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', @@ -558,6 +577,8 @@ describe('TxProposals model', function() { it('#toObj #fromObj roundtrip', function() { var priv = new PrivateKey(config); + var pub = priv.publicHex; + var pkr = createPKR([priv]); var w = new TxProposals({ walletId: 'qwerty', @@ -565,8 +586,8 @@ describe('TxProposals model', function() { }); var ts = Date.now(); - unspentTest[0].address = pkr.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = pkr.getAddress(index, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange, pub); w.add(createTx( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 3e392833c..ce4196a0b 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -123,7 +123,7 @@ describe('Wallet model', function() { var opts = {}; var w = cachedCreateW(); addCopayers(w); - w.publicKeyRing.generateAddress(false); + w.publicKeyRing.generateAddress(false, w.publicKey); w.publicKeyRing.isComplete().should.equal(true); w.generateAddress(true).isValid().should.equal(true); w.generateAddress(true, function(addr) { @@ -177,8 +177,8 @@ describe('Wallet model', function() { var w = cachedCreateW2(); - unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true); + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); var ntxid = w.createTxSync( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', @@ -202,8 +202,8 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var comment = 'This is a comment'; - unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true); + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); var ntxid = w.createTxSync( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', @@ -224,8 +224,8 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var comment = 'Lorem ipsum dolor sit amet, suas euismod vis te, velit deleniti vix an. Pri ex suscipit similique, inermis per'; - unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true); + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); var badCreate = function() { w.createTxSync( @@ -260,8 +260,8 @@ describe('Wallet model', function() { var ts = Date.now(); for (var isChange = false; !isChange; isChange = true) { for (var index = 0; index < 3; index++) { - unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); + unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange, w.publicKey); w.createTxSync( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '123456789', From fd2cf54eb4c6c2069c4aa4169ed255a4aefcc5e4 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 3 Jul 2014 13:04:01 -0300 Subject: [PATCH 21/44] Hide empty addresses from othe copayers --- index.html | 4 ++-- js/controllers/addresses.js | 3 ++- js/filters.js | 8 ++++++++ js/models/core/PublicKeyRing.js | 16 +++++++++++----- js/models/core/Wallet.js | 2 +- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index c08288641..6c7d97a0d 100644 --- a/index.html +++ b/index.html @@ -389,7 +389,7 @@
- @@ -411,7 +411,7 @@ - + Show all Show less diff --git a/js/controllers/addresses.js b/js/controllers/addresses.js index 3a0f7bacd..a0281e1b1 100644 --- a/js/controllers/addresses.js +++ b/js/controllers/addresses.js @@ -33,7 +33,8 @@ angular.module('copayApp.controllers').controller('AddressesController', $scope.addresses.push({ 'address': addrinfo.addressStr, 'balance': $rootScope.balanceByAddr ? $rootScope.balanceByAddr[addrinfo.addressStr] : 0, - 'isChange': addrinfo.isChange + 'isChange': addrinfo.isChange, + 'owned': addrinfo.owned }); } $scope.selectedAddr = $scope.addresses[0]; diff --git a/js/filters.js b/js/filters.js index 46d56469d..6b0a99ab0 100644 --- a/js/filters.js +++ b/js/filters.js @@ -17,6 +17,14 @@ angular.module('copayApp.filters', []) return false; }; }) + .filter('removeEmpty', function() { + return function(elements) { + // Hide empty addresses from other copayers + return elements.filter(function(e) { + return e.owned || e.balance > 0; + }); + } + }) .filter('limitAddress', function() { return function(elements, showAll) { if (elements.length <= 1 || showAll) { diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index b82c76365..90cab98cd 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -246,18 +246,22 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { } -PublicKeyRing.prototype.getAddressesInfo = function(opts) { +PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) { var ret = []; var self = this; + var cosigner = pubkey && this.getCosigner(pubkey); this.indexes.forEach(function(index) { - ret = ret.concat(self.getAddressesInfoForIndex(index, opts)); + ret = ret.concat(self.getAddressesInfoForIndex(index, opts, cosigner)); }); return ret; } -PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts) { +PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, cosigner) { opts = opts || {}; + var isOwned = index.cosigner == Structure.SHARED_INDEX + || index.cosigner == cosigner; + var ret = []; if (!opts.excludeChange) { for (var i = 0; i < index.changeIndex; i++) { @@ -265,7 +269,8 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts) { ret.unshift({ address: a, addressStr: a.toString(), - isChange: true + isChange: true, + owned: isOwned }); } } @@ -276,7 +281,8 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts) { ret.unshift({ address: a, addressStr: a.toString(), - isChange: false + isChange: false, + owned: isOwned }); } } diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 800ef5d8e..ae9514926 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -588,7 +588,7 @@ Wallet.prototype.getAddressesStr = function(opts) { }; Wallet.prototype.getAddressesInfo = function(opts) { - return this.publicKeyRing.getAddressesInfo(opts); + return this.publicKeyRing.getAddressesInfo(opts, this.publicKey); }; Wallet.prototype.addressIsOwn = function(addrStr, opts) { From 2abc35ae577024fbe99b6cae89d633e814daef46 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 3 Jul 2014 16:42:03 -0300 Subject: [PATCH 22/44] Add update indexes and support old indexes schema --- js/models/core/AddressIndex.js | 11 ++++++++ js/models/core/PublicKeyRing.js | 9 ++----- js/models/core/Wallet.js | 46 +++++++++++++++++++++------------ test/test.Wallet.js | 23 ++++++++++------- test/test.WalletFactory.js | 2 +- 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index 484865b87..d6f953a53 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -35,6 +35,17 @@ AddressIndex.fromObj = function(data) { return new AddressIndex(data); }; +AddressIndex.serialize = function(indexes) { + return indexes.map(function(i) { return i.toObj(); }); +} + +AddressIndex.update = function(shared, totalCopayers) { + var indexes = this.init(totalCopayers); + indexes[0].changeIndex = shared.changeIndex; + indexes[0].receiveIndex = shared.receiveIndex; + return this.serialize(indexes); +}; + AddressIndex.prototype.toObj = function() { return { cosigner: this.cosigner, diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 90cab98cd..a08c160cf 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -40,8 +40,7 @@ PublicKeyRing.fromObj = function(data) { // Support old indexes schema if (!Array.isArray(data.indexes)) { - data.indexes.cosigner = Structure.SHARED_INDEX; - data.indexes = [data.indexes]; + data.indexes = AddressIndex.update(data.indexes, data.totalCopayers); } var ret = new PublicKeyRing(data); @@ -59,7 +58,7 @@ PublicKeyRing.prototype.toObj = function() { networkName: this.network.name, requiredCopayers: this.requiredCopayers, totalCopayers: this.totalCopayers, - indexes: this.getIndexesObj(), + indexes: AddressIndex.serialize(this.indexes), copayersExtPubKeys: this.copayersHK.map(function(b) { return b.extendedPublicKeyString(); @@ -69,10 +68,6 @@ PublicKeyRing.prototype.toObj = function() { }; }; -PublicKeyRing.prototype.getIndexesObj = function(i) { - return this.indexes.map(function(i) { return i.toObj(); }); -} - PublicKeyRing.prototype.getCopayerId = function(i) { preconditions.checkArgument(typeof i !== 'undefined'); return this.copayerIds[i]; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index ae9514926..e03a75762 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -440,11 +440,12 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { }); }; Wallet.prototype.sendIndexes = function(recipients) { - this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.getIndexesObj()); + var indexes = AddressIndex.serialize(this.publicKeyRing.indexes); + this.log('### INDEXES TO:', recipients || 'All', indexes); this.network.send(recipients, { type: 'indexes', - indexes: this.publicKeyRing.getIndexesObj(), + indexes: indexes, walletId: this.id, }); }; @@ -752,34 +753,45 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos return ntxid; }; -// TODO: Updetear todos los indices Wallet.prototype.updateIndexes = function(callback) { var self = this; - var start = self.publicKeyRing.getSharedIndex().changeIndex; self.log('Updating indexes...'); - self.indexDiscovery(start, true, 20, function(err, changeIndex) { + + var tasks = this.publicKeyRing.indexes.map(function(index) { + return function(callback) { + self.updateIndex(index, callback); + }; + }); + + async.parallel(tasks, function(err) { + if (err) callback(err); + self.log('Indexes updated'); + self.emit('publicKeyRingUpdated'); + self.store(); + callback(); + }); +} + +Wallet.prototype.updateIndex = function(index, callback) { + var self = this; + self.indexDiscovery(index.changeIndex, true, index.cosigner, 20, function(err, changeIndex) { if (err) return callback(err); if (changeIndex != -1) - self.publicKeyRing.getSharedIndex().changeIndex = changeIndex + 1; + index.changeIndex = changeIndex + 1; - start = self.publicKeyRing.getSharedIndex().receiveIndex; - self.indexDiscovery(start, false, 20, function(err, receiveIndex) { + self.indexDiscovery(index.receiveIndex, false, index.cosigner, 20, function(err, receiveIndex) { if (err) return callback(err); if (receiveIndex != -1) - self.publicKeyRing.getSharedIndex().receiveIndex = receiveIndex + 1; - - self.log('Indexes updated'); - self.emit('publicKeyRingUpdated'); - self.store(); + index.receiveIndex = receiveIndex + 1; callback(); }); }); } -Wallet.prototype.deriveAddresses = function(index, amout, isChange) { +Wallet.prototype.deriveAddresses = function(index, amout, isChange, cosigner) { var ret = new Array(amout); for (var i = 0; i < amout; i++) { - ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString(); + ret[i] = this.publicKeyRing.getAddress(index + i, isChange, cosigner).toString(); } return ret; } @@ -787,7 +799,7 @@ Wallet.prototype.deriveAddresses = function(index, amout, isChange) { // This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, // using a scan window of @gap. The argument @change defines the branch to scan: internal or external. // Returns -1 if no activity is found in range. -Wallet.prototype.indexDiscovery = function(start, change, gap, cb) { +Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) { var scanIndex = start; var lastActive = -1; var hasActivity = false; @@ -797,7 +809,7 @@ Wallet.prototype.indexDiscovery = function(start, change, gap, cb) { function _do(next) { // Optimize window to minimize the derivations. var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; - var addresses = self.deriveAddresses(scanIndex, scanWindow, change); + var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner); self.blockchain.checkActivity(addresses, function(err, actives) { if (err) throw err; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index ce4196a0b..b21f25774 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -670,8 +670,8 @@ describe('Wallet model', function() { before(function() { w = cachedCreateW2(); - ADDRESSES_CHANGE = w.deriveAddresses(0, 20, true); - ADDRESSES_RECEIVE = w.deriveAddresses(0, 20, false); + ADDRESSES_CHANGE = w.deriveAddresses(0, 20, true, 0); + ADDRESSES_RECEIVE = w.deriveAddresses(0, 20, false, 0); }); var mockFakeActivity = function(f) { @@ -690,7 +690,7 @@ describe('Wallet model', function() { mockFakeActivity(function(index) { return false; }); - w.indexDiscovery(0, false, 5, function(e, lastActive) { + w.indexDiscovery(0, false, 0, 5, function(e, lastActive) { lastActive.should.equal(-1); done(); }); @@ -700,7 +700,7 @@ describe('Wallet model', function() { mockFakeActivity(function(index) { return index <= 7; }); - w.indexDiscovery(0, false, 5, function(e, lastActive) { + w.indexDiscovery(0, false, 0, 5, function(e, lastActive) { lastActive.should.equal(7); done(); }); @@ -710,7 +710,7 @@ describe('Wallet model', function() { mockFakeActivity(function(index) { return index <= 10 || index == 17; }); - w.indexDiscovery(0, false, 5, function(e, lastActive) { + w.indexDiscovery(0, false, 0, 5, function(e, lastActive) { lastActive.should.equal(10); done(); }); @@ -720,7 +720,7 @@ describe('Wallet model', function() { mockFakeActivity(function(index) { return index <= 14 && index % 2 == 0; }); - w.indexDiscovery(0, false, 5, function(e, lastActive) { + w.indexDiscovery(0, false, 0, 5, function(e, lastActive) { lastActive.should.equal(14); done(); }); @@ -732,8 +732,11 @@ describe('Wallet model', function() { }); w.updateIndexes(function(err) { - w.publicKeyRing.getSharedIndex().receiveIndex.should.equal(15); - w.publicKeyRing.getSharedIndex().changeIndex.should.equal(15); + w.publicKeyRing.getIndex(0).receiveIndex.should.equal(15); + w.publicKeyRing.getIndex(0).changeIndex.should.equal(15); + + w.publicKeyRing.getIndex(1).receiveIndex.should.equal(0); + w.publicKeyRing.getIndex(1).changeIndex.should.equal(0); done(); }); }); @@ -753,8 +756,8 @@ describe('Wallet model', function() { it('#deriveAddresses', function(done) { var w = cachedCreateW2(); - var addresses1 = w.deriveAddresses(0, 5, false); - var addresses2 = w.deriveAddresses(4, 5, false); + var addresses1 = w.deriveAddresses(0, 5, false, 0); + var addresses2 = w.deriveAddresses(4, 5, false, 0); addresses1.length.should.equal(5); addresses2.length.should.equal(5); diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index f57b740c7..018779871 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -96,7 +96,7 @@ describe('WalletFactory model', function() { it('support old index schema: #fromObj #toObj round trip', function() { var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; - var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; + var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"cosigner":2147483647,"changeIndex":0,"receiveIndex":0},{"cosigner":0,"changeIndex":0,"receiveIndex":0},{"cosigner":1,"changeIndex":0,"receiveIndex":0},{"cosigner":2,"changeIndex":0,"receiveIndex":0},{"cosigner":3,"changeIndex":0,"receiveIndex":0},{"cosigner":4,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}'; var wf = new WalletFactory(config, '0.0.5'); var w = wf.fromObj(JSON.parse(o)); From 680b0b553ead2481c000c7b3a397682f4e4cc72b Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 3 Jul 2014 16:57:07 -0300 Subject: [PATCH 23/44] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bbbc4c45..226fbe6b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "copay", - "version": "0.2.1", + "version": "0.3.1", "description": "A multisignature wallet", "repository": { "type": "git", From 16f520811b99d5f751100ac8973bd31c3c5b2148 Mon Sep 17 00:00:00 2001 From: Bechi Date: Thu, 3 Jul 2014 17:44:00 -0300 Subject: [PATCH 24/44] fix columns layout --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index c08288641..a4e568f86 100644 --- a/index.html +++ b/index.html @@ -629,7 +629,7 @@ - + diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index fec67735b..436ef0795 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -38,12 +38,6 @@ function Wallet(opts) { this.id = opts.id || Wallet.getRandomId(); this.name = opts.name; - // Renew token every 24hs - if (opts.tokenTime && new Date().getTime() - opts.tokenTime < 86400000) { - this.token = opts.token; - this.tokenTime = opts.tokenTime; - } - this.verbose = opts.verbose; this.publicKeyRing.walletId = this.id; this.txProposals.walletId = this.id; @@ -222,11 +216,6 @@ Wallet.prototype._optsToObj = function() { version: this.version, }; - if (this.token) { - obj.token = this.token; - obj.tokenTime = new Date().getTime(); - } - return obj; }; @@ -284,7 +273,6 @@ Wallet.prototype.netStart = function(callback) { var startOpts = { copayerId: myId, privkey: myIdPriv, - token: self.token, maxPeers: self.totalCopayers }; @@ -294,7 +282,6 @@ Wallet.prototype.netStart = function(callback) { net.start(startOpts, function() { self.emit('ready', net.getPeer()); - self.token = net.peer.options.token; setTimeout(function() { self.emit('publicKeyRingUpdated', true); self.scheduleConnect(); @@ -382,8 +369,6 @@ Wallet.fromObj = function(o, storage, network, blockchain) { Wallet.prototype.toEncryptedObj = function() { var walletObj = this.toObj(); - delete walletObj.opts.token; - delete walletObj.opts.tokenTime; return this.storage.export(walletObj); }; diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index bfd2ad10a..8c2db9747 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -346,6 +346,7 @@ Network.prototype.start = function(opts, openCallback) { if (!self.criticalError && self.tries < self.reconnectAttempts) { self.tries++; + self.opts.token = util.sha256(self.peerId).toString('hex'); self.peer = new Peer(self.peerId, self.opts); self.started = true; self._setupPeerHandlers(openCallback); From e22eed8610bcee81467abbdfc91137e47a9dcaaa Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Mon, 7 Jul 2014 12:17:36 -0300 Subject: [PATCH 34/44] Fix double wallet store on open/create/join --- js/models/core/Wallet.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 4396d3936..cff598e9c 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -301,7 +301,6 @@ Wallet.prototype.netStart = function(callback) { self.emit('publicKeyRingUpdated', true); self.scheduleConnect(); self.emit('txProposalsUpdated'); - self.store(); }, 10); }); }; From d0693442ceae354b0aff3f12545850807c7dc83b Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Mon, 7 Jul 2014 12:26:42 -0300 Subject: [PATCH 35/44] Remove magic number fixes #736 --- js/models/core/Wallet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e03a75762..5ccea0ce5 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -774,12 +774,13 @@ Wallet.prototype.updateIndexes = function(callback) { Wallet.prototype.updateIndex = function(index, callback) { var self = this; - self.indexDiscovery(index.changeIndex, true, index.cosigner, 20, function(err, changeIndex) { + var SCANN_WINDOW = 20; + self.indexDiscovery(index.changeIndex, true, index.cosigner, SCANN_WINDOW, function(err, changeIndex) { if (err) return callback(err); if (changeIndex != -1) index.changeIndex = changeIndex + 1; - self.indexDiscovery(index.receiveIndex, false, index.cosigner, 20, function(err, receiveIndex) { + self.indexDiscovery(index.receiveIndex, false, index.cosigner, SCANN_WINDOW, function(err, receiveIndex) { if (err) return callback(err); if (receiveIndex != -1) index.receiveIndex = receiveIndex + 1; From 9b88ba67d088343e5bc5d0ff2e099e194a2b2399 Mon Sep 17 00:00:00 2001 From: Alan Stoll Date: Mon, 7 Jul 2014 11:59:52 -0400 Subject: [PATCH 36/44] disable node integration in the browser window because it screws stuff up --- shell/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/index.js b/shell/index.js index 71e4543c9..f28a1ee73 100644 --- a/shell/index.js +++ b/shell/index.js @@ -24,7 +24,8 @@ module.exports = function(copay) { // create the main window mainWindow = new BrowserWindow({ width: config.window.width, - height: config.window.height + height: config.window.height, + "node-integration": "disable" }); // hide the empty window @@ -52,7 +53,7 @@ module.exports = function(copay) { mainWindow = null; }); - // mainWindow.toggleDevTools(); + //mainWindow.toggleDevTools(); }); From 490f0b45369e1a9996711d7323e8efdd568a32f8 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 7 Jul 2014 14:18:11 -0300 Subject: [PATCH 37/44] remove duplicate error --- js/services/controllerUtils.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index aa386b618..96f8322ff 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -43,20 +43,15 @@ angular.module('copayApp.services') }; root.installStartupHandlers = function(wallet, $scope) { - wallet.on('serverError', function(msg) { - notification.error('PeerJS Error', 'There was an error connecting to the PeerJS server.' + (msg || 'Check you settings and Internet connection.')); - root.onErrorDigest($scope); - $location.path('addresses'); - }); wallet.on('connectionError', function() { var message = "Looks like you are already connected to this wallet, please logout and try importing it again."; notification.error('PeerJS Error', message); root.onErrorDigest($scope); }); - wallet.on('serverError', function() { - var message = 'The PeerJS server is not responding, please try again'; - notification.error('PeerJS Error', message); - root.onErrorDigest($scope); + wallet.on('serverError', function(m) { + var message = m || 'The PeerJS server is not responding, please try again'; + $location.path('addresses'); + root.onErrorDigest($scope, message); }); wallet.on('ready', function() { $scope.loading = false; From b7752202097b70f9ae4409601e1c8ed65e14ca55 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Mon, 7 Jul 2014 10:35:06 -0700 Subject: [PATCH 38/44] update to bitcore v0.1.25 ...to fix an error where the scriptForAddress function no longer exists. --- bower.json | 2 +- package.json | 2 +- test/test.Wallet.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 8004861c5..068b61a46 100644 --- a/bower.json +++ b/bower.json @@ -18,7 +18,7 @@ "sjcl": "1.0.0", "file-saver": "*", "qrcode-decoder-js": "*", - "bitcore": "0.1.24", + "bitcore": "0.1.25", "angular-moment": "~0.7.1", "socket.io-client": ">=1.0.0", "mousetrap": "1.4.6" diff --git a/package.json b/package.json index 1e943a55b..1c5db39b1 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "homepage": "https://github.com/bitpay/copay", "devDependencies": { "async": "0.9.0", - "bitcore": "0.1.24", + "bitcore": "0.1.25", "blanket": "1.1.6", "browser-pack": "2.0.1", "browserify": "3.32.1", diff --git a/test/test.Wallet.js b/test/test.Wallet.js index d53182fcd..4dc6c5f0d 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -595,7 +595,7 @@ describe('Wallet model', function() { }]; var addr = w.generateAddress().toString(); utxo[0].address = addr; - utxo[0].scriptPubKey = TransactionBuilder.scriptForAddress(addr).serialize().toString('hex'); + utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex'); return utxo; }; var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh'; From 3522c3067d9e78bc2da05df20a5d561dc298806a Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Mon, 7 Jul 2014 16:02:04 -0300 Subject: [PATCH 39/44] Add peer.js file --- lib/peer.js | 2657 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2657 insertions(+) create mode 100644 lib/peer.js diff --git a/lib/peer.js b/lib/peer.js new file mode 100644 index 000000000..0289ef5f2 --- /dev/null +++ b/lib/peer.js @@ -0,0 +1,2657 @@ +/*! peerjs.js build:0.3.8, development. Copyright(c) 2013 Michelle Bu */ +(function(exports){ +var binaryFeatures = {}; +binaryFeatures.useBlobBuilder = (function(){ + try { + new Blob([]); + return false; + } catch (e) { + return true; + } +})(); + +binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ + try { + return (new Blob([new Uint8Array([])])).size === 0; + } catch (e) { + return true; + } +})(); + +exports.binaryFeatures = binaryFeatures; +exports.BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; + +function BufferBuilder(){ + this._pieces = []; + this._parts = []; +} + +BufferBuilder.prototype.append = function(data) { + if(typeof data === 'number') { + this._pieces.push(data); + } else { + this.flush(); + this._parts.push(data); + } +}; + +BufferBuilder.prototype.flush = function() { + if (this._pieces.length > 0) { + var buf = new Uint8Array(this._pieces); + if(!binaryFeatures.useArrayBufferView) { + buf = buf.buffer; + } + this._parts.push(buf); + this._pieces = []; + } +}; + +BufferBuilder.prototype.getBuffer = function() { + this.flush(); + if(binaryFeatures.useBlobBuilder) { + var builder = new BlobBuilder(); + for(var i = 0, ii = this._parts.length; i < ii; i++) { + builder.append(this._parts[i]); + } + return builder.getBlob(); + } else { + return new Blob(this._parts); + } +}; +exports.BinaryPack = { + unpack: function(data){ + var unpacker = new Unpacker(data); + return unpacker.unpack(); + }, + pack: function(data){ + var packer = new Packer(); + packer.pack(data); + var buffer = packer.getBuffer(); + return buffer; + } +}; + +function Unpacker (data){ + // Data is ArrayBuffer + this.index = 0; + this.dataBuffer = data; + this.dataView = new Uint8Array(this.dataBuffer); + this.length = this.dataBuffer.byteLength; +} + + +Unpacker.prototype.unpack = function(){ + var type = this.unpack_uint8(); + if (type < 0x80){ + var positive_fixnum = type; + return positive_fixnum; + } else if ((type ^ 0xe0) < 0x20){ + var negative_fixnum = (type ^ 0xe0) - 0x20; + return negative_fixnum; + } + var size; + if ((size = type ^ 0xa0) <= 0x0f){ + return this.unpack_raw(size); + } else if ((size = type ^ 0xb0) <= 0x0f){ + return this.unpack_string(size); + } else if ((size = type ^ 0x90) <= 0x0f){ + return this.unpack_array(size); + } else if ((size = type ^ 0x80) <= 0x0f){ + return this.unpack_map(size); + } + switch(type){ + case 0xc0: + return null; + case 0xc1: + return undefined; + case 0xc2: + return false; + case 0xc3: + return true; + case 0xca: + return this.unpack_float(); + case 0xcb: + return this.unpack_double(); + case 0xcc: + return this.unpack_uint8(); + case 0xcd: + return this.unpack_uint16(); + case 0xce: + return this.unpack_uint32(); + case 0xcf: + return this.unpack_uint64(); + case 0xd0: + return this.unpack_int8(); + case 0xd1: + return this.unpack_int16(); + case 0xd2: + return this.unpack_int32(); + case 0xd3: + return this.unpack_int64(); + case 0xd4: + return undefined; + case 0xd5: + return undefined; + case 0xd6: + return undefined; + case 0xd7: + return undefined; + case 0xd8: + size = this.unpack_uint16(); + return this.unpack_string(size); + case 0xd9: + size = this.unpack_uint32(); + return this.unpack_string(size); + case 0xda: + size = this.unpack_uint16(); + return this.unpack_raw(size); + case 0xdb: + size = this.unpack_uint32(); + return this.unpack_raw(size); + case 0xdc: + size = this.unpack_uint16(); + return this.unpack_array(size); + case 0xdd: + size = this.unpack_uint32(); + return this.unpack_array(size); + case 0xde: + size = this.unpack_uint16(); + return this.unpack_map(size); + case 0xdf: + size = this.unpack_uint32(); + return this.unpack_map(size); + } +} + +Unpacker.prototype.unpack_uint8 = function(){ + var byte = this.dataView[this.index] & 0xff; + this.index++; + return byte; +}; + +Unpacker.prototype.unpack_uint16 = function(){ + var bytes = this.read(2); + var uint16 = + ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); + this.index += 2; + return uint16; +} + +Unpacker.prototype.unpack_uint32 = function(){ + var bytes = this.read(4); + var uint32 = + ((bytes[0] * 256 + + bytes[1]) * 256 + + bytes[2]) * 256 + + bytes[3]; + this.index += 4; + return uint32; +} + +Unpacker.prototype.unpack_uint64 = function(){ + var bytes = this.read(8); + var uint64 = + ((((((bytes[0] * 256 + + bytes[1]) * 256 + + bytes[2]) * 256 + + bytes[3]) * 256 + + bytes[4]) * 256 + + bytes[5]) * 256 + + bytes[6]) * 256 + + bytes[7]; + this.index += 8; + return uint64; +} + + +Unpacker.prototype.unpack_int8 = function(){ + var uint8 = this.unpack_uint8(); + return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); +}; + +Unpacker.prototype.unpack_int16 = function(){ + var uint16 = this.unpack_uint16(); + return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); +} + +Unpacker.prototype.unpack_int32 = function(){ + var uint32 = this.unpack_uint32(); + return (uint32 < Math.pow(2, 31) ) ? uint32 : + uint32 - Math.pow(2, 32); +} + +Unpacker.prototype.unpack_int64 = function(){ + var uint64 = this.unpack_uint64(); + return (uint64 < Math.pow(2, 63) ) ? uint64 : + uint64 - Math.pow(2, 64); +} + +Unpacker.prototype.unpack_raw = function(size){ + if ( this.length < this.index + size){ + throw new Error('BinaryPackFailure: index is out of range' + + ' ' + this.index + ' ' + size + ' ' + this.length); + } + var buf = this.dataBuffer.slice(this.index, this.index + size); + this.index += size; + + //buf = util.bufferToString(buf); + + return buf; +} + +Unpacker.prototype.unpack_string = function(size){ + var bytes = this.read(size); + var i = 0, str = '', c, code; + while(i < size){ + c = bytes[i]; + if ( c < 128){ + str += String.fromCharCode(c); + i++; + } else if ((c ^ 0xc0) < 32){ + code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); + str += String.fromCharCode(code); + i += 2; + } else { + code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | + (bytes[i+2] & 63); + str += String.fromCharCode(code); + i += 3; + } + } + this.index += size; + return str; +} + +Unpacker.prototype.unpack_array = function(size){ + var objects = new Array(size); + for(var i = 0; i < size ; i++){ + objects[i] = this.unpack(); + } + return objects; +} + +Unpacker.prototype.unpack_map = function(size){ + var map = {}; + for(var i = 0; i < size ; i++){ + var key = this.unpack(); + var value = this.unpack(); + map[key] = value; + } + return map; +} + +Unpacker.prototype.unpack_float = function(){ + var uint32 = this.unpack_uint32(); + var sign = uint32 >> 31; + var exp = ((uint32 >> 23) & 0xff) - 127; + var fraction = ( uint32 & 0x7fffff ) | 0x800000; + return (sign == 0 ? 1 : -1) * + fraction * Math.pow(2, exp - 23); +} + +Unpacker.prototype.unpack_double = function(){ + var h32 = this.unpack_uint32(); + var l32 = this.unpack_uint32(); + var sign = h32 >> 31; + var exp = ((h32 >> 20) & 0x7ff) - 1023; + var hfrac = ( h32 & 0xfffff ) | 0x100000; + var frac = hfrac * Math.pow(2, exp - 20) + + l32 * Math.pow(2, exp - 52); + return (sign == 0 ? 1 : -1) * frac; +} + +Unpacker.prototype.read = function(length){ + var j = this.index; + if (j + length <= this.length) { + return this.dataView.subarray(j, j + length); + } else { + throw new Error('BinaryPackFailure: read index out of range'); + } +} + +function Packer(){ + this.bufferBuilder = new BufferBuilder(); +} + +Packer.prototype.getBuffer = function(){ + return this.bufferBuilder.getBuffer(); +} + +Packer.prototype.pack = function(value){ + var type = typeof(value); + if (type == 'string'){ + this.pack_string(value); + } else if (type == 'number'){ + if (Math.floor(value) === value){ + this.pack_integer(value); + } else{ + this.pack_double(value); + } + } else if (type == 'boolean'){ + if (value === true){ + this.bufferBuilder.append(0xc3); + } else if (value === false){ + this.bufferBuilder.append(0xc2); + } + } else if (type == 'undefined'){ + this.bufferBuilder.append(0xc0); + } else if (type == 'object'){ + if (value === null){ + this.bufferBuilder.append(0xc0); + } else { + var constructor = value.constructor; + if (constructor == Array){ + this.pack_array(value); + } else if (constructor == Blob || constructor == File) { + this.pack_bin(value); + } else if (constructor == ArrayBuffer) { + if(binaryFeatures.useArrayBufferView) { + this.pack_bin(new Uint8Array(value)); + } else { + this.pack_bin(value); + } + } else if ('BYTES_PER_ELEMENT' in value){ + if(binaryFeatures.useArrayBufferView) { + this.pack_bin(new Uint8Array(value.buffer)); + } else { + this.pack_bin(value.buffer); + } + } else if (constructor == Object){ + this.pack_object(value); + } else if (constructor == Date){ + this.pack_string(value.toString()); + } else if (typeof value.toBinaryPack == 'function'){ + this.bufferBuilder.append(value.toBinaryPack()); + } else { + throw new Error('Type "' + constructor.toString() + '" not yet supported'); + } + } + } else { + throw new Error('Type "' + type + '" not yet supported'); + } + this.bufferBuilder.flush(); +} + + +Packer.prototype.pack_bin = function(blob){ + var length = blob.length || blob.byteLength || blob.size; + if (length <= 0x0f){ + this.pack_uint8(0xa0 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xda) ; + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdb); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + return; + } + this.bufferBuilder.append(blob); +} + +Packer.prototype.pack_string = function(str){ + var length = utf8Length(str); + + if (length <= 0x0f){ + this.pack_uint8(0xb0 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xd8) ; + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xd9); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + return; + } + this.bufferBuilder.append(str); +} + +Packer.prototype.pack_array = function(ary){ + var length = ary.length; + if (length <= 0x0f){ + this.pack_uint8(0x90 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xdc) + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdd); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + } + for(var i = 0; i < length ; i++){ + this.pack(ary[i]); + } +} + +Packer.prototype.pack_integer = function(num){ + if ( -0x20 <= num && num <= 0x7f){ + this.bufferBuilder.append(num & 0xff); + } else if (0x00 <= num && num <= 0xff){ + this.bufferBuilder.append(0xcc); + this.pack_uint8(num); + } else if (-0x80 <= num && num <= 0x7f){ + this.bufferBuilder.append(0xd0); + this.pack_int8(num); + } else if ( 0x0000 <= num && num <= 0xffff){ + this.bufferBuilder.append(0xcd); + this.pack_uint16(num); + } else if (-0x8000 <= num && num <= 0x7fff){ + this.bufferBuilder.append(0xd1); + this.pack_int16(num); + } else if ( 0x00000000 <= num && num <= 0xffffffff){ + this.bufferBuilder.append(0xce); + this.pack_uint32(num); + } else if (-0x80000000 <= num && num <= 0x7fffffff){ + this.bufferBuilder.append(0xd2); + this.pack_int32(num); + } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ + this.bufferBuilder.append(0xd3); + this.pack_int64(num); + } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ + this.bufferBuilder.append(0xcf); + this.pack_uint64(num); + } else{ + throw new Error('Invalid integer'); + } +} + +Packer.prototype.pack_double = function(num){ + var sign = 0; + if (num < 0){ + sign = 1; + num = -num; + } + var exp = Math.floor(Math.log(num) / Math.LN2); + var frac0 = num / Math.pow(2, exp) - 1; + var frac1 = Math.floor(frac0 * Math.pow(2, 52)); + var b32 = Math.pow(2, 32); + var h32 = (sign << 31) | ((exp+1023) << 20) | + (frac1 / b32) & 0x0fffff; + var l32 = frac1 % b32; + this.bufferBuilder.append(0xcb); + this.pack_int32(h32); + this.pack_int32(l32); +} + +Packer.prototype.pack_object = function(obj){ + var keys = Object.keys(obj); + var length = keys.length; + if (length <= 0x0f){ + this.pack_uint8(0x80 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xde); + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdf); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + } + for(var prop in obj){ + if (obj.hasOwnProperty(prop)){ + this.pack(prop); + this.pack(obj[prop]); + } + } +} + +Packer.prototype.pack_uint8 = function(num){ + this.bufferBuilder.append(num); +} + +Packer.prototype.pack_uint16 = function(num){ + this.bufferBuilder.append(num >> 8); + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_uint32 = function(num){ + var n = num & 0xffffffff; + this.bufferBuilder.append((n & 0xff000000) >>> 24); + this.bufferBuilder.append((n & 0x00ff0000) >>> 16); + this.bufferBuilder.append((n & 0x0000ff00) >>> 8); + this.bufferBuilder.append((n & 0x000000ff)); +} + +Packer.prototype.pack_uint64 = function(num){ + var high = num / Math.pow(2, 32); + var low = num % Math.pow(2, 32); + this.bufferBuilder.append((high & 0xff000000) >>> 24); + this.bufferBuilder.append((high & 0x00ff0000) >>> 16); + this.bufferBuilder.append((high & 0x0000ff00) >>> 8); + this.bufferBuilder.append((high & 0x000000ff)); + this.bufferBuilder.append((low & 0xff000000) >>> 24); + this.bufferBuilder.append((low & 0x00ff0000) >>> 16); + this.bufferBuilder.append((low & 0x0000ff00) >>> 8); + this.bufferBuilder.append((low & 0x000000ff)); +} + +Packer.prototype.pack_int8 = function(num){ + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_int16 = function(num){ + this.bufferBuilder.append((num & 0xff00) >> 8); + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_int32 = function(num){ + this.bufferBuilder.append((num >>> 24) & 0xff); + this.bufferBuilder.append((num & 0x00ff0000) >>> 16); + this.bufferBuilder.append((num & 0x0000ff00) >>> 8); + this.bufferBuilder.append((num & 0x000000ff)); +} + +Packer.prototype.pack_int64 = function(num){ + var high = Math.floor(num / Math.pow(2, 32)); + var low = num % Math.pow(2, 32); + this.bufferBuilder.append((high & 0xff000000) >>> 24); + this.bufferBuilder.append((high & 0x00ff0000) >>> 16); + this.bufferBuilder.append((high & 0x0000ff00) >>> 8); + this.bufferBuilder.append((high & 0x000000ff)); + this.bufferBuilder.append((low & 0xff000000) >>> 24); + this.bufferBuilder.append((low & 0x00ff0000) >>> 16); + this.bufferBuilder.append((low & 0x0000ff00) >>> 8); + this.bufferBuilder.append((low & 0x000000ff)); +} + +function _utf8Replace(m){ + var code = m.charCodeAt(0); + + if(code <= 0x7ff) return '00'; + if(code <= 0xffff) return '000'; + if(code <= 0x1fffff) return '0000'; + if(code <= 0x3ffffff) return '00000'; + return '000000'; +} + +function utf8Length(str){ + if (str.length > 600) { + // Blob method faster for large strings + return (new Blob([str])).size; + } else { + return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; + } +} +/** + * Light EventEmitter. Ported from Node.js/events.js + * Eric Zhang + */ + +/** + * EventEmitter class + * Creates an object with event registering and firing methods + */ +function EventEmitter() { + // Initialise required storage variables + this._events = {}; +} + +var isArray = Array.isArray; + + +EventEmitter.prototype.addListener = function(type, listener, scope, once) { + if ('function' !== typeof listener) { + throw new Error('addListener only takes instances of Function'); + } + + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, typeof listener.listener === 'function' ? + listener.listener : listener); + + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { + + // If we've already got an array, just append. + this._events[type].push(listener); + + } else { + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + } + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener, scope) { + if ('function' !== typeof listener) { + throw new Error('.once only takes instances of Function'); + } + + var self = this; + function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }; + + g.listener = listener; + self.on(type, g); + + return this; +}; + +EventEmitter.prototype.removeListener = function(type, listener, scope) { + if ('function' !== typeof listener) { + throw new Error('removeListener only takes instances of Function'); + } + + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events[type]) return this; + + var list = this._events[type]; + + if (isArray(list)) { + var position = -1; + for (var i = 0, length = list.length; i < length; i++) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) + { + position = i; + break; + } + } + + if (position < 0) return this; + list.splice(position, 1); + if (list.length == 0) + delete this._events[type]; + } else if (list === listener || + (list.listener && list.listener === listener)) + { + delete this._events[type]; + } + + return this; +}; + + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + + +EventEmitter.prototype.removeAllListeners = function(type) { + if (arguments.length === 0) { + this._events = {}; + return this; + } + + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; +}; + +EventEmitter.prototype.emit = function(type) { + var type = arguments[0]; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + handler.apply(this, args); + } + return true; + + } else if (isArray(handler)) { + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; + } else { + return false; + } +}; + + + +/** + * Reliable transfer for Chrome Canary DataChannel impl. + * Author: @michellebu + */ +function Reliable(dc, debug) { + if (!(this instanceof Reliable)) return new Reliable(dc); + this._dc = dc; + + util.debug = debug; + + // Messages sent/received so far. + // id: { ack: n, chunks: [...] } + this._outgoing = {}; + // id: { ack: ['ack', id, n], chunks: [...] } + this._incoming = {}; + this._received = {}; + + // Window size. + this._window = 1000; + // MTU. + this._mtu = 500; + // Interval for setInterval. In ms. + this._interval = 0; + + // Messages sent. + this._count = 0; + + // Outgoing message queue. + this._queue = []; + + this._setupDC(); +}; + +// Send a message reliably. +Reliable.prototype.send = function(msg) { + // Determine if chunking is necessary. + var bl = util.pack(msg); + if (bl.size < this._mtu) { + this._handleSend(['no', bl]); + return; + } + + this._outgoing[this._count] = { + ack: 0, + chunks: this._chunk(bl) + }; + + if (util.debug) { + this._outgoing[this._count].timer = new Date(); + } + + // Send prelim window. + this._sendWindowedChunks(this._count); + this._count += 1; +}; + +// Set up interval for processing queue. +Reliable.prototype._setupInterval = function() { + // TODO: fail gracefully. + + var self = this; + this._timeout = setInterval(function() { + // FIXME: String stuff makes things terribly async. + var msg = self._queue.shift(); + if (msg._multiple) { + for (var i = 0, ii = msg.length; i < ii; i += 1) { + self._intervalSend(msg[i]); + } + } else { + self._intervalSend(msg); + } + }, this._interval); +}; + +Reliable.prototype._intervalSend = function(msg) { + var self = this; + msg = util.pack(msg); + util.blobToBinaryString(msg, function(str) { + self._dc.send(str); + }); + if (self._queue.length === 0) { + clearTimeout(self._timeout); + self._timeout = null; + //self._processAcks(); + } +}; + +// Go through ACKs to send missing pieces. +Reliable.prototype._processAcks = function() { + for (var id in this._outgoing) { + if (this._outgoing.hasOwnProperty(id)) { + this._sendWindowedChunks(id); + } + } +}; + +// Handle sending a message. +// FIXME: Don't wait for interval time for all messages... +Reliable.prototype._handleSend = function(msg) { + var push = true; + for (var i = 0, ii = this._queue.length; i < ii; i += 1) { + var item = this._queue[i]; + if (item === msg) { + push = false; + } else if (item._multiple && item.indexOf(msg) !== -1) { + push = false; + } + } + if (push) { + this._queue.push(msg); + if (!this._timeout) { + this._setupInterval(); + } + } +}; + +// Set up DataChannel handlers. +Reliable.prototype._setupDC = function() { + // Handle various message types. + var self = this; + this._dc.onmessage = function(e) { + var msg = e.data; + var datatype = msg.constructor; + // FIXME: msg is String until binary is supported. + // Once that happens, this will have to be smarter. + if (datatype === String) { + var ab = util.binaryStringToArrayBuffer(msg); + msg = util.unpack(ab); + self._handleMessage(msg); + } + }; +}; + +// Handles an incoming message. +Reliable.prototype._handleMessage = function(msg) { + var id = msg[1]; + var idata = this._incoming[id]; + var odata = this._outgoing[id]; + var data; + switch (msg[0]) { + // No chunking was done. + case 'no': + var message = id; + if (!!message) { + this.onmessage(util.unpack(message)); + } + break; + // Reached the end of the message. + case 'end': + data = idata; + + // In case end comes first. + this._received[id] = msg[2]; + + if (!data) { + break; + } + + this._ack(id); + break; + case 'ack': + data = odata; + if (!!data) { + var ack = msg[2]; + // Take the larger ACK, for out of order messages. + data.ack = Math.max(ack, data.ack); + + // Clean up when all chunks are ACKed. + if (data.ack >= data.chunks.length) { + util.log('Time: ', new Date() - data.timer); + delete this._outgoing[id]; + } else { + this._processAcks(); + } + } + // If !data, just ignore. + break; + // Received a chunk of data. + case 'chunk': + // Create a new entry if none exists. + data = idata; + if (!data) { + var end = this._received[id]; + if (end === true) { + break; + } + data = { + ack: ['ack', id, 0], + chunks: [] + }; + this._incoming[id] = data; + } + + var n = msg[2]; + var chunk = msg[3]; + data.chunks[n] = new Uint8Array(chunk); + + // If we get the chunk we're looking for, ACK for next missing. + // Otherwise, ACK the same N again. + if (n === data.ack[2]) { + this._calculateNextAck(id); + } + this._ack(id); + break; + default: + // Shouldn't happen, but would make sense for message to just go + // through as is. + this._handleSend(msg); + break; + } +}; + +// Chunks BL into smaller messages. +Reliable.prototype._chunk = function(bl) { + var chunks = []; + var size = bl.size; + var start = 0; + while (start < size) { + var end = Math.min(size, start + this._mtu); + var b = bl.slice(start, end); + var chunk = { + payload: b + } + chunks.push(chunk); + start = end; + } + util.log('Created', chunks.length, 'chunks.'); + return chunks; +}; + +// Sends ACK N, expecting Nth blob chunk for message ID. +Reliable.prototype._ack = function(id) { + var ack = this._incoming[id].ack; + + // if ack is the end value, then call _complete. + if (this._received[id] === ack[2]) { + this._complete(id); + this._received[id] = true; + } + + this._handleSend(ack); +}; + +// Calculates the next ACK number, given chunks. +Reliable.prototype._calculateNextAck = function(id) { + var data = this._incoming[id]; + var chunks = data.chunks; + for (var i = 0, ii = chunks.length; i < ii; i += 1) { + // This chunk is missing!!! Better ACK for it. + if (chunks[i] === undefined) { + data.ack[2] = i; + return; + } + } + data.ack[2] = chunks.length; +}; + +// Sends the next window of chunks. +Reliable.prototype._sendWindowedChunks = function(id) { + util.log('sendWindowedChunks for: ', id); + var data = this._outgoing[id]; + var ch = data.chunks; + var chunks = []; + var limit = Math.min(data.ack + this._window, ch.length); + for (var i = data.ack; i < limit; i += 1) { + if (!ch[i].sent || i === data.ack) { + ch[i].sent = true; + chunks.push(['chunk', id, i, ch[i].payload]); + } + } + if (data.ack + this._window >= ch.length) { + chunks.push(['end', id, ch.length]) + } + chunks._multiple = true; + this._handleSend(chunks); +}; + +// Puts together a message from chunks. +Reliable.prototype._complete = function(id) { + util.log('Completed called for', id); + var self = this; + var chunks = this._incoming[id].chunks; + var bl = new Blob(chunks); + util.blobToArrayBuffer(bl, function(ab) { + self.onmessage(util.unpack(ab)); + }); + delete this._incoming[id]; +}; + +// Ups bandwidth limit on SDP. Meant to be called during offer/answer. +Reliable.higherBandwidthSDP = function(sdp) { + // AS stands for Application-Specific Maximum. + // Bandwidth number is in kilobits / sec. + // See RFC for more info: http://www.ietf.org/rfc/rfc2327.txt + + // Chrome 31+ doesn't want us munging the SDP, so we'll let them have their + // way. + var version = navigator.appVersion.match(/Chrome\/(.*?) /); + if (version) { + version = parseInt(version[1].split('.').shift()); + if (version < 31) { + var parts = sdp.split('b=AS:30'); + var replace = 'b=AS:102400'; // 100 Mbps + if (parts.length > 1) { + return parts[0] + replace + parts[1]; + } + } + } + + return sdp; +}; + +// Overwritten, typically. +Reliable.prototype.onmessage = function(msg) {}; + +exports.Reliable = Reliable; +exports.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; +exports.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; +exports.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; +var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]}; +var dataCount = 1; + +var util = { + noop: function() {}, + + CLOUD_HOST: '0.peerjs.com', + CLOUD_PORT: 9000, + + // Browsers that need chunking: + chunkedBrowsers: {'Chrome': 1}, + chunkedMTU: 16300, // The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually. + + // Logging logic + logLevel: 0, + setLogLevel: function(level) { + var debugLevel = parseInt(level, 10); + if (!isNaN(parseInt(level, 10))) { + util.logLevel = debugLevel; + } else { + // If they are using truthy/falsy values for debug + util.logLevel = level ? 3 : 0; + } + util.log = util.warn = util.error = util.noop; + if (util.logLevel > 0) { + util.error = util._printWith('ERROR'); + } + if (util.logLevel > 1) { + util.warn = util._printWith('WARNING'); + } + if (util.logLevel > 2) { + util.log = util._print; + } + }, + setLogFunction: function(fn) { + if (fn.constructor !== Function) { + util.warn('The log function you passed in is not a function. Defaulting to regular logs.'); + } else { + util._print = fn; + } + }, + + _printWith: function(prefix) { + return function() { + var copy = Array.prototype.slice.call(arguments); + copy.unshift(prefix); + util._print.apply(util, copy); + }; + }, + _print: function () { + var err = false; + var copy = Array.prototype.slice.call(arguments); + copy.unshift('PeerJS: '); + for (var i = 0, l = copy.length; i < l; i++){ + if (copy[i] instanceof Error) { + copy[i] = '(' + copy[i].name + ') ' + copy[i].message; + err = true; + } + } + err ? console.error.apply(console, copy) : console.log.apply(console, copy); + }, + // + + // Returns browser-agnostic default config + defaultConfig: defaultConfig, + // + + // Returns the current browser. + browser: (function() { + if (window.mozRTCPeerConnection) { + return 'Firefox'; + } else if (window.webkitRTCPeerConnection) { + return 'Chrome'; + } else if (window.RTCPeerConnection) { + return 'Supported'; + } else { + return 'Unsupported'; + } + })(), + // + + // Lists which features are supported + supports: (function() { + if (typeof RTCPeerConnection === 'undefined') { + return {}; + } + + var data = true; + var audioVideo = true; + + var binaryBlob = false; + var sctp = false; + var onnegotiationneeded = !!window.webkitRTCPeerConnection; + + var pc, dc; + try { + pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); + } catch (e) { + data = false; + audioVideo = false; + } + + if (data) { + try { + dc = pc.createDataChannel('_PEERJSTEST'); + } catch (e) { + data = false; + } + } + + if (data) { + // Binary test + try { + dc.binaryType = 'blob'; + binaryBlob = true; + } catch (e) { + } + + // Reliable test. + // Unfortunately Chrome is a bit unreliable about whether or not they + // support reliable. + var reliablePC = new RTCPeerConnection(defaultConfig, {}); + try { + var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {}); + sctp = reliableDC.reliable; + } catch (e) { + } + reliablePC.close(); + } + + // FIXME: not really the best check... + if (audioVideo) { + audioVideo = !!pc.addStream; + } + + // FIXME: this is not great because in theory it doesn't work for + // av-only browsers (?). + if (!onnegotiationneeded && data) { + // sync default check. + var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); + negotiationPC.onnegotiationneeded = function() { + onnegotiationneeded = true; + // async check. + if (util && util.supports) { + util.supports.onnegotiationneeded = true; + } + }; + var negotiationDC = negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST'); + + setTimeout(function() { + negotiationPC.close(); + }, 1000); + } + + if (pc) { + pc.close(); + } + + return { + audioVideo: audioVideo, + data: data, + binaryBlob: binaryBlob, + binary: sctp, // deprecated; sctp implies binary support. + reliable: sctp, // deprecated; sctp implies reliable data. + sctp: sctp, + onnegotiationneeded: onnegotiationneeded + }; + }()), + // + + // Ensure alphanumeric ids + validateId: function(id) { + // Allow empty ids + return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id); + }, + + validateKey: function(key) { + // Allow empty keys + return !key || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key); + }, + + + debug: false, + + inherits: function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }, + extend: function(dest, source) { + for(var key in source) { + if(source.hasOwnProperty(key)) { + dest[key] = source[key]; + } + } + return dest; + }, + pack: BinaryPack.pack, + unpack: BinaryPack.unpack, + + log: function () { + if (util.debug) { + var err = false; + var copy = Array.prototype.slice.call(arguments); + copy.unshift('PeerJS: '); + for (var i = 0, l = copy.length; i < l; i++){ + if (copy[i] instanceof Error) { + copy[i] = '(' + copy[i].name + ') ' + copy[i].message; + err = true; + } + } + err ? console.error.apply(console, copy) : console.log.apply(console, copy); + } + }, + + setZeroTimeout: (function(global) { + var timeouts = []; + var messageName = 'zero-timeout-message'; + + // Like setTimeout, but only takes a function argument. There's + // no time argument (always zero) and no arguments (you have to + // use a closure). + function setZeroTimeoutPostMessage(fn) { + timeouts.push(fn); + global.postMessage(messageName, '*'); + } + + function handleMessage(event) { + if (event.source == global && event.data == messageName) { + if (event.stopPropagation) { + event.stopPropagation(); + } + if (timeouts.length) { + timeouts.shift()(); + } + } + } + if (global.addEventListener) { + global.addEventListener('message', handleMessage, true); + } else if (global.attachEvent) { + global.attachEvent('onmessage', handleMessage); + } + return setZeroTimeoutPostMessage; + }(this)), + + // Binary stuff + + // chunks a blob. + chunk: function(bl) { + var chunks = []; + var size = bl.size; + var start = index = 0; + var total = Math.ceil(size / util.chunkedMTU); + while (start < size) { + var end = Math.min(size, start + util.chunkedMTU); + var b = bl.slice(start, end); + + var chunk = { + __peerData: dataCount, + n: index, + data: b, + total: total + }; + + chunks.push(chunk); + + start = end; + index += 1; + } + dataCount += 1; + return chunks; + }, + + blobToArrayBuffer: function(blob, cb){ + var fr = new FileReader(); + fr.onload = function(evt) { + cb(evt.target.result); + }; + fr.readAsArrayBuffer(blob); + }, + blobToBinaryString: function(blob, cb){ + var fr = new FileReader(); + fr.onload = function(evt) { + cb(evt.target.result); + }; + fr.readAsBinaryString(blob); + }, + binaryStringToArrayBuffer: function(binary) { + var byteArray = new Uint8Array(binary.length); + for (var i = 0; i < binary.length; i++) { + byteArray[i] = binary.charCodeAt(i) & 0xff; + } + return byteArray.buffer; + }, + randomToken: function () { + return Math.random().toString(36).substr(2); + }, + // + + isSecure: function() { + return location.protocol === 'https:'; + } +}; + +exports.util = util; +/** + * A peer who can initiate connections with other peers. + */ +function Peer(id, options) { + if (!(this instanceof Peer)) return new Peer(id, options); + EventEmitter.call(this); + + // Deal with overloading + if (id && id.constructor == Object) { + options = id; + id = undefined; + } else if (id) { + // Ensure id is a string + id = id.toString(); + } + // + + // Configurize options + options = util.extend({ + debug: 0, // 1: Errors, 2: Warnings, 3: All logs + host: util.CLOUD_HOST, + port: util.CLOUD_PORT, + key: 'peerjs', + path: '/', + token: util.randomToken(), + config: util.defaultConfig + }, options); + this.options = options; + // Detect relative URL host. + if (options.host === '/') { + options.host = window.location.hostname; + } + // Set path correctly. + if (options.path[0] !== '/') { + options.path = '/' + options.path; + } + if (options.path[options.path.length - 1] !== '/') { + options.path += '/'; + } + + // Set whether we use SSL to same as current host + if (options.secure === undefined && options.host !== util.CLOUD_HOST) { + options.secure = util.isSecure(); + } + // Set a custom log function if present + if (options.logFunction) { + util.setLogFunction(options.logFunction); + } + util.setLogLevel(options.debug); + // + + // Sanity checks + // Ensure WebRTC supported + if (!util.supports.audioVideo && !util.supports.data ) { + this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC'); + return; + } + // Ensure alphanumeric id + if (!util.validateId(id)) { + this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid'); + return; + } + // Ensure valid key + if (!util.validateKey(options.key)) { + this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid'); + return; + } + // Ensure not using unsecure cloud server on SSL page + if (options.secure && options.host === '0.peerjs.com') { + this._delayedAbort('ssl-unavailable', + 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.'); + return; + } + // + + // States. + this.destroyed = false; // Connections have been killed + this.disconnected = false; // Connection to PeerServer killed manually but P2P connections still active + this.open = false; // Sockets and such are not yet open. + // + + // References + this.connections = {}; // DataConnections for this peer. + this._lostMessages = {}; // src => [list of messages] + // + + // Initialize the 'socket' (which is actually a mix of XHR streaming and + // websockets.) + var self = this; + this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key); + this.socket.on('message', function(data) { + self._handleMessage(data); + }); + this.socket.on('error', function(error) { + self._abort('socket-error', error); + }); + this.socket.on('close', function() { + if (!self.disconnected) { // If we haven't explicitly disconnected, emit error. + self._abort('socket-closed', 'Underlying socket is already closed.'); + } + }); + // + + // Start the connections + if (id) { + this._initialize(id); + } else { + this._retrieveId(); + } + // +}; + +util.inherits(Peer, EventEmitter); + +/** Get a unique ID from the server via XHR. */ +Peer.prototype._retrieveId = function(cb) { + var self = this; + var http = new XMLHttpRequest(); + var protocol = this.options.secure ? 'https://' : 'http://'; + var url = protocol + this.options.host + ':' + this.options.port + + this.options.path + this.options.key + '/id'; + var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); + url += queryString; + + // If there's no ID we need to wait for one before trying to init socket. + http.open('get', url, true); + http.onerror = function(e) { + util.error('Error retrieving ID', e); + var pathError = ''; + if (self.options.path === '/' && self.options.host !== util.CLOUD_HOST) { + pathError = ' If you passed in a `path` to your self-hosted PeerServer, ' + + 'you\'ll also need to pass in that same path when creating a new' + + ' Peer.'; + } + self._abort('server-error', 'Could not get an ID from the server.' + pathError); + } + http.onreadystatechange = function() { + if (http.readyState !== 4) { + return; + } + if (http.status !== 200) { + http.onerror(); + return; + } + self._initialize(http.responseText); + }; + http.send(null); +}; + +/** Initialize a connection with the server. */ +Peer.prototype._initialize = function(id) { + var self = this; + this.id = id; + this.socket.start(this.id, this.options.token); +} + +/** Handles messages from the server. */ +Peer.prototype._handleMessage = function(message) { + var type = message.type; + var payload = message.payload; + var peer = message.src; + + switch (type) { + case 'OPEN': // The connection to the server is open. + this.emit('open', this.id); + this.open = true; + break; + case 'ERROR': // Server error. + this._abort('server-error', payload.msg); + break; + case 'ID-TAKEN': // The selected ID is taken. + this._abort('unavailable-id', 'ID `' + this.id + '` is taken'); + break; + case 'INVALID-KEY': // The given API key cannot be found. + this._abort('invalid-key', 'API KEY "' + this.options.key + '" is invalid'); + break; + + // + case 'LEAVE': // Another peer has closed its connection to this peer. + util.log('Received leave message from', peer); + this._cleanupPeer(peer); + break; + + case 'EXPIRE': // The offer sent to a peer has expired without response. + this.emit('error', new Error('Could not connect to peer ' + peer)); + break; + case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option. + var connectionId = payload.connectionId; + var connection = this.getConnection(peer, connectionId); + + if (connection) { + util.warn('Offer received for existing Connection ID:', connectionId); + //connection.handleMessage(message); + } else { + // Create a new connection. + if (payload.type === 'media') { + var connection = new MediaConnection(peer, this, { + connectionId: connectionId, + _payload: payload, + metadata: payload.metadata + }); + this._addConnection(peer, connection); + this.emit('call', connection); + } else if (payload.type === 'data') { + connection = new DataConnection(peer, this, { + connectionId: connectionId, + _payload: payload, + metadata: payload.metadata, + label: payload.label, + serialization: payload.serialization, + reliable: payload.reliable + }); + this._addConnection(peer, connection); + this.emit('connection', connection); + } else { + util.warn('Received malformed connection type:', payload.type); + return; + } + // Find messages. + var messages = this._getMessages(connectionId); + for (var i = 0, ii = messages.length; i < ii; i += 1) { + connection.handleMessage(messages[i]); + } + } + break; + default: + if (!payload) { + util.warn('You received a malformed message from ' + peer + ' of type ' + type); + return; + } + + var id = payload.connectionId; + var connection = this.getConnection(peer, id); + + if (connection && connection.pc) { + // Pass it on. + connection.handleMessage(message); + } else if (id) { + // Store for possible later use + this._storeMessage(id, message); + } else { + util.warn('You received an unrecognized message:', message); + } + break; + } +} + +/** Stores messages without a set up connection, to be claimed later. */ +Peer.prototype._storeMessage = function(connectionId, message) { + if (!this._lostMessages[connectionId]) { + this._lostMessages[connectionId] = []; + } + this._lostMessages[connectionId].push(message); +} + +/** Retrieve messages from lost message store */ +Peer.prototype._getMessages = function(connectionId) { + var messages = this._lostMessages[connectionId]; + if (messages) { + delete this._lostMessages[connectionId]; + return messages; + } else { + return []; + } +} + +/** + * Returns a DataConnection to the specified peer. See documentation for a + * complete list of options. + */ +Peer.prototype.connect = function(peer, options) { + if (this.disconnected) { + util.warn('You cannot connect to a new Peer because you called ' + + '.disconnect() on this Peer and ended your connection with the' + + ' server. You can create a new Peer to reconnect.'); + this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.')); + return; + } + var connection = new DataConnection(peer, this, options); + this._addConnection(peer, connection); + return connection; +} + +/** + * Returns a MediaConnection to the specified peer. See documentation for a + * complete list of options. + */ +Peer.prototype.call = function(peer, stream, options) { + if (this.disconnected) { + util.warn('You cannot connect to a new Peer because you called ' + + '.disconnect() on this Peer and ended your connection with the' + + ' server. You can create a new Peer to reconnect.'); + this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.')); + return; + } + if (!stream) { + util.error('To call a peer, you must provide a stream from your browser\'s `getUserMedia`.'); + return; + } + options = options || {}; + options._stream = stream; + var call = new MediaConnection(peer, this, options); + this._addConnection(peer, call); + return call; +} + +/** Add a data/media connection to this peer. */ +Peer.prototype._addConnection = function(peer, connection) { + if (!this.connections[peer]) { + this.connections[peer] = []; + } + this.connections[peer].push(connection); +} + +/** Retrieve a data/media connection for this peer. */ +Peer.prototype.getConnection = function(peer, id) { + var connections = this.connections[peer]; + if (!connections) { + return null; + } + for (var i = 0, ii = connections.length; i < ii; i++) { + if (connections[i].id === id) { + return connections[i]; + } + } + return null; +} + +Peer.prototype._delayedAbort = function(type, message) { + var self = this; + util.setZeroTimeout(function(){ + self._abort(type, message); + }); +} + +/** Destroys the Peer and emits an error message. */ +Peer.prototype._abort = function(type, message) { + util.error('Aborting. Error:', message); + var err = new Error(message); + err.type = type; + this.destroy(); + this.emit('error', err); +}; + +/** + * Destroys the Peer: closes all active connections as well as the connection + * to the server. + * Warning: The peer can no longer create or accept connections after being + * destroyed. + */ +Peer.prototype.destroy = function() { + if (!this.destroyed) { + this._cleanup(); + this.disconnect(); + this.destroyed = true; + } +} + + +/** Disconnects every connection on this peer. */ +Peer.prototype._cleanup = function() { + if (this.connections) { + var peers = Object.keys(this.connections); + for (var i = 0, ii = peers.length; i < ii; i++) { + this._cleanupPeer(peers[i]); + } + } + this.emit('close'); +} + +/** Closes all connections to this peer. */ +Peer.prototype._cleanupPeer = function(peer) { + var connections = this.connections[peer]; + for (var j = 0, jj = connections.length; j < jj; j += 1) { + connections[j].close(); + } +} + +/** + * Disconnects the Peer's connection to the PeerServer. Does not close any + * active connections. + * Warning: The peer can no longer create or accept connections after being + * disconnected. It also cannot reconnect to the server. + */ +Peer.prototype.disconnect = function() { + var self = this; + util.setZeroTimeout(function(){ + if (!self.disconnected) { + self.disconnected = true; + self.open = false; + if (self.socket) { + self.socket.close(); + } + self.id = null; + } + }); +} + +/** + * Get a list of available peer IDs. If you're running your own server, you'll + * want to set allow_discovery: true in the PeerServer options. If you're using + * the cloud server, email team@peerjs.com to get the functionality enabled for + * your key. + */ +Peer.prototype.listAllPeers = function(cb) { + cb = cb || function() {}; + var self = this; + var http = new XMLHttpRequest(); + var protocol = this.options.secure ? 'https://' : 'http://'; + var url = protocol + this.options.host + ':' + this.options.port + + this.options.path + this.options.key + '/peers'; + var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); + url += queryString; + + // If there's no ID we need to wait for one before trying to init socket. + http.open('get', url, true); + http.onerror = function(e) { + self._abort('server-error', 'Could not get peers from the server.'); + cb([]); + } + http.onreadystatechange = function() { + if (http.readyState !== 4) { + return; + } + if (http.status === 401) { + var helpfulError = ''; + if (self.options.host !== util.CLOUD_HOST) { + helpfulError = 'It looks like you\'re using the cloud server. You can email ' + + 'team@peerjs.com to enable peer listing for your API key.'; + } else { + helpfulError = 'You need to enable `allow_discovery` on your self-hosted' + + ' PeerServer to use this feature.'; + } + throw new Error('It doesn\'t look like you have permission to list peers IDs. ' + helpfulError); + cb([]); + } else if (http.status !== 200) { + cb([]); + } else { + cb(JSON.parse(http.responseText)); + } + }; + http.send(null); +} + +exports.Peer = Peer; +/** + * Wraps a DataChannel between two Peers. + */ +function DataConnection(peer, provider, options) { + if (!(this instanceof DataConnection)) return new DataConnection(peer, provider, options); + EventEmitter.call(this); + + this.options = util.extend({ + serialization: 'binary', + reliable: false + }, options); + + // Connection is not open yet. + this.open = false; + this.type = 'data'; + this.peer = peer; + this.provider = provider; + + this.id = this.options.connectionId || DataConnection._idPrefix + util.randomToken(); + + this.label = this.options.label || this.id; + this.metadata = this.options.metadata; + this.serialization = this.options.serialization; + this.reliable = this.options.reliable; + + // Data channel buffering. + this._buffer = []; + this._buffering = false; + this.bufferSize = 0; + + // For storing large data. + this._chunkedData = {}; + + if (this.options._payload) { + this._peerBrowser = this.options._payload.browser; + } + + Negotiator.startConnection( + this, + this.options._payload || { + originator: true + } + ); +} + +util.inherits(DataConnection, EventEmitter); + +DataConnection._idPrefix = 'dc_'; + +/** Called by the Negotiator when the DataChannel is ready. */ +DataConnection.prototype.initialize = function(dc) { + this._dc = this.dataChannel = dc; + this._configureDataChannel(); +} + +DataConnection.prototype._configureDataChannel = function() { + var self = this; + if (util.supports.sctp) { + this._dc.binaryType = 'arraybuffer'; + } + this._dc.onopen = function() { + util.log('Data channel connection success'); + self.open = true; + self.emit('open'); + } + + // Use the Reliable shim for non Firefox browsers + if (!util.supports.sctp && this.reliable) { + this._reliable = new Reliable(this._dc, util.debug); + } + + if (this._reliable) { + this._reliable.onmessage = function(msg) { + self.emit('data', msg); + }; + } else { + this._dc.onmessage = function(e) { + self._handleDataMessage(e); + }; + } + this._dc.onclose = function(e) { + util.log('DataChannel closed for:', self.peer); + self.close(); + }; +} + +// Handles a DataChannel message. +DataConnection.prototype._handleDataMessage = function(e) { + var self = this; + var data = e.data; + var datatype = data.constructor; + if (this.serialization === 'binary' || this.serialization === 'binary-utf8') { + if (datatype === Blob) { + // Datatype should never be blob + util.blobToArrayBuffer(data, function(ab) { + data = util.unpack(ab); + self.emit('data', data); + }); + return; + } else if (datatype === ArrayBuffer) { + data = util.unpack(data); + } else if (datatype === String) { + // String fallback for binary data for browsers that don't support binary yet + var ab = util.binaryStringToArrayBuffer(data); + data = util.unpack(ab); + } + } else if (this.serialization === 'json') { + data = JSON.parse(data); + } + + // Check if we've chunked--if so, piece things back together. + // We're guaranteed that this isn't 0. + if (data.__peerData) { + var id = data.__peerData; + var chunkInfo = this._chunkedData[id] || {data: [], count: 0, total: data.total}; + + chunkInfo.data[data.n] = data.data; + chunkInfo.count += 1; + + if (chunkInfo.total === chunkInfo.count) { + // We've received all the chunks--time to construct the complete data. + data = new Blob(chunkInfo.data); + this._handleDataMessage({data: data}); + + // We can also just delete the chunks now. + delete this._chunkedData[id]; + } + + this._chunkedData[id] = chunkInfo; + return; + } + + this.emit('data', data); +} + +/** + * Exposed functionality for users. + */ + +/** Allows user to close connection. */ +DataConnection.prototype.close = function() { + if (!this.open) { + return; + } + this.open = false; + Negotiator.cleanup(this); + this.emit('close'); +} + +/** Allows user to send data. */ +DataConnection.prototype.send = function(data, chunked) { + if (!this.open) { + this.emit('error', new Error('Connection is not open. You should listen for the `open` event before sending messages.')); + return; + } + if (this._reliable) { + // Note: reliable shim sending will make it so that you cannot customize + // serialization. + this._reliable.send(data); + return; + } + var self = this; + if (this.serialization === 'json') { + this._bufferedSend(JSON.stringify(data)); + } else if (this.serialization === 'binary' || this.serialization === 'binary-utf8') { + var blob = util.pack(data); + + // For Chrome-Firefox interoperability, we need to make Firefox "chunk" + // the data it sends out. + var needsChunking = util.chunkedBrowsers[this._peerBrowser] || util.chunkedBrowsers[util.browser]; + if (needsChunking && !chunked && blob.size > util.chunkedMTU) { + this._sendChunks(blob); + return; + } + + // DataChannel currently only supports strings. + if (!util.supports.sctp) { + util.blobToBinaryString(blob, function(str) { + self._bufferedSend(str); + }); + } else if (!util.supports.binaryBlob) { + // We only do this if we really need to (e.g. blobs are not supported), + // because this conversion is costly. + util.blobToArrayBuffer(blob, function(ab) { + self._bufferedSend(ab); + }); + } else { + this._bufferedSend(blob); + } + } else { + this._bufferedSend(data); + } +} + +DataConnection.prototype._bufferedSend = function(msg) { + if (this._buffering || !this._trySend(msg)) { + this._buffer.push(msg); + this.bufferSize = this._buffer.length; + } +} + +// Returns true if the send succeeds. +DataConnection.prototype._trySend = function(msg) { + try { + this._dc.send(msg); + } catch (e) { + this._buffering = true; + + var self = this; + setTimeout(function() { + // Try again. + self._buffering = false; + self._tryBuffer(); + }, 100); + return false; + } + return true; +} + +// Try to send the first message in the buffer. +DataConnection.prototype._tryBuffer = function() { + if (this._buffer.length === 0) { + return; + } + + var msg = this._buffer[0]; + + if (this._trySend(msg)) { + this._buffer.shift(); + this.bufferSize = this._buffer.length; + this._tryBuffer(); + } +} + +DataConnection.prototype._sendChunks = function(blob) { + var blobs = util.chunk(blob); + for (var i = 0, ii = blobs.length; i < ii; i += 1) { + var blob = blobs[i]; + this.send(blob, true); + } +} + +DataConnection.prototype.handleMessage = function(message) { + var payload = message.payload; + + switch (message.type) { + case 'ANSWER': + this._peerBrowser = payload.browser; + + // Forward to negotiator + Negotiator.handleSDP(message.type, this, payload.sdp); + break; + case 'CANDIDATE': + Negotiator.handleCandidate(this, payload.candidate); + break; + default: + util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); + break; + } +} +/** + * Wraps the streaming interface between two Peers. + */ +function MediaConnection(peer, provider, options) { + if (!(this instanceof MediaConnection)) return new MediaConnection(peer, provider, options); + EventEmitter.call(this); + + this.options = util.extend({}, options); + + this.open = false; + this.type = 'media'; + this.peer = peer; + this.provider = provider; + this.metadata = this.options.metadata; + this.localStream = this.options._stream; + + this.id = this.options.connectionId || MediaConnection._idPrefix + util.randomToken(); + if (this.localStream) { + Negotiator.startConnection( + this, + {_stream: this.localStream, originator: true} + ); + } +}; + +util.inherits(MediaConnection, EventEmitter); + +MediaConnection._idPrefix = 'mc_'; + +MediaConnection.prototype.addStream = function(remoteStream) { + util.log('Receiving stream', remoteStream); + + this.remoteStream = remoteStream; + this.emit('stream', remoteStream); // Should we call this `open`? + +}; + +MediaConnection.prototype.handleMessage = function(message) { + var payload = message.payload; + + switch (message.type) { + case 'ANSWER': + // Forward to negotiator + Negotiator.handleSDP(message.type, this, payload.sdp); + this.open = true; + break; + case 'CANDIDATE': + Negotiator.handleCandidate(this, payload.candidate); + break; + default: + util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); + break; + } +} + +MediaConnection.prototype.answer = function(stream) { + if (this.localStream) { + util.warn('Local stream already exists on this MediaConnection. Are you answering a call twice?'); + return; + } + + this.options._payload._stream = stream; + + this.localStream = stream; + Negotiator.startConnection( + this, + this.options._payload + ) + // Retrieve lost messages stored because PeerConnection not set up. + var messages = this.provider._getMessages(this.id); + for (var i = 0, ii = messages.length; i < ii; i += 1) { + this.handleMessage(messages[i]); + } + this.open = true; +}; + +/** + * Exposed functionality for users. + */ + +/** Allows user to close connection. */ +MediaConnection.prototype.close = function() { + if (!this.open) { + return; + } + this.open = false; + Negotiator.cleanup(this); + this.emit('close') +}; +/** + * Manages all negotiations between Peers. + */ +var Negotiator = { + pcs: { + data: {}, + media: {} + }, // type => {peerId: {pc_id: pc}}. + //providers: {}, // provider's id => providers (there may be multiple providers/client. + queue: [] // connections that are delayed due to a PC being in use. +} + +Negotiator._idPrefix = 'pc_'; + +/** Returns a PeerConnection object set up correctly (for data, media). */ +Negotiator.startConnection = function(connection, options) { + var pc = Negotiator._getPeerConnection(connection, options); + + if (connection.type === 'media' && options._stream) { + // Add the stream. + pc.addStream(options._stream); + } + + // Set the connection's PC. + connection.pc = connection.peerConnection = pc; + // What do we need to do now? + if (options.originator) { + if (connection.type === 'data') { + // Create the datachannel. + var config = {}; + // Dropping reliable:false support, since it seems to be crashing + // Chrome. + /*if (util.supports.sctp && !options.reliable) { + // If we have canonical reliable support... + config = {maxRetransmits: 0}; + }*/ + // Fallback to ensure older browsers don't crash. + if (!util.supports.sctp) { + config = {reliable: options.reliable}; + } + var dc = pc.createDataChannel(connection.label, config); + connection.initialize(dc); + } + + if (!util.supports.onnegotiationneeded) { + Negotiator._makeOffer(connection); + } + } else { + Negotiator.handleSDP('OFFER', connection, options.sdp); + } +} + +Negotiator._getPeerConnection = function(connection, options) { + if (!Negotiator.pcs[connection.type]) { + util.error(connection.type + ' is not a valid connection type. Maybe you overrode the `type` property somewhere.'); + } + + if (!Negotiator.pcs[connection.type][connection.peer]) { + Negotiator.pcs[connection.type][connection.peer] = {}; + } + var peerConnections = Negotiator.pcs[connection.type][connection.peer]; + + var pc; + // Not multiplexing while FF and Chrome have not-great support for it. + /*if (options.multiplex) { + ids = Object.keys(peerConnections); + for (var i = 0, ii = ids.length; i < ii; i += 1) { + pc = peerConnections[ids[i]]; + if (pc.signalingState === 'stable') { + break; // We can go ahead and use this PC. + } + } + } else */ + if (options.pc) { // Simplest case: PC id already provided for us. + pc = Negotiator.pcs[connection.type][connection.peer][options.pc]; + } + + if (!pc || pc.signalingState !== 'stable') { + pc = Negotiator._startPeerConnection(connection); + } + return pc; +} + +/* +Negotiator._addProvider = function(provider) { + if ((!provider.id && !provider.disconnected) || !provider.socket.open) { + // Wait for provider to obtain an ID. + provider.on('open', function(id) { + Negotiator._addProvider(provider); + }); + } else { + Negotiator.providers[provider.id] = provider; + } +}*/ + + +/** Start a PC. */ +Negotiator._startPeerConnection = function(connection) { + util.log('Creating RTCPeerConnection.'); + + var id = Negotiator._idPrefix + util.randomToken(); + var optional = {}; + + if (connection.type === 'data' && !util.supports.sctp) { + optional = {optional: [{RtpDataChannels: true}]}; + } else if (connection.type === 'media') { + // Interop req for chrome. + optional = {optional: [{DtlsSrtpKeyAgreement: true}]}; + } + + var pc = new RTCPeerConnection(connection.provider.options.config, optional); + Negotiator.pcs[connection.type][connection.peer][id] = pc; + + Negotiator._setupListeners(connection, pc, id); + + return pc; +} + +/** Set up various WebRTC listeners. */ +Negotiator._setupListeners = function(connection, pc, pc_id) { + var peerId = connection.peer; + var connectionId = connection.id; + var provider = connection.provider; + + // ICE CANDIDATES. + util.log('Listening for ICE candidates.'); + pc.onicecandidate = function(evt) { + if (evt.candidate) { + util.log('Received ICE candidates for:', connection.peer); + provider.socket.send({ + type: 'CANDIDATE', + payload: { + candidate: evt.candidate, + type: connection.type, + connectionId: connection.id + }, + dst: peerId + }); + } + }; + + pc.oniceconnectionstatechange = function() { + switch (pc.iceConnectionState) { + case 'disconnected': + case 'failed': + util.log('iceConnectionState is disconnected, closing connections to ' + peerId); + connection.close(); + break; + case 'completed': + pc.onicecandidate = util.noop; + break; + } + }; + + // Fallback for older Chrome impls. + pc.onicechange = pc.oniceconnectionstatechange; + + // ONNEGOTIATIONNEEDED (Chrome) + util.log('Listening for `negotiationneeded`'); + pc.onnegotiationneeded = function() { + util.log('`negotiationneeded` triggered'); + if (pc.signalingState == 'stable') { + Negotiator._makeOffer(connection); + } else { + util.log('onnegotiationneeded triggered when not stable. Is another connection being established?'); + } + }; + + // DATACONNECTION. + util.log('Listening for data channel'); + // Fired between offer and answer, so options should already be saved + // in the options hash. + pc.ondatachannel = function(evt) { + util.log('Received data channel'); + var dc = evt.channel; + var connection = provider.getConnection(peerId, connectionId); + connection.initialize(dc); + }; + + // MEDIACONNECTION. + util.log('Listening for remote stream'); + pc.onaddstream = function(evt) { + util.log('Received remote stream'); + var stream = evt.stream; + provider.getConnection(peerId, connectionId).addStream(stream); + }; +} + +Negotiator.cleanup = function(connection) { + util.log('Cleaning up PeerConnection to ' + connection.peer); + + var pc = connection.pc; + + if (!!pc && (pc.readyState !== 'closed' || pc.signalingState !== 'closed')) { + pc.close(); + connection.pc = null; + } +} + +Negotiator._makeOffer = function(connection) { + var pc = connection.pc; + pc.createOffer(function(offer) { + util.log('Created offer.'); + + if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { + offer.sdp = Reliable.higherBandwidthSDP(offer.sdp); + } + + pc.setLocalDescription(offer, function() { + util.log('Set localDescription: offer', 'for:', connection.peer); + connection.provider.socket.send({ + type: 'OFFER', + payload: { + sdp: offer, + type: connection.type, + label: connection.label, + connectionId: connection.id, + reliable: connection.reliable, + serialization: connection.serialization, + metadata: connection.metadata, + browser: util.browser + }, + dst: connection.peer + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setLocalDescription, ', err); + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to createOffer, ', err); + }, connection.options.constraints); +} + +Negotiator._makeAnswer = function(connection) { + var pc = connection.pc; + + pc.createAnswer(function(answer) { + util.log('Created answer.'); + + if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { + answer.sdp = Reliable.higherBandwidthSDP(answer.sdp); + } + + pc.setLocalDescription(answer, function() { + util.log('Set localDescription: answer', 'for:', connection.peer); + connection.provider.socket.send({ + type: 'ANSWER', + payload: { + sdp: answer, + type: connection.type, + connectionId: connection.id, + browser: util.browser + }, + dst: connection.peer + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setLocalDescription, ', err); + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to create answer, ', err); + }); +} + +/** Handle an SDP. */ +Negotiator.handleSDP = function(type, connection, sdp) { + sdp = new RTCSessionDescription(sdp); + var pc = connection.pc; + + util.log('Setting remote description', sdp); + pc.setRemoteDescription(sdp, function() { + util.log('Set remoteDescription:', type, 'for:', connection.peer); + + if (type === 'OFFER') { + Negotiator._makeAnswer(connection); + } + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setRemoteDescription, ', err); + }); +} + +/** Handle a candidate. */ +Negotiator.handleCandidate = function(connection, ice) { + var candidate = ice.candidate; + var sdpMLineIndex = ice.sdpMLineIndex; + connection.pc.addIceCandidate(new RTCIceCandidate({ + sdpMLineIndex: sdpMLineIndex, + candidate: candidate + })); + util.log('Added ICE candidate for:', connection.peer); +} +/** + * An abstraction on top of WebSockets and XHR streaming to provide fastest + * possible connection for peers. + */ +function Socket(secure, host, port, path, key) { + if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key); + + EventEmitter.call(this); + + // Disconnected manually. + this.disconnected = false; + this._queue = []; + + var httpProtocol = secure ? 'https://' : 'http://'; + var wsProtocol = secure ? 'wss://' : 'ws://'; + this._httpUrl = httpProtocol + host + ':' + port + path + key; + this._wsUrl = wsProtocol + host + ':' + port + path + 'peerjs?key=' + key; +} + +util.inherits(Socket, EventEmitter); + + +/** Check in with ID or get one from server. */ +Socket.prototype.start = function(id, token) { + this.id = id; + + this._httpUrl += '/' + id + '/' + token; + this._wsUrl += '&id='+id+'&token='+token; + + this._startXhrStream(); + this._startWebSocket(); +} + + +/** Start up websocket communications. */ +Socket.prototype._startWebSocket = function(id) { + var self = this; + + if (this._socket) { + return; + } + + this._socket = new WebSocket(this._wsUrl); + + this._socket.onmessage = function(event) { + var data; + try { + data = JSON.parse(event.data); + } catch(e) { + util.log('Invalid server message', event.data); + return; + } + self.emit('message', data); + }; + + // Take care of the queue of connections if necessary and make sure Peer knows + // socket is open. + this._socket.onopen = function() { + if (self._timeout) { + clearTimeout(self._timeout); + setTimeout(function(){ + self._http.abort(); + self._http = null; + }, 5000); + } + self._sendQueuedMessages(); + util.log('Socket open'); + }; +} + +/** Start XHR streaming. */ +Socket.prototype._startXhrStream = function(n) { + try { + var self = this; + this._http = new XMLHttpRequest(); + this._http._index = 1; + this._http._streamIndex = n || 0; + this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true); + this._http.onreadystatechange = function() { + if (this.readyState == 2 && this.old) { + this.old.abort(); + delete this.old; + } + if (this.readyState > 2 && this.status == 200 && this.responseText) { + self._handleStream(this); + } + }; + this._http.send(null); + this._setHTTPTimeout(); + } catch(e) { + util.log('XMLHttpRequest not available; defaulting to WebSockets'); + } +} + + +/** Handles onreadystatechange response as a stream. */ +Socket.prototype._handleStream = function(http) { + // 3 and 4 are loading/done state. All others are not relevant. + var messages = http.responseText.split('\n'); + + // Check to see if anything needs to be processed on buffer. + if (http._buffer) { + while (http._buffer.length > 0) { + var index = http._buffer.shift(); + var bufferedMessage = messages[index]; + try { + bufferedMessage = JSON.parse(bufferedMessage); + } catch(e) { + http._buffer.shift(index); + break; + } + this.emit('message', bufferedMessage); + } + } + + var message = messages[http._index]; + if (message) { + http._index += 1; + // Buffering--this message is incomplete and we'll get to it next time. + // This checks if the httpResponse ended in a `\n`, in which case the last + // element of messages should be the empty string. + if (http._index === messages.length) { + if (!http._buffer) { + http._buffer = []; + } + http._buffer.push(http._index - 1); + } else { + try { + message = JSON.parse(message); + } catch(e) { + util.log('Invalid server message', message); + return; + } + this.emit('message', message); + } + } +} + +Socket.prototype._setHTTPTimeout = function() { + var self = this; + this._timeout = setTimeout(function() { + var old = self._http; + if (!self._wsOpen()) { + self._startXhrStream(old._streamIndex + 1); + self._http.old = old; + } else { + old.abort(); + } + }, 25000); +} + +/** Is the websocket currently open? */ +Socket.prototype._wsOpen = function() { + return this._socket && this._socket.readyState == 1; +} + +/** Send queued messages. */ +Socket.prototype._sendQueuedMessages = function() { + for (var i = 0, ii = this._queue.length; i < ii; i += 1) { + this.send(this._queue[i]); + } +} + +/** Exposed send for DC & Peer. */ +Socket.prototype.send = function(data) { + if (this.disconnected) { + return; + } + + // If we didn't get an ID yet, we can't yet send anything so we should queue + // up these messages. + if (!this.id) { + this._queue.push(data); + return; + } + + if (!data.type) { + this.emit('error', 'Invalid message'); + return; + } + + var message = JSON.stringify(data); + if (this._wsOpen()) { + this._socket.send(message); + } else { + var http = new XMLHttpRequest(); + var url = this._httpUrl + '/' + data.type.toLowerCase(); + http.open('post', url, true); + http.setRequestHeader('Content-Type', 'application/json'); + http.send(message); + } +} + +Socket.prototype.close = function() { + if (!this.disconnected && this._wsOpen()) { + this._socket.close(); + this.disconnected = true; + } +} + +})(this); From 996f6f01509584fece35860a84c03736363e9762 Mon Sep 17 00:00:00 2001 From: Alan Stoll Date: Mon, 7 Jul 2014 15:53:26 -0400 Subject: [PATCH 40/44] we still need node integration, so fix moment loader another way --- index.html | 4 +++- js/shell.js | 2 +- shell/index.js | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index b4322cbcf..cc93e7ee3 100644 --- a/index.html +++ b/index.html @@ -919,6 +919,9 @@ on supported browsers please check http://www.w + + + @@ -939,7 +942,6 @@ on supported browsers please check http://www.w - diff --git a/js/shell.js b/js/shell.js index 6019b164b..9e3a89ff0 100644 --- a/js/shell.js +++ b/js/shell.js @@ -12,7 +12,7 @@ ** the renderer into thinking that we are _not_ in a CommonJS environment. */ if (typeof module !== 'undefined') module = { - exports: null + exports: false }; // are we running in copay shell? diff --git a/shell/index.js b/shell/index.js index f28a1ee73..899d053a2 100644 --- a/shell/index.js +++ b/shell/index.js @@ -24,8 +24,7 @@ module.exports = function(copay) { // create the main window mainWindow = new BrowserWindow({ width: config.window.width, - height: config.window.height, - "node-integration": "disable" + height: config.window.height }); // hide the empty window From 164d0c198a1b9b9eb0522d6df89c9f85b6328fb3 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 7 Jul 2014 16:57:57 -0300 Subject: [PATCH 41/44] workaround bug of urihandler in firefox --- js/services/uriHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/services/uriHandler.js b/js/services/uriHandler.js index ef50e1b5a..969ff1c0e 100644 --- a/js/services/uriHandler.js +++ b/js/services/uriHandler.js @@ -5,8 +5,7 @@ var UriHandler = function() {}; UriHandler.prototype.register = function() { var base = window.location.origin + '/'; var url = base + '#/uri_payment/%s'; - navigator.registerProtocolHandler('bitcoin', - url, 'Copay'); + // navigator.registerProtocolHandler('bitcoin', url, 'Copay'); }; angular.module('copayApp.services').value('uriHandler', new UriHandler()); From e180e53e73ce618d7c7d33cf8cbaddda68529572 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 7 Jul 2014 17:00:26 -0300 Subject: [PATCH 42/44] add SIN and video --- css/main.css | 5 +++ index.html | 69 ++++++++++++++++++++++++---------------- js/models/core/Wallet.js | 3 +- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/css/main.css b/css/main.css index 5147a20e4..b2ea53654 100644 --- a/css/main.css +++ b/css/main.css @@ -286,6 +286,11 @@ hr { margin: 2.25rem 0;} display: inline; float: right; } + +.setup .video-small { + float: none !important; +} + .online { background-color: black; border: 3px solid #1ABC9C; diff --git a/index.html b/index.html index b4322cbcf..f641caa40 100644 --- a/index.html +++ b/index.html @@ -82,31 +82,10 @@
-
-
-
- - Not all copayers have joined your wallet yet. - - {{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have - - - One person has - - yet to join. -
-
- - All copayers have joined the wallet, it's ready for use! -
-
-
-

Share this secret with your other copayers - for them to join your wallet

@@ -126,11 +105,34 @@
- +
People on this wallet
+ +
+ + + + you + + + {{c.nick}} + [SIN: {{c.peerId}}] + +
+ +
+ + Waiting for other copayers to join +
+
@@ -139,7 +141,20 @@
diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index d705b05d3..5ee65c49b 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -326,7 +326,8 @@ Wallet.prototype.getRegisteredPeerIds = function() { var pid = this.network.peerFromCopayer(cid); this.registeredPeerIds.push({ peerId: pid, - nick: this.publicKeyRing.nicknameForCopayer(cid) + nick: this.publicKeyRing.nicknameForCopayer(cid), + index: i, }); } } From 93bbc68f5fa9ea3b88b54d758bd60dc1534367c0 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 7 Jul 2014 17:30:42 -0300 Subject: [PATCH 43/44] fix save in Firefox --- js/controllers/settings.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/controllers/settings.js b/js/controllers/settings.js index afcd60262..be92f08ba 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -73,6 +73,11 @@ angular.module('copayApp.controllers').controller('SettingsController', unitToSatoshi: $scope.selectedUnit.value, })); - $window.location.href = $window.location.origin + $window.location.pathname; + var target = ($window.location.origin !== 'null' ? $window.location.origin : '') + $window.location.pathname; + console.log('[settings.js.76:target:]', target); //TODO + + + + $window.location.href = target; }; }); From 7e6827351f08aaf46a02c0ecf256744ca27bbb0e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 7 Jul 2014 18:06:30 -0300 Subject: [PATCH 44/44] rm log --- js/controllers/settings.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/js/controllers/settings.js b/js/controllers/settings.js index be92f08ba..5d1d21868 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -74,9 +74,6 @@ angular.module('copayApp.controllers').controller('SettingsController', })); var target = ($window.location.origin !== 'null' ? $window.location.origin : '') + $window.location.pathname; - console.log('[settings.js.76:target:]', target); //TODO - - $window.location.href = target; };