Fix Conflicts:

.gitignore
	index.html
	js/controllers/backup.js
	js/controllers/sidebar.js
This commit is contained in:
Gustavo Maximiliano Cortez 2014-07-28 12:28:07 -03:00
commit 85aa5842f1
74 changed files with 5237 additions and 200 deletions

View file

@ -33,6 +33,13 @@ var copayApp = window.copayApp = angular.module('copayApp', [
'copayApp.directives',
]);
copayApp.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'mailto:**'
]);
});
angular.module('copayApp.filters', []);
angular.module('copayApp.services', []);
angular.module('copayApp.controllers', []);

View file

@ -17,9 +17,9 @@ angular.module('copayApp.controllers').controller('ImportController',
updateStatus('Importing wallet - Setting things up...');
var w, errMsg;
// try to import encrypted wallet with passphrase
try {
w = walletFactory.import(encryptedObj, passphrase);
} catch (e) {
errMsg = e.message;
}
@ -31,12 +31,14 @@ angular.module('copayApp.controllers').controller('ImportController',
return;
}
// if wallet was never used, we're done
if (!w.isReady()) {
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
return;
}
// if it was used, we need to scan for indices
w.updateIndexes(function(err) {
updateStatus('Importing wallet - We are almost there...');
if (err) {

View file

@ -32,6 +32,7 @@ angular.module('copayApp.controllers').controller('SendController',
// Detect protocol
$scope.isHttp = ($window.location.protocol.indexOf('http') === 0);
$scope.isCordova = typeof(window.cordova) != 'undefined';
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
@ -72,8 +73,8 @@ angular.module('copayApp.controllers').controller('SendController',
});
// reset fields
$scope.address = $scope.amount = $scope.comment = null;
form.address.$pristine = form.amount.$pristine = form.comment.$pristine = true;
$scope.address = $scope.amount = $scope.commentText = null;
form.address.$pristine = form.amount.$pristine = true;
};
// QR code Scanner
@ -194,6 +195,25 @@ angular.module('copayApp.controllers').controller('SendController',
}, 500);
};
$scope.scannerIntent = function() {
cordova.plugins.barcodeScanner.scan(
function onSuccess(result) {
if (result.cancelled) return;
var bip21 = copay.Structure.parseBitcoinURI(result.text);
$scope.address = bip21.address;
if (bip21.amount) {
$scope.amount = bip21.amount * bitcore.util.COIN * satToUnit;
}
$rootScope.$digest();
},
function onError(error) {
alert('Scanning error');
});
}
$scope.toggleAddressBookEntry = function(key) {
var w = $rootScope.wallet;
w.toggleAddressBookEntry(key);

View file

@ -18,7 +18,7 @@ angular.module('copayApp.controllers').controller('SidebarController',
'icon': 'fi-arrow-right',
'link': 'send'
}, {
'title': 'More...',
'title': 'More',
'icon': 'fi-download',
'link': 'backup'
}];

View file

@ -156,63 +156,55 @@ angular.module('copayApp.directives')
restrict: 'EACM',
require: 'ngModel',
link: function(scope, element, attrs) {
var strength = {
messages: ['very weak', 'weak', 'weak', 'medium', 'strong'],
colors: ['#c0392b', '#e74c3c', '#d35400', '#f39c12', '#27ae60'],
mesureStrength: function(p) {
var force = 0;
var regex = /[$-/:-?{-~!"^_`\[\]]/g;
var lowerLetters = /[a-z]+/.test(p);
var upperLetters = /[A-Z]+/.test(p);
var numbers = /[0-9]+/.test(p);
var symbols = regex.test(p);
var flags = [lowerLetters, upperLetters, numbers, symbols];
var passedMatches = flags.filter(function(el) {
return !!el;
}).length;
force = 2 * p.length + (p.length >= 10 ? 1 : 0);
force += passedMatches * 10;
var MIN_LENGTH = 8;
var MESSAGES = ['Very Weak', 'Very Weak', 'Weak', 'Medium', 'Strong', 'Very Strong'];
var COLOR = ['#dd514c', '#dd514c', '#faa732', '#faa732', '#5eb95e', '#5eb95e'];
// penality (short password)
force = (p.length <= 6) ? Math.min(force, 10) : force;
// penality (poor variety of characters)
force = (passedMatches == 1) ? Math.min(force, 10) : force;
force = (passedMatches == 2) ? Math.min(force, 20) : force;
force = (passedMatches == 3) ? Math.min(force, 40) : force;
return force;
},
getColor: function(s) {
var idx = 0;
if (s <= 10) {
idx = 0;
} else if (s <= 20) {
idx = 1;
} else if (s <= 30) {
idx = 2;
} else if (s <= 40) {
idx = 3;
function evaluateMeter(password) {
var passwordStrength = 0;
var text;
if (password.length > 0) passwordStrength = 1;
if (password.length >= MIN_LENGTH) {
if ((password.match(/[a-z]/)) && (password.match(/[A-Z]/))) {
passwordStrength++;
} else {
idx = 4;
text = ', add mixed case';
}
return {
idx: idx + 1,
col: this.colors[idx],
message: this.messages[idx]
};
if (password.match(/\d+/)) {
passwordStrength++;
} else {
if (!text) text = ', add numerals';
}
if (password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/)) {
passwordStrength++;
} else {
if (!text) text = ', add punctuation';
}
if (password.length > 12) {
passwordStrength++;
} else {
if (!text) text = ', add characters';
}
} else {
text = ', that\'s short';
}
};
if (!text) text = '';
return {
strength: passwordStrength,
message: MESSAGES[passwordStrength] + text,
color: COLOR[passwordStrength]
}
}
scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue && newValue !== '') {
var c = strength.getColor(strength.mesureStrength(newValue));
var info = evaluateMeter(newValue);
element.css({
'border-color': c.col
'border-color': info.color
});
scope[attrs.checkStrength] = c.message;
scope[attrs.checkStrength] = info.message;
}
});
}

View file

@ -20,14 +20,18 @@ angular.module('copayApp.filters', [])
.filter('removeEmpty', function() {
return function(elements) {
elements = elements || [];
// Hide empty addresses from other copayers
// Hide empty change addresses from other copayers
return elements.filter(function(e) {
return e.owned || e.balance > 0;
return !e.isChange || e.balance > 0;
});
}
})
.filter('limitAddress', function() {
return function(elements, showAll) {
var elements = elements.sort(function(a, b) {
return (+b.owned) - (+a.owned);
});
if (elements.length <= 1 || showAll) {
return elements;
}

View file

@ -0,0 +1,26 @@
'use strict';
var bitcore = require('bitcore');
var Transaction = bitcore.Transaction;
function BuilderMockV0 (data) {
this.vanilla = data;
this.tx = new Transaction();
this.tx.parse(new Buffer(data.tx, 'hex'));
};
BuilderMockV0.prototype.build = function() {
return this.tx;
};
BuilderMockV0.prototype.getSelectedUnspent = function() {
return [];
};
BuilderMockV0.prototype.toObj = function() {
return this.vanilla;
};
module.exports = BuilderMockV0;

View file

@ -59,13 +59,16 @@ Structure.parseBitcoinURI = function(uri) {
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;
if (splitQuestion.length > 1) {
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;
};

View file

@ -5,9 +5,11 @@ var imports = require('soop').imports();
var bitcore = require('bitcore');
var util = bitcore.util;
var Transaction = bitcore.Transaction;
var Builder = bitcore.TransactionBuilder;
var BuilderMockV0 = require('./BuilderMockV0');;
var TransactionBuilder = bitcore.TransactionBuilder;
var Script = bitcore.Script;
var buffertools = bitcore.buffertools;
var preconditions = require('preconditions').instance();
function TxProposal(opts) {
this.creator = opts.creator;
@ -23,8 +25,7 @@ function TxProposal(opts) {
}
TxProposal.prototype.getID = function() {
var ntxid = this.builder.build().getNormalizedHash().toString('hex');
return ntxid;
return this.builder.build().getNormalizedHash().toString('hex');
};
TxProposal.prototype.toObj = function() {
@ -40,13 +41,41 @@ TxProposal.prototype.setSent = function(sentTxid) {
this.sentTs = Date.now();
};
TxProposal.fromObj = function(o) {
TxProposal.fromObj = function(o, forceOpts) {
var t = new TxProposal(o);
var b = new Builder.fromObj(o.builderObj);
t.builder = b;
try {
// force opts is requested.
for (var k in forceOpts) {
o.builderObj.opts[k] = forceOpts[k];
}
t.builder = TransactionBuilder.fromObj(o.builderObj);
} catch (e) {
if (!o.version) {
t.builder = new BuilderMockV0(o.builderObj);
t.readonly = 1;
};
}
return t;
};
TxProposal.prototype.isValid = function() {
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
return false;
}
var tx = this.builder.build();
for (var i = 0; i < tx.ins.length; i++) {
var hashType = tx.getHashType(i);
if (hashType && hashType !== Transaction.SIGHASH_ALL) {
return false;
}
}
return true;
};
TxProposal.getSentTs = function() {
return this.sentTs;
};
@ -127,6 +156,17 @@ TxProposal.prototype.mergeMetadata = function(v1, author) {
};
//This should be on bitcore / Transaction
TxProposal.prototype.countSignatures = function() {
var tx = this.builder.build();
var ret = 0;
for (var i in tx.ins) {
ret += tx.countInputSignatures(i);
}
return ret;
};
module.exports = require('soop')(TxProposal);
@ -138,15 +178,18 @@ function TxProposals(opts) {
this.txps = {};
}
TxProposals.fromObj = function(o) {
TxProposals.fromObj = function(o, forceOpts) {
var ret = new TxProposals({
networkName: o.networkName,
walletId: o.walletId,
});
o.txps.forEach(function(o2) {
var t = TxProposal.fromObj(o2);
var id = t.builder.build().getNormalizedHash().toString('hex');
ret.txps[id] = t;
var t = TxProposal.fromObj(o2, forceOpts);
if (t.builder) {
var id = t.getID();
ret.txps[id] = t;
}
});
return ret;
};
@ -198,7 +241,6 @@ TxProposals.prototype.merge = function(inTxp, author) {
return ret;
};
var preconditions = require('preconditions').instance();
TxProposals.prototype.add = function(data) {
preconditions.checkArgument(data.inputChainPaths);
preconditions.checkArgument(data.signedBy);

View file

@ -35,8 +35,8 @@ function Wallet(opts) {
self[k] = opts[k];
});
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
throw new Error('Network forced to '+copayConfig.networkName+
' and tried to create a Wallet with network '+ this.getNetworkName());
throw new Error('Network forced to ' + copayConfig.networkName +
' and tried to create a Wallet with network ' + this.getNetworkName());
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
@ -57,6 +57,14 @@ function Wallet(opts) {
this.network.setHexNonces(opts.networkNonces);
}
Wallet.builderOpts = {
lockTime: null,
signhash: bitcore.Transaction.SIGNHASH_ALL,
fee: null,
feeSat: null,
};
Wallet.parent = EventEmitter;
Wallet.prototype.log = function() {
if (!this.verbose) return;
@ -121,11 +129,20 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
};
Wallet.prototype._handleTxProposal = function(senderId, data) {
preconditions.checkArgument(senderId);
this.log('RECV TXPROPOSAL:', data);
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal);
Wallet.prototype._handleTxProposal = function(senderId, data) {
this.log('RECV TXPROPOSAL: ', data);
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts);
var valid = inTxp.isValid();
if (!valid) {
var corruptEvent = {
type: 'corrupt',
cId: inTxp.creator
};
this.emit('txProposalEvent', corruptEvent);
return;
}
var mergeInfo = this.txProposals.merge(inTxp, senderId);
var added = this.addSeenToTxProposals();
@ -370,7 +387,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
opts.addressBook = o.addressBook;
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
opts.txProposals = TxProposals.fromObj(o.txProposals);
opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts);
opts.privateKey = PrivateKey.fromObj(o.privateKey);
opts.storage = storage;
@ -489,7 +506,9 @@ Wallet.prototype.getTxProposals = function() {
txp.finallyRejected = true;
}
ret.push(txp);
if (txp.readonly && !txp.finallyRejected && !txp.sentTs) {} else {
ret.push(txp);
}
}
return ret;
};
@ -509,6 +528,7 @@ Wallet.prototype.reject = function(ntxid) {
};
Wallet.prototype.sign = function(ntxid, cb) {
preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined');
var self = this;
@ -522,11 +542,11 @@ Wallet.prototype.sign = function(ntxid, cb) {
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
var b = txp.builder;
var before = b.signaturesAdded;
var before = txp.countSignatures();
b.sign(keys);
var ret = false;
if (b.signaturesAdded > before) {
if (txp.countSignatures() > before) {
txp.signedBy[myId] = Date.now();
self.sendTxProposal(ntxid);
self.store();
@ -697,15 +717,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
var priv = this.privateKey;
opts = opts || {};
var amountSat = bignum(amountSatStr);
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
if (!pkr.isComplete()) {
throw new Error('publicKeyRing is not complete');
}
if (comment && comment.length > 100) {
throw new Error("comment can't be longer that 100 characters");
}
preconditions.checkState(pkr.isComplete());
if (comment) preconditions.checkArgument(comment.length <= 100);
if (!opts.remainderOut) {
opts.remainderOut = {
@ -713,11 +727,15 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
};
}
for (var k in Wallet.builderOpts){
opts[k] = Wallet.builderOpts[k];
}
var b = new Builder(opts)
.setUnspent(utxos)
.setOutputs([{
address: toAddress,
amountSat: amountSat
amountSatStr: amountSatStr,
}]);
var selectedUtxos = b.getSelectedUnspent();
@ -735,7 +753,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
var now = Date.now();
var me = {};
if (priv && b.signaturesAdded) me[myId] = now;
var tx = b.build();
if (priv && tx.countInputSignatures(0)) me[myId] = now;
var meSeen = {};
if (priv) meSeen[myId] = now;

View file

@ -28,6 +28,18 @@ BackupService.prototype.download = function(wallet) {
wallet: ew
});
}
// throw an email intent if we are in the mobile version
if (window.cordova) {
var name = wallet.name ? wallet.name + ' ' : '';
var partial = partial ? 'Partial ' : '';
return window.plugin.email.open({
subject: 'Copay - ' + name + 'Wallet ' + partial + 'Backup',
body: 'Here is the encrypted backup of the wallet ' + wallet.id,
attachments: ['base64:' + filename + '//' + btoa(ew)]
});
}
// otherwise lean on the browser implementation
saveAs(blob, filename);
};

View file

@ -149,15 +149,17 @@ angular.module('copayApp.services')
}, 3000);
});
w.on('txProposalEvent', function(e) {
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
switch (e.type) {
case 'signed':
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
notification.info('Transaction Update', 'A transaction was signed by ' + user);
break;
case 'rejected':
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
notification.info('Transaction Update', 'A transaction was rejected by ' + user);
break;
case 'corrupt':
notification.error('Transaction Error', 'Received corrupt transaction from '+user);
break;
}
});
w.on('addressBookUpdated', function(dontDigest) {

View file

@ -198,7 +198,12 @@ factory('notification', ['$timeout',
$timeout(function removeFromQueueTimeout() {
queue.splice(queue.indexOf(notification), 1);
}, settings[type].duration);
}
// Movile notification
window.navigator.vibrate([200,100,200]);
if (document.hidden && (type == 'info' || type == 'funds')) {
new window.Notification(title, {body: content, icon:'img/notification.png'});
}
this.save();