Merge branch 'master' into bugs/ui

This commit is contained in:
bechi 2014-09-03 10:39:09 -03:00
commit 2d33dcfef6
40 changed files with 520 additions and 3158 deletions

View File

@ -83,7 +83,6 @@ module.exports = function(grunt) {
'js/shell.js', // shell must be loaded before moment due to the way moment loads in a commonjs env
'lib/moment/min/moment.min.js',
'lib/qrcode-generator/js/qrcode.js',
'lib/peer.js',
'lib/bitcore.js',
'lib/crypto-js/rollups/sha256.js',
'lib/crypto-js/rollups/pbkdf2.js',

View File

@ -10,7 +10,6 @@
"angular-foundation": "*",
"angular-route": "~1.2.14",
"angular-qrcode": "~3.1.0",
"peerjs": "=0.3.8",
"angular-mocks": "~1.2.14",
"mocha": "~1.18.2",
"chai": "~1.9.1",

View File

@ -3,10 +3,13 @@ var defaultConfig = {
// DEFAULT network (livenet or testnet)
networkName: 'testnet',
forceNetwork: false,
logLevel: 'info',
// DEFAULT unit: Bit
unitName: 'bits',
unitToSatoshi: 100,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
// wallet limits
limits: {
@ -54,7 +57,11 @@ var defaultConfig = {
storageSalt: 'mjuBtGybi/4=',
},
disableVideo: true,
rate: {
url: 'https://bitpay.com/api/rates',
updateFrequencySeconds: 60 * 60
},
verbose: 1,
};
if (typeof module !== 'undefined')

View File

@ -205,23 +205,6 @@ a:hover {
color: #fff;
}
.sidebar ul.copayer-list {
list-style-type: none;
padding:0; margin:0;
}
.sidebar ul.copayer-list li {
margin-top: 15px;
font-weight: 100;
font-size: 12px;
color: #C9C9C9;
}
.sidebar ul.copayer-list img {
width: 30px;
height: 30px;
}
.button.small.side-bar {
padding: 0rem 0.4rem;
}
@ -954,7 +937,15 @@ button, .button, p {
cursor: pointer;
}
.video-box {
.copay-box-small {
width: 40px;
text-align: center;
margin-right: 10px;
padding-bottom: 5px;
float: left;
}
.copay-box {
width: 70px;
text-align: center;
margin-right: 20px;
@ -962,11 +953,6 @@ button, .button, p {
float: left;
}
.video-small {
width: 50px;
height: 50px;
}
.icon-input {
position: absolute;
top: 11px;

View File

@ -18,11 +18,6 @@ if (localConfig) {
}
}
var log = function() {
if (config.verbose) console.log(arguments);
}
var copayApp = window.copayApp = angular.module('copayApp', [
'ngRoute',
'angularMoment',

View File

@ -26,4 +26,15 @@ angular.module('copayApp.controllers').controller('CopayersController',
});
};
// Cached list of copayers
$scope.copayers = $rootScope.wallet.getRegisteredPeerIds();
$scope.copayersList = function() {
return $rootScope.wallet.getRegisteredPeerIds();
}
$scope.isBackupReady = function(copayer) {
return $rootScope.wallet.publicKeyRing.isBackupReady(copayer.copayerId);
}
});

View File

@ -32,12 +32,11 @@ var valid_pairs = {
'1,12': 489
};
angular.module('copayApp.controllers').controller('SetupController',
angular.module('copayApp.controllers').controller('CreateController',
function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase, backupService, notification) {
controllerUtils.redirIfLogged();
$rootScope.fromSetup = true;
$rootScope.videoInfo = {};
$scope.loading = false;
$scope.walletPassword = $rootScope.walletPassword;
$scope.isMobile = !!window.cordova;

View File

@ -2,14 +2,67 @@
var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('SendController',
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils) {
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
$scope.title = 'Send';
$scope.loading = false;
var satToUnit = 1 / config.unitToSatoshi;
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
$scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN;
$scope.unitToSatoshi = config.unitToSatoshi;
$scope.minAmount = config.limits.minAmountSatoshi * satToUnit;
$scope.alternativeName = config.alternativeName;
$scope.alternativeIsoCode = config.alternativeIsoCode;
$scope.isRateAvailable = false;
$scope.rateService = rateService;
rateService.whenAvailable(function() {
$scope.isRateAvailable = true;
$scope.$digest();
});
/**
* Setting the two related amounts as properties prevents an infinite
* recursion for watches while preserving the original angular updates
*/
Object.defineProperty($scope,
"alternative", {
get: function () {
return this._alternative;
},
set: function (newValue) {
this._alternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._amount = Number.parseFloat(
(rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit
).toFixed(config.unitDecimals), 10);
} else {
this._amount = 0;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope,
"amount", {
get: function () {
return this._amount;
},
set: function (newValue) {
this._amount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._alternative = Number.parseFloat(
(rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode)
).toFixed(2), 10);
} else {
this._alternative = 0;
}
},
enumerable: true,
configurable: true
});
$scope.loadTxs = function() {
var opts = {
pending: true,

View File

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils) {
angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils, rateService) {
controllerUtils.redirIfLogged();
$scope.title = 'Settings';
@ -8,27 +8,46 @@ angular.module('copayApp.controllers').controller('SettingsController', function
$scope.insightHost = config.blockchain.host;
$scope.insightPort = config.blockchain.port;
$scope.insightSecure = config.blockchain.schema === 'https';
$scope.disableVideo = typeof config.disableVideo === undefined ? true : config.disableVideo;
$scope.forceNetwork = config.forceNetwork;
$scope.unitOpts = [{
name: 'Satoshis (100,000,000 satoshis = 1BTC)',
shortName: 'SAT',
value: 1
value: 1,
decimals: 0
}, {
name: 'bits (1,000,000 bits = 1BTC)',
shortName: 'bits',
value: 100
value: 100,
decimals: 2
}, {
name: 'mBTC (1,000 mBTC = 1BTC)',
shortName: 'mBTC',
value: 100000
value: 100000,
decimals: 5
}, {
name: 'BTC',
shortName: 'BTC',
value: 100000000
value: 100000000,
decimals: 8
}];
$scope.selectedAlternative = {
name: config.alternativeName,
isoCode: config.alternativeIsoCode
};
$scope.alternativeOpts = rateService.isAvailable ?
rateService.listAlternatives() : [$scope.selectedAlternative];
rateService.whenAvailable(function() {
$scope.alternativeOpts = rateService.listAlternatives();
for (var ii in $scope.alternativeOpts) {
if (config.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) {
$scope.selectedAlternative = $scope.alternativeOpts[ii];
}
}
});
for (var ii in $scope.unitOpts) {
if (config.unitName === $scope.unitOpts[ii].shortName) {
$scope.selectedUnit = $scope.unitOpts[ii];
@ -65,10 +84,13 @@ angular.module('copayApp.controllers').controller('SettingsController', function
schema: $scope.insightSecure ? 'https' : 'http',
},
network: network,
disableVideo: $scope.disableVideo,
unitName: $scope.selectedUnit.shortName,
unitToSatoshi: $scope.selectedUnit.value,
version: copay.version,
unitDecimals: $scope.selectedUnit.decimals,
alternativeName: $scope.selectedAlternative.name,
alternativeIsoCode: $scope.selectedAlternative.isoCode,
version: copay.version
}));
// Go home reloading the application

View File

@ -4,7 +4,7 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
$scope.menu = [{
'title': 'Receive',
'icon': 'fi-arrow-left',
'icon': 'fi-download',
'link': 'receive'
}, {
'title': 'Send',
@ -15,8 +15,8 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
'icon': 'fi-clipboard-pencil',
'link': 'history'
}, {
'title': 'More',
'icon': 'fi-download',
'title': 'Settings',
'icon': 'fi-widget',
'link': 'more'
}];

View File

@ -1,46 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('VideoController',
function($scope, $rootScope, $sce) {
$rootScope.videoInfo = {};
// Cached list of copayers
$scope.copayers = $rootScope.wallet.getRegisteredPeerIds();
$scope.copayersList = function() {
return $rootScope.wallet.getRegisteredPeerIds();
}
$scope.hasVideo = function(copayer) {
return $rootScope.videoInfo[copayer.peerId];
}
$scope.isConnected = function(copayer) {
return $rootScope.wallet.getOnlinePeerIDs().indexOf(copayer.peerId) != -1;
}
$scope.isBackupReady = function(copayer) {
return $rootScope.wallet.publicKeyRing.isBackupReady(copayer.copayerId);
}
$scope.getVideoURL = function(copayer) {
if (config.disableVideo) return;
var vi = $scope.videoInfo[copayer.peerId];
if (!vi) return;
if ($scope.isConnected(copayer)) {
// peer disconnected, remove his video
delete $rootScope.videoInfo[copayer.peerId];
return;
}
var encoded = vi.url;
var url = decodeURI(encoded);
var trusted = $sce.trustAsResourceUrl(url);
return trusted;
};
});

View File

@ -45,9 +45,10 @@ angular.module('copayApp.directives')
link: function(scope, element, attrs, ctrl) {
var val = function(value) {
var availableBalanceNum = Number(($rootScope.availableBalance * config.unitToSatoshi).toFixed(0));
var vNum = Number((value * config.unitToSatoshi).toFixed(0)) + feeSat;
var vNum = Number((value * config.unitToSatoshi).toFixed(0));
if (typeof vNum == "number" && vNum > 0) {
vNum = vNum + feeSat;
if (availableBalanceNum < vNum || isNaN(availableBalanceNum)) {
ctrl.$setValidity('enoughAmount', false);
scope.notEnoughAmount = true;
@ -108,20 +109,6 @@ angular.module('copayApp.directives')
}
}
})
.directive('avatar', function($rootScope, controllerUtils) {
return {
link: function(scope, element, attrs) {
var peer = JSON.parse(attrs.peer)
var peerId = peer.peerId;
var nick = peer.nick;
element.addClass('video-small');
var muted = controllerUtils.getVideoMutedStatus(peerId);
if (true || muted) { // mute everyone for now
element.attr("muted", true);
}
}
}
})
.directive('contact', function() {
return {
restrict: 'E',

43
js/log.js Normal file
View File

@ -0,0 +1,43 @@
var config = require('../config');
var Logger = function(name) {
this.name = name || 'log';
this.level = 2;
};
var levels = {
'debug': 0,
'info': 1,
'log': 2,
'warn': 3,
'error': 4,
'fatal': 5
};
Object.keys(levels).forEach(function(level) {
Logger.prototype[level] = function() {
if (levels[level] >= levels[this.level]) {
var str = '[' + level + '] ' + this.name + ': ' + arguments[0],
extraArgs,
extraArgs = [].slice.call(arguments, 1);
if (console[level]) {
extraArgs.unshift(str);
console[level].apply(console, extraArgs);
} else {
if (extraArgs.length) {
str += JSON.stringify(extraArgs);
}
console.log(str);
}
}
};
});
Logger.prototype.setLevel = function(level) {
this.level = level;
}
var logger = new Logger('copay');
logger.setLevel(config.logLevel);
module.exports = logger;

View File

@ -15,14 +15,12 @@ Passphrase.prototype.get = function(password) {
keySize: 512 / 32,
iterations: this.iterations
});
return key512;
};
Passphrase.prototype.getBase64 = function(password) {
var key512 = this.get(password);
var keyBase64 = key512.toString(CryptoJS.enc.Base64);
return keyBase64;
};

View File

@ -15,6 +15,7 @@ var Base58Check = bitcore.Base58.base58Check;
var Address = bitcore.Address;
var PayPro = bitcore.PayPro;
var Transaction = bitcore.Transaction;
var log = require('../../log');
var HDParams = require('./HDParams');
var PublicKeyRing = require('./PublicKeyRing');
@ -72,12 +73,6 @@ Wallet.builderOpts = {
feeSat: null,
};
Wallet.prototype.log = function() {
if (!this.verbose) return;
if (console)
console.log.apply(console, arguments);
};
Wallet.getRandomId = function() {
var r = bitcore.SecureRandom.getPseudoRandomBuffer(8).toString('hex');
return r;
@ -88,7 +83,7 @@ Wallet.prototype.seedCopayer = function(pubKey) {
};
Wallet.prototype._onIndexes = function(senderId, data) {
this.log('RECV INDEXES:', data);
log.debug('RECV INDEXES:', data);
var inIndexes = HDParams.fromList(data.indexes);
var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
if (hasChanged) {
@ -98,7 +93,7 @@ Wallet.prototype._onIndexes = function(senderId, data) {
};
Wallet.prototype._onPublicKeyRing = function(senderId, data) {
this.log('RECV PUBLICKEYRING:', data);
log.debug('RECV PUBLICKEYRING:', data);
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
var wasIncomplete = !this.publicKeyRing.isComplete();
@ -107,7 +102,7 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) {
try {
hasChanged = this.publicKeyRing.merge(inPKR, true);
} catch (e) {
this.log('## WALLET ERROR', e);
log.debug('## WALLET ERROR', e);
this.emit('connectionError', e.message);
return;
}
@ -205,7 +200,7 @@ Wallet.prototype._checkSentTx = function(ntxid, cb) {
Wallet.prototype._onTxProposal = function(senderId, data) {
var self = this;
this.log('RECV TXPROPOSAL: ', data);
log.debug('RECV TXPROPOSAL: ', data);
var m;
try {
@ -214,7 +209,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
ret.newCopayer = m.txp.setCopayers(senderId, keyMap);
} catch (e) {
this.log('Corrupt TX proposal received from:', senderId, e);
log.debug('Corrupt TX proposal received from:', senderId, e);
}
if (m) {
@ -242,7 +237,7 @@ Wallet.prototype._onTxProposal = function(senderId, data) {
Wallet.prototype._onReject = function(senderId, data) {
preconditions.checkState(data.ntxid);
this.log('RECV REJECT:', data);
log.debug('RECV REJECT:', data);
var txp = this.txProposals.get(data.ntxid);
@ -265,7 +260,7 @@ Wallet.prototype._onReject = function(senderId, data) {
Wallet.prototype._onSeen = function(senderId, data) {
preconditions.checkState(data.ntxid);
this.log('RECV SEEN:', data);
log.debug('RECV SEEN:', data);
var txp = this.txProposals.get(data.ntxid);
txp.setSeen(senderId);
@ -283,7 +278,7 @@ Wallet.prototype._onSeen = function(senderId, data) {
Wallet.prototype._onAddressBook = function(senderId, data) {
preconditions.checkState(data.addressBook);
this.log('RECV ADDRESSBOOK:', data);
log.debug('RECV ADDRESSBOOK:', data);
var rcv = data.addressBook;
var hasChange;
for (var key in rcv) {
@ -311,7 +306,7 @@ Wallet.prototype.updateTimestamp = function(ts) {
Wallet.prototype._onNoMessages = function() {
console.log('No messages at the server. Requesting sync'); //TODO
log.debug('No messages at the server. Requesting sync'); //TODO
this.sendWalletReady();
};
@ -322,7 +317,7 @@ Wallet.prototype._onData = function(senderId, data, ts) {
preconditions.checkArgument(ts);
preconditions.checkArgument(typeof ts === 'number');
console.log('RECV', senderId, data);
log.debug('RECV', senderId, data);
if (data.type !== 'walletId' && this.id !== data.walletId) {
this.emit('corrupt', senderId);
@ -375,7 +370,7 @@ Wallet.prototype._onData = function(senderId, data, ts) {
Wallet.prototype._onConnect = function(newCopayerId) {
if (newCopayerId) {
this.log('#### Setting new COPAYER:', newCopayerId);
log.debug('#### Setting new COPAYER:', newCopayerId);
this.sendWalletId(newCopayerId);
}
var peerID = this.network.peerFromCopayer(newCopayerId)
@ -460,28 +455,12 @@ Wallet.prototype.netStart = function(callback) {
self.emit('ready', net.getPeer());
setTimeout(function() {
self.emit('publicKeyRingUpdated', true);
//self.scheduleConnect();
// no connection logic for now
self.emit('txProposalsUpdated');
}, 10);
});
};
// not being used now
Wallet.prototype.scheduleConnect = function() {
var self = this;
if (self.network.isOnline()) {
self.connectToAll();
self.currentDelay = self.currentDelay * 2 || self.reconnectDelay;
setTimeout(self.scheduleConnect.bind(self), self.currentDelay);
}
}
Wallet.prototype.getOnlinePeerIDs = function() {
return this.network.getOnlinePeerIDs();
};
Wallet.prototype.getRegisteredCopayerIds = function() {
var l = this.publicKeyRing.registeredCopayers();
var copayers = [];
@ -515,7 +494,7 @@ Wallet.prototype.keepAlive = function() {
try {
this.lock.keepAlive();
} catch (e) {
this.log(e);
log.debug(e);
this.emit('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.');
}
};
@ -525,7 +504,7 @@ Wallet.prototype.store = function() {
var wallet = this.toObj();
this.storage.setFromObj(this.id, wallet);
this.log('Wallet stored');
log.debug('Wallet stored');
};
Wallet.prototype.toObj = function() {
@ -613,7 +592,7 @@ Wallet.prototype.sendAllTxProposals = function(recipients) {
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
preconditions.checkArgument(ntxid);
this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
log.debug('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals);
this.send(recipients, {
type: 'txProposal',
txProposal: this.txProposals.get(ntxid).toObjTrim(),
@ -623,7 +602,7 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
Wallet.prototype.sendSeen = function(ntxid) {
preconditions.checkArgument(ntxid);
this.log('### SENDING seen: ' + ntxid + ' TO: All');
log.debug('### SENDING seen: ' + ntxid + ' TO: All');
this.send(null, {
type: 'seen',
ntxid: ntxid,
@ -633,7 +612,7 @@ Wallet.prototype.sendSeen = function(ntxid) {
Wallet.prototype.sendReject = function(ntxid) {
preconditions.checkArgument(ntxid);
this.log('### SENDING reject: ' + ntxid + ' TO: All');
log.debug('### SENDING reject: ' + ntxid + ' TO: All');
this.send(null, {
type: 'reject',
ntxid: ntxid,
@ -643,7 +622,7 @@ Wallet.prototype.sendReject = function(ntxid) {
Wallet.prototype.sendWalletReady = function(recipients) {
this.log('### SENDING WalletReady TO:', recipients || 'All');
log.debug('### SENDING WalletReady TO:', recipients || 'All');
this.send(recipients, {
type: 'walletReady',
@ -652,7 +631,7 @@ Wallet.prototype.sendWalletReady = function(recipients) {
};
Wallet.prototype.sendWalletId = function(recipients) {
this.log('### SENDING walletId TO:', recipients || 'All', this.id);
log.debug('### SENDING walletId TO:', recipients || 'All', this.id);
this.send(recipients, {
type: 'walletId',
@ -664,7 +643,7 @@ Wallet.prototype.sendWalletId = function(recipients) {
Wallet.prototype.sendPublicKeyRing = function(recipients) {
this.log('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
log.debug('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
var publicKeyRing = this.publicKeyRing.toObj();
this.send(recipients, {
@ -675,7 +654,7 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
};
Wallet.prototype.sendIndexes = function(recipients) {
var indexes = HDParams.serialize(this.publicKeyRing.indexes);
this.log('### INDEXES TO:', recipients || 'All', indexes);
log.debug('### INDEXES TO:', recipients || 'All', indexes);
this.send(recipients, {
type: 'indexes',
@ -685,7 +664,7 @@ Wallet.prototype.sendIndexes = function(recipients) {
};
Wallet.prototype.sendAddressBook = function(recipients) {
this.log('### SENDING addressBook TO:', recipients || 'All', this.addressBook);
log.debug('### SENDING addressBook TO:', recipients || 'All', this.addressBook);
this.send(recipients, {
type: 'addressbook',
addressBook: this.addressBook,
@ -796,23 +775,23 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
var tx = txp.builder.build();
if (!tx.isComplete())
throw new Error('Tx is not complete. Can not broadcast');
this.log('Broadcasting Transaction');
log.debug('Broadcasting Transaction');
var scriptSig = tx.ins[0].getScript();
var size = scriptSig.serialize().length;
var txHex = tx.serialize().toString('hex');
this.log('Raw transaction: ', txHex);
log.debug('Raw transaction: ', txHex);
var self = this;
this.blockchain.broadcast(txHex, function(err, txid) {
self.log('BITCOIND txid:', txid);
log.debug('BITCOIND txid:', txid);
if (txid) {
self.txProposals.get(ntxid).setSent(txid);
self.sendTxProposal(ntxid);
self.store();
return cb(txid);
} else {
self.log('Sent failed. Checking if the TX was sent already');
log.debug('Sent failed. Checking if the TX was sent already');
self._checkSentTx(ntxid, function(txid) {
if (txid)
self.store();
@ -1003,10 +982,8 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
self.emit('txProposalsUpdated');
}
self.log('You are currently on this BTC network:');
self.log(network);
self.log('The server sent you a message:');
self.log(memo);
log.debug('You are currently on this BTC network:', network);
log.debug('The server sent you a message:', memo);
return cb(ntxid, merchantData);
});
@ -1025,7 +1002,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
var tx = txp.builder.build();
if (!tx.isComplete()) return;
this.log('Sending Transaction');
log.debug('Sending Transaction');
var refund_outputs = [];
@ -1085,8 +1062,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
pay = pay.serialize();
this.log('Sending Payment Message:');
this.log(pay.toString('hex'));
log.debug('Sending Payment Message:', pay.toString('hex'));
var buf = new ArrayBuffer(pay.length);
var view = new Uint8Array(buf);
@ -1127,8 +1103,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
var payment = ack.get('payment');
var memo = ack.get('memo');
this.log('Our payment was acknowledged!');
this.log('Message from Merchant: %s', memo);
log.debug('Our payment was acknowledged!');
log.debug('Message from Merchant: %s', memo);
payment = PayPro.Payment.decode(payment);
var pay = new PayPro();
@ -1141,7 +1117,7 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
var tx = payment.message.transactions[0];
if (!tx) {
this.log('Sending to server was not met with a returned tx.');
log.debug('Sending to server was not met with a returned tx.');
return this._checkSentTx(ntxid, function(txid) {
self.log('[Wallet.js.1048:txid:%s]', txid);
if (txid) self.store();
@ -1159,8 +1135,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) {
var txid = tx.getHash().toString('hex');
var txHex = tx.serialize().toString('hex');
this.log('Raw transaction: ', txHex);
this.log('BITCOIND txid:', txid);
log.debug('Raw transaction: ', txHex);
log.debug('BITCOIND txid:', txid);
this.txProposals.get(ntxid).setSent(txid);
this.sendTxProposal(ntxid);
this.store();
@ -1260,10 +1236,7 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
if (options.fetch) return;
this.log('');
this.log('Created transaction:');
this.log(b.tx.getStandardizedObject());
this.log('');
log.debug('Created transaction: %s', b.tx.getStandardizedObject());
var myId = this.getMyCopayerId();
var now = Date.now();
@ -1661,7 +1634,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
self.log('Updating indexes...');
log.debug('Updating indexes...');
var tasks = this.publicKeyRing.indexes.map(function(index) {
return function(callback) {
@ -1671,7 +1644,7 @@ Wallet.prototype.updateIndexes = function(callback) {
async.parallel(tasks, function(err) {
if (err) callback(err);
self.log('Indexes updated');
log.debug('Indexes updated');
self.emit('publicKeyRingUpdated');
self.store();
callback();
@ -1747,7 +1720,7 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
Wallet.prototype.close = function() {
this.log('## CLOSING');
log.debug('## CLOSING');
this.lock.release();
this.network.cleanUp();
this.blockchain.destroy();

View File

@ -4,6 +4,9 @@ var TxProposals = require('./TxProposals');
var PublicKeyRing = require('./PublicKeyRing');
var PrivateKey = require('./PrivateKey');
var Wallet = require('./Wallet');
var preconditions = require('preconditions').instance();
var log = require('../../log');
var Async = module.exports.Async = require('../network/Async');
var Insight = module.exports.Insight = require('../blockchain/Insight');
@ -11,7 +14,6 @@ var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../s
/*
* WalletFactory
*
*/
function WalletFactory(config, version) {
@ -27,19 +29,10 @@ function WalletFactory(config, version) {
this.blockchain = new this.Blockchain(config.blockchain);
this.networkName = config.networkName;
this.verbose = config.verbose;
this.walletDefaults = config.wallet;
this.version = version;
}
WalletFactory.prototype.log = function() {
if (!this.verbose) return;
if (console) {
console.log.apply(console, arguments);
}
};
WalletFactory.prototype._checkRead = function(walletId) {
var s = this.storage;
var ret =
@ -112,7 +105,7 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
WalletFactory.prototype.create = function(opts) {
opts = opts || {};
this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
var privOpts = {
networkName: this.networkName,
@ -137,12 +130,12 @@ WalletFactory.prototype.create = function(opts) {
opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(),
opts.nickname
);
this.log('\t### PublicKeyRing Initialized');
log.debug('\t### PublicKeyRing Initialized');
opts.txProposals = opts.txProposals || new TxProposals({
networkName: this.networkName,
});
this.log('\t### TxProposals Initialized');
log.debug('\t### TxProposals Initialized');
this.storage._setPassphrase(opts.passphrase);
@ -236,7 +229,7 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
//Create our PrivateK
var privateKey = new PrivateKey(privOpts);
this.log('\t### PrivateKey Initialized');
log.debug('\t### PrivateKey Initialized');
var opts = {
copayerId: privateKey.getId(),
privkey: privateKey.getIdPriv(),

View File

@ -2,6 +2,7 @@
var EventEmitter = require('events').EventEmitter;
var bitcore = require('bitcore');
var log = require('../../log');
var AuthMessage = bitcore.AuthMessage;
var util = bitcore.util;
var nodeUtil = require('util');
@ -187,7 +188,7 @@ Network.prototype._onMessage = function(enc) {
return;
}
//console.log('receiving ' + JSON.stringify(payload));
log.debug('receiving ' + JSON.stringify(payload));
var self = this;
switch (payload.type) {
@ -234,8 +235,8 @@ Network.prototype._setupConnectionHandlers = function(cb) {
};
Network.prototype._onError = function(err) {
console.log('RECV ERROR: ', err);
console.log(err.stack);
log.debug('RECV ERROR: ', err);
log.debug(err.stack);
this.criticalError = err.message;
};
@ -348,7 +349,7 @@ Network.prototype.send = function(dest, payload, cb) {
var to = dest[ii];
if (to == this.copayerId)
continue;
//console.log('SEND to: ' + to, this.copayerId, payload);
log.debug('SEND to: ' + to, this.copayerId, payload);
var message = this.encode(to, payload);
this.socket.emit('message', message);
}

View File

@ -2,6 +2,7 @@
var CryptoJS = require('node-cryptojs-aes').CryptoJS;
var bitcore = require('bitcore');
var preconditions = require('preconditions').instance();
var id = 0;
function Storage(opts) {
@ -11,15 +12,12 @@ function Storage(opts) {
if (opts.password)
this._setPassphrase(opts.password);
try{
try {
this.localStorage = opts.localStorage || localStorage;
this.sessionStorage = opts.sessionStorage || sessionStorage;
} catch (e) {};
if (!this.localStorage)
throw new Error('no localStorage');
if (!this.sessionStorage)
throw new Error('no sessionStorage');
} catch (e) {}
preconditions.checkState(this.localStorage, 'No localstorage found');
preconditions.checkState(this.sessionStorage, 'No sessionStorage found');
}
var pps = {};
@ -40,11 +38,6 @@ Storage.prototype._encrypt = function(string) {
return encryptedBase64;
};
Storage.prototype._encryptObj = function(obj) {
var string = JSON.stringify(obj);
return this._encrypt(string);
};
Storage.prototype._decrypt = function(base64) {
var decryptedStr = null;
try {
@ -58,10 +51,6 @@ Storage.prototype._decrypt = function(base64) {
return decryptedStr;
};
Storage.prototype._decryptObj = function(base64) {
var decryptedStr = this._decrypt(base64);
return JSON.parse(decryptedStr);
};
Storage.prototype._read = function(k) {
var ret;
@ -98,7 +87,7 @@ Storage.prototype.removeGlobal = function(k) {
};
Storage.prototype.getSessionId = function() {
var sessionId = this.sessionStorage.getItem('sessionId');
var sessionId = this.sessionStorage.getItem('sessionId');
if (!sessionId) {
sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex');
this.sessionStorage.setItem('sessionId', sessionId);
@ -131,7 +120,6 @@ Storage.prototype.setName = function(walletId, name) {
Storage.prototype.getName = function(walletId) {
var ret = this.getGlobal('nameFor::' + walletId);
return ret;
};
@ -145,7 +133,7 @@ Storage.prototype.getWalletIds = function() {
if (split.length == 2) {
var walletId = split[0];
if (!walletId || walletId === 'nameFor' || walletId ==='lock')
if (!walletId || walletId === 'nameFor' || walletId === 'lock')
continue;
if (typeof uniq[walletId] === 'undefined') {
@ -207,14 +195,14 @@ Storage.prototype.clearAll = function() {
this.localStorage.clear();
};
Storage.prototype.export = function(obj) {
var encryptedObj = this._encryptObj(obj);
return encryptedObj;
Storage.prototype.import = function(base64) {
var decryptedStr = this._decrypt(base64);
return JSON.parse(decryptedStr);
};
Storage.prototype.import = function(base64) {
var decryptedObj = this._decryptObj(base64);
return decryptedObj;
Storage.prototype.export = function(obj) {
var string = JSON.stringify(obj);
return this._encrypt(string);
};
module.exports = Storage;

View File

@ -22,8 +22,8 @@ angular
templateUrl: 'views/import.html',
validate: false
})
.when('/setup', {
templateUrl: 'views/setup.html',
.when('/create', {
templateUrl: 'views/create.html',
validate: false
})
.when('/copayers', {

View File

@ -2,17 +2,8 @@
var bitcore = require('bitcore');
angular.module('copayApp.services')
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, video, uriHandler) {
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, uriHandler, rateService) {
var root = {};
root.getVideoMutedStatus = function(copayer) {
if (!$rootScope.videoInfo) return;
var vi = $rootScope.videoInfo[copayer]
if (!vi) {
return;
}
return vi.muted;
};
root.redirIfLogged = function() {
if ($rootScope.wallet) {
@ -27,7 +18,6 @@ angular.module('copayApp.services')
$rootScope.wallet = null;
delete $rootScope['wallet'];
video.close();
// Clear rootScope
for (var i in $rootScope) {
if (i.charAt(0) != '$') {
@ -102,18 +92,6 @@ angular.module('copayApp.services')
root.installStartupHandlers(w, $scope);
root.updateGlobalAddresses();
var handlePeerVideo = function(err, peerID, url) {
if (err) {
delete $rootScope.videoInfo[peerID];
return;
}
$rootScope.videoInfo[peerID] = {
url: encodeURI(url),
muted: peerID === w.network.peerId
};
$rootScope.$digest();
};
notification.enableHtml5Mode(); // for chrome: if support, enable it
w.on('corrupt', function(peerId) {
@ -128,8 +106,6 @@ angular.module('copayApp.services')
} else {
$location.path('receive');
}
if (!config.disableVideo)
video.setOwnPeer(myPeerID, w, handlePeerVideo);
});
w.on('publicKeyRingUpdated', function(dontDigest) {
@ -172,9 +148,6 @@ angular.module('copayApp.services')
root.onErrorDigest(null, msg);
});
w.on('connect', function(peerID) {
if (peerID && !config.disableVideo) {
video.callPeer(peerID, handlePeerVideo);
}
$rootScope.$digest();
});
w.on('close', root.onErrorDigest);
@ -217,7 +190,15 @@ angular.module('copayApp.services')
$rootScope.balanceByAddr = balanceByAddr;
root.updateAddressList();
$rootScope.updatingBalance = false;
return cb ? cb() : null;
rateService.whenAvailable(function() {
$rootScope.totalBalanceAlternative = rateService.toFiat(balanceSat, config.alternativeIsoCode);
$rootScope.alternativeIsoCode = config.alternativeIsoCode;
$rootScope.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, config.alternativeIsoCode);
return cb ? cb() : null;
});
});
};

83
js/services/rate.js Normal file
View File

@ -0,0 +1,83 @@
'use strict';
var RateService = function(request) {
this.isAvailable = false;
this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable';
this.SAT_TO_BTC = 1 / 1e8;
var MINS_IN_HOUR = 60;
var MILLIS_IN_SECOND = 1000;
var rateServiceConfig = config.rate;
var updateFrequencySeconds = rateServiceConfig.updateFrequencySeconds || 60 * MINS_IN_HOUR;
var rateServiceUrl = rateServiceConfig.url || 'https://bitpay.com/api/rates';
this.queued = [];
this.alternatives = [];
var that = this;
var backoffSeconds = 5;
var retrieve = function() {
request.get({
url: rateServiceUrl,
json: true
}, function(err, response, listOfCurrencies) {
if (err) {
backoffSeconds *= 1.5;
setTimeout(retrieve, backoffSeconds * MILLIS_IN_SECOND);
return;
}
var rates = {};
listOfCurrencies.forEach(function(element) {
rates[element.code] = element.rate;
that.alternatives.push({
name: element.name,
isoCode: element.code,
rate: element.rate
});
});
that.isAvailable = true;
that.rates = rates;
that.queued.forEach(function(callback) {
setTimeout(callback, 1);
});
setTimeout(retrieve, updateFrequencySeconds * MILLIS_IN_SECOND);
});
};
retrieve();
};
RateService.prototype.whenAvailable = function(callback) {
if (this.isAvailable) {
setTimeout(callback, 1);
} else {
this.queued.push(callback);
}
};
RateService.prototype.toFiat = function(satoshis, code) {
if (!this.isAvailable) {
throw new Error(this.UNAVAILABLE_ERROR);
}
return satoshis * this.SAT_TO_BTC * this.rates[code];
};
RateService.prototype.fromFiat = function(amount, code) {
if (!this.isAvailable) {
throw new Error(this.UNAVAILABLE_ERROR);
}
return amount / this.rates[code] / this.SAT_TO_BTC;
};
RateService.prototype.listAlternatives = function() {
if (!this.isAvailable) {
throw new Error(this.UNAVAILABLE_ERROR);
}
var alts = [];
this.alternatives.forEach(function(element) {
alts.push({
name: element.name,
isoCode: element.isoCode
});
});
return alts;
};
angular.module('copayApp.services').service('rateService', RateService);

6
js/services/request.js Normal file
View File

@ -0,0 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('request', function() {
return require('request');
});

View File

@ -1,92 +0,0 @@
'use strict';
var Video = function() {
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
this.mediaConnections = {};
this.localStream = null;
};
Video.prototype.setOwnPeer = function(peer, wallet, cb) {
var self = this;
var VWIDTH = 320;
var VHEIGHT = 320;
var constraints = {
audio: true,
video: {
mandatory: {
maxWidth: VWIDTH,
maxHeight: VHEIGHT,
}
}
};
navigator.getUserMedia(constraints, function(stream) {
// This is called when user accepts using webcam
self.localStream = stream;
var online = wallet.getOnlinePeerIDs();
for (var i = 0; i < online.length; i++) {
var o = online[i];
if (o !== peer.id) {
self.callPeer(o, cb);
}
}
cb(null, peer.id, URL.createObjectURL(stream));
}, function() {
cb(new Error('Failed to access the webcam and microphone.'));
});
// Receiving a call
peer.on('call', function(mediaConnection) {
if (self.localStream) {
mediaConnection.answer(self.localStream);
} else {
mediaConnection.answer();
}
self._addCall(mediaConnection, cb);
});
this.peer = peer;
};
Video.prototype.callPeer = function(peerID, cb) {
if (this.localStream) {
var mediaConnection = this.peer.call(peerID, this.localStream);
this._addCall(mediaConnection, cb);
}
};
Video.prototype._addCall = function(mediaConnection, cb) {
var self = this;
var peerID = mediaConnection.peer;
// Wait for stream on the call, then set peer video display
mediaConnection.on('stream', function(stream) {
cb(null, peerID, URL.createObjectURL(stream));
});
mediaConnection.on('close', function() {
cb(true, peerID, null); // ask to stop video streaming in UI
});
mediaConnection.on('error', function(e) {
cb(e, peerID, null);
});
this.mediaConnections[peerID] = mediaConnection;
}
Video.prototype.close = function() {
if (this.localStream) {
this.localStream.stop();
this.localStream.mozSrcObject = null;
this.localStream.src = "";
this.localStream.src = null;
this.localStream = null;
}
for (var i = 0; this.mediaConnections.length; i++) {
this.mediaConnections[i].close();
}
this.mediaConnections = {};
};
angular.module('copayApp.services').value('video', new Video());

View File

@ -28,7 +28,6 @@ module.exports = function(config) {
'lib/angular-route/angular-route.min.js',
'lib/angular-foundation/mm-foundation.min.js',
'lib/angular-foundation/mm-foundation-tpls.min.js',
'lib/peerjs/peer.js',
'lib/bitcore.js',
'lib/crypto-js/rollups/sha256.js',
'lib/crypto-js/rollups/pbkdf2.js',
@ -42,6 +41,7 @@ module.exports = function(config) {
//App-specific Code
'js/app.js',
'js/log.js',
'js/routes.js',
'js/services/*.js',
'js/directives.js',

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,9 @@
"travis-cov": "0.2.5",
"uglifyify": "1.2.3",
"crypto-js": "3.1.2",
"shelljs": "0.3.0"
"shelljs":"0.3.0",
"browser-request": "0.3.2",
"request": "2.40.0"
},
"main": "app.js",
"homepage": "https://github.com/bitpay/copay",

0
test/run.sh Normal file → Executable file
View File

View File

@ -18,7 +18,7 @@ describe('Passphrase model', function() {
should.exist(p);
});
it('should generate key from password', function () {
it('should generate key from password', function (done) {
var p = new Passphrase({
salt: 'mjuBtGybi/4=',
iterations: 10,
@ -33,6 +33,7 @@ describe('Passphrase model', function() {
p.getBase64Async(pass, function (ret) {
ret.toString().should.equal('IoP+EbmhibgvHAkgCAaSDL3Y73UvU96pEPkKtSb0Qazb1RKFVWR6fjkKGp/qBCImljzND3hRAws9bigszrqhfg==');
done();
});
});

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,9 @@ describe("Unit: Controllers", function() {
totalCopayers: 5,
spendUnconfirmed: 1,
reconnectDelay: 100,
networkName: 'testnet'
networkName: 'testnet',
alternativeName: 'lol currency',
alternativeIsoCode: 'LOL'
};
it('Copay config should be binded', function() {
@ -65,11 +67,11 @@ describe("Unit: Controllers", function() {
});
});
describe('Setup Controller', function() {
var setupCtrl;
describe('Create Controller', function() {
var c;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
setupCtrl = $controller('SetupController', {
c = $controller('CreateController', {
$scope: scope,
});
}));
@ -124,11 +126,21 @@ describe("Unit: Controllers", function() {
});
describe('Send Controller', function() {
var scope, form, sendForm;
var scope, form, sendForm, sendCtrl;
beforeEach(angular.mock.module('copayApp'));
beforeEach(angular.mock.inject(function($compile, $rootScope, $controller) {
beforeEach(module(function($provide) {
$provide.value('request', {
'get': function(_, cb) {
cb(null, null, [{name: 'lol currency', code: 'LOL', rate: 2}]);
}
});
}));
beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService) {
scope = $rootScope.$new();
scope.rateService = rateService;
$rootScope.wallet = new FakeWallet(walletConfig);
config.alternativeName = 'lol currency';
config.alternativeIsoCode = 'LOL';
var element = angular.element(
'<form name="form">' +
'<input type="text" id="newaddress" name="newaddress" ng-disabled="loading" placeholder="Address" ng-model="newaddress" valid-address required>' +
@ -147,11 +159,12 @@ describe("Unit: Controllers", function() {
'<form name="form2">' +
'<input type="text" id="address" name="address" ng-model="address" valid-address required>' +
'<input type="number" id="amount" name="amount" ng-model="amount" min="1" max="10000000000" required>' +
'<input type="number" id="alternative" name="alternative" ng-model="alternative">' +
'<textarea id="comment" name="comment" ng-model="commentText" ng-maxlength="100"></textarea>' +
'</form>'
);
$compile(element2)(scope);
$controller('SendController', {
sendCtrl = $controller('SendController', {
$scope: scope,
$modal: {},
});
@ -241,8 +254,22 @@ describe("Unit: Controllers", function() {
config.unitToSatoshi = old;
});
it('should convert bits amount to fiat', function(done) {
scope.rateService.whenAvailable(function() {
sendForm.amount.$setViewValue(1e6);
scope.$digest();
expect(scope.alternative).to.equal(2);
done();
});
});
it('should convert fiat to bits amount', function(done) {
scope.rateService.whenAvailable(function() {
sendForm.alternative.$setViewValue(2);
scope.$digest();
expect(scope.amount).to.equal(1e6);
done();
});
});
it('should create and send a transaction proposal', function() {
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');

View File

@ -79,8 +79,6 @@ describe("Unit: controllerUtils", function() {
expect($rootScope.addrInfos[0].address).to.be.equal(Waddr);;
}));
});
});
describe("Unit: Notification Service", function() {
@ -118,12 +116,7 @@ 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) {
@ -135,3 +128,36 @@ describe("Unit: uriHandler service", function() {
}).should.not.throw();
}));
});
describe('Unit: Rate Service', function() {
beforeEach(angular.mock.module('copayApp.services'));
it('should be injected correctly', inject(function(rateService) {
should.exist(rateService);
}));
it('should be possible to ask if it is available',
inject(function(rateService) {
should.exist(rateService.isAvailable);
})
);
beforeEach(module(function($provide) {
$provide.value('request', {
'get': function(_, cb) {
cb(null, null, [{name: 'lol currency', code: 'LOL', rate: 2}]);
}
});
}));
it('should be possible to ask for conversion from fiat',
inject(function(rateService) {
rateService.whenAvailable(function() {
(1).should.equal(rateService.fromFiat(2, 'LOL'));
});
})
);
it('should be possible to ask for conversion to fiat',
inject(function(rateService) {
rateService.whenAvailable(function() {
(2).should.equal(rateService.toFiat(1e8, 'LOL'));
});
})
);
});

View File

@ -51,10 +51,10 @@
<div class="box-setup-copayers p20">
<div class="oh">
<div ng-include="'views/includes/video.html'"></div>
<div ng-include="'views/includes/copayer.html'"></div>
<div ng-if="!$root.wallet.publicKeyRing.isComplete()">
<img
class="waiting br100 no-video"
class="waiting br100"
ng-if="!hasVideo(copayer)"
src="./img/satoshi.gif"
alt="Waiting Copayer"

View File

@ -1,4 +1,4 @@
<div ng-controller="SetupController">
<div ng-controller="CreateController">
<div data-alert class="loading-screen" ng-show="loading">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
Creating wallet...

View File

@ -9,13 +9,13 @@
<a class="text-white" href="#!/open">Open a wallet</a>
</div>
<div class="button-setup" ng-show="!hasWallets">
<a class="text-secondary" href="#!/setup">Create a new wallet</a>
<a class="text-secondary" href="#!/create">Create a new wallet</a>
</div>
<div class="button-setup">
<a class="text-primary" href="#!/join">Join a Wallet in Creation</a>
</div>
<div class="button-setup" ng-show="hasWallets">
<a class="text-secondary" href="#!/setup">Create a wallet</a>
<a class="text-secondary" href="#!/create">Create a wallet</a>
</div>
<div class="footer-setup">

View File

@ -1,17 +1,9 @@
<div ng-controller="VideoController">
<div class="video-box" ng-repeat="copayer in copayersList()">
<video
ng-if="hasVideo(copayer)"
peer="{{copayer}}" avatar autoplay
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
ng-src="{{getVideoURL(copayer)}}"></video>
<div ng-controller="CopayersController">
<div class="copay-box" ng-repeat="copayer in copayersList()">
<img
class="br100 no-video"
ng-if="!hasVideo(copayer)"
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
class="br100 online"
src="./img/satoshi.gif"
alt="{{copayer}}"
alt="{{copayer.peerId}}"
width="70">
<div

View File

@ -1,55 +1,22 @@
<div ng-controller="VideoController" class="copayers">
<div>
<h3>
<i class="fi-torsos-all size-21 m20r"></i>
Copayers
<small class="m15l">
{{$root.wallet.requiredCopayers}} of {{$root.wallet.totalCopayers}}
</small>
</h3>
</div>
<div ng-controller="CopayersController" class="copayers">
<h3>
<i class="fi-torsos-all size-21 m20r"></i>
Copayers
<small class="m15l">
{{$root.wallet.requiredCopayers}} of {{$root.wallet.totalCopayers}}
</small>
</h3>
<ul class="copayer-list" ng-repeat="copayer in copayers">
<li class="ellipsis">
<video
ng-if="hasVideo(copayer)"
peer="{{copayer}}" avatar autoplay
ng-class="isConnected(copayer) ? 'online' : 'offline'"
ng-src="{{getVideoURL(copayer)}}"></video>
<div class="copay-box-small" ng-repeat="copayer in copayers">
<img
class="br100 online"
src="./img/satoshi.gif"
alt="{{copayer.peerId}}"
width="30">
<img
class="br100 oh no-video m20r"
ng-if="!hasVideo(copayer)"
ng-class="isConnected(copayer) ? 'online' : 'offline'"
src="./img/satoshi.gif"
alt="{{copayer}}"
width="70">
<span ng-show="copayer.index == 0">Me</span>
<span ng-show="copayer.index > 0">{{copayer.nick}}</span>
<span class="btn-copy" clip-copy="copayer.peerId"></span>
</li>
</ul>
<div class="copayer-list-small-height" ng-repeat="copayer in copayers">
<span class="left" class="has-tip" tooltip-placement="top" tooltip="{{copayer.nick}}">
<video
ng-if="hasVideo(copayer)"
peer="{{copayer}}" avatar autoplay
ng-class="isConnected(copayer) ? 'online' : 'offline'"
ng-src="{{getVideoURL(copayer)}}"></video>
<img
class="br100 oh no-video m20r"
ng-if="!hasVideo(copayer)"
ng-class="isConnected(copayer) ? 'online' : 'offline'"
src="./img/satoshi.gif"
alt="{{copayer}}"
width="70">
<!-- <span ng-show="copayer.index == 0">Me</span>
<span ng-show="copayer.index > 0">{{copayer.nick}}</span>
<span class="btn-copy" clip-copy="copayer.peerId"></span> -->
</span>
</div>
<div class="ellipsis" tooltip-placement="top" tooltip="{{copayer.nick}}">
<small class="text-gray" ng-show="copayer.index == 0">Me</small>
<small class="text-gray" ng-show="copayer.index > 0">{{copayer.nick}}</small>
</div>
</div>
</div>

View File

@ -24,10 +24,9 @@
class="has-tip"
data-options="disable_for_touch:true"
tooltip-popup-delay='500'
tooltip="{{totalBalanceBTC |noFractionNumber:8}} BTC"
tooltip="{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{totalBalance || 0
|noFractionNumber}} {{$root.unitName}}
tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
</span>
<br>
Locked &nbsp;
@ -38,7 +37,7 @@
class="has-tip"
data-options="disable_for_touch:true"
tooltip-popup-delay='500'
tooltip="{{lockedBalanceBTC |noFractionNumber:8}} BTC"
tooltip="{{lockedBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
</span> &nbsp;<i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>

View File

@ -1,8 +1,8 @@
<div class="backup" ng-controller="MoreController">
<h1>Backup & Delete </h1>
<h1>Settings </h1>
<div class="oh large-12 columns panel">
<h3><i class="fi-download m10r"></i> Backup </h3>
<p class="large-8 columns text-gray"> Its important to back up your wallet so that you can recover your wallet in case of disaster </p>
<p class="large-8 columns text-gray"> It's important to backup your wallet so that you can recover it in case of disaster</p>
<div class="large-4 columns">
<a class="button primary expand" ng-click="downloadBackup()">Download File</a>
</div>

View File

@ -83,6 +83,22 @@
</div>
</div>
</div>
<div class="large-6 medium-6 columns">
<div class="row collapse">
<label for="alternative">Amount in {{ alternativeName }} </label>
<div class="small-9 columns">
<input type="number" id="alternative_amount"
ng-disabled="loading || !isRateAvailable "
name="alternative" placeholder="Amount" ng-model="alternative"
required
autocomplete="off"
>
</div>
<div class="small-3 columns">
<span class="postfix">{{alternativeIsoCode}}</span>
</div>
</div>
</div>
</div>
<div class="row" ng-show="wallet.isShared()">
@ -110,7 +126,7 @@
</form>
</div>
<div class="large-6 columns show-for-large-up">
<div class="large-6 columns show-for-large-up" ng-show="!!$root.merchant">
<div class="send-note">
<h6>Send to</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}">
@ -119,8 +135,11 @@
<h6>Total amount for this transaction:</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}">
<b>{{amount + defaultFee |noFractionNumber}}</b> {{$root.unitName}}
<small ng-if="isRateAvailable">
{{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }}
<br>
</small>
<small>
{{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC <br/>
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}}
</small>
</p>

View File

@ -27,9 +27,9 @@
</select>
</fieldset>
<fieldset>
<legend>Videoconferencing</legend>
<input id="disableVideo-opt" type="checkbox" ng-model="disableVideo" class="form-control">
<label for="disableVideo-opt">Disable videoconferencing (for slow networks)</label>
<legend>Alternative Currency</legend>
<select class="form-control" ng-model="selectedAlternative" ng-options="alternative.name for alternative in alternativeOpts" required>
</select>
</fieldset>
<fieldset>
<legend>Insight API server</legend>