diff --git a/Gruntfile.js b/Gruntfile.js index 907f8c706..21b799ac7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -124,8 +124,7 @@ module.exports = function(grunt) { 'lib/socket.io-client/socket.io.js', 'lib/sjcl.js', 'lib/ios-imagefile-megapixel/megapix-image.js', - 'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js', - 'lib/zeroclipboard/ZeroClipboard.min.js' + 'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js' ], dest: 'lib/vendors.js' }, diff --git a/bower.json b/bower.json index c77f71035..60023c9d9 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,6 @@ "angular-moment": "~0.7.1", "socket.io-client": ">=1.0.0", "mousetrap": "1.4.6", - "zeroclipboard": "~1.3.5", "ng-idle": "*", "inherits": "~0.0.1", "angular-load": "0.2.0", diff --git a/js/directives.js b/js/directives.js index 7bf829def..3fea9be8b 100644 --- a/js/directives.js +++ b/js/directives.js @@ -5,6 +5,24 @@ var Address = bitcore.Address; var bignum = bitcore.Bignum; var preconditions = require('preconditions').singleton(); + +function selectText(element) { + var doc = document; + if (doc.body.createTextRange) { // ms + var range = doc.body.createTextRange(); + range.moveToElementText(element); + range.select(); + } else if (window.getSelection) { + var selection = window.getSelection(); + var range = doc.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + + } +} + + angular.module('copayApp.directives') .directive('validAddress', ['$rootScope', @@ -162,10 +180,14 @@ angular.module('copayApp.directives') var contact = scope.wallet.addressBook[address]; if (contact && !contact.hidden) { element.append(contact.label); - attrs['tooltip'] = attrs.address; + element.attr('tooltip',attrs.address); } else { element.append(address); } + + element.bind('click', function() { + selectText(elm[0]); + }); } }; }) @@ -296,39 +318,19 @@ angular.module('copayApp.directives') }; }) .directive('clipCopy', function() { - ZeroClipboard.config({ - moviePath: './lib/zeroclipboard/ZeroClipboard.swf', - trustedDomains: ['*'], - allowScriptAccess: 'always', - forceHandCursor: true - }); - return { restric: 'A', scope: { clipCopy: '=clipCopy' }, link: function(scope, elm) { - var client = new ZeroClipboard(elm); + // TODO this does not work (FIXME) + elm.attr('tooltip','Press Ctrl+C to Copy'); + elm.attr('tooltip-placement','top'); - client.on('load', function(client) { - - client.on('datarequested', function(client) { - client.setText(scope.clipCopy); - }); - - client.on('complete', function(client, args) { - elm.removeClass('btn-copy').addClass('btn-copied').html('Copied!'); - setTimeout(function() { - elm.addClass('btn-copy').removeClass('btn-copied').html(''); - }, 1000); - }); + elm.bind('click', function() { + selectText(elm[0]); }); - client.on('wrongflash noflash', function() { - elm.removeClass('btn-copy').html(''); - ZeroClipboard.destroy(); - }); - } }; }); diff --git a/js/models/Insight.js b/js/models/Insight.js index b62181117..ddf408ed0 100644 --- a/js/models/Insight.js +++ b/js/models/Insight.js @@ -256,8 +256,8 @@ Insight.prototype.broadcast = function(rawtx, cb) { this.requestPost('/api/tx/send', { rawtx: rawtx }, function(err, res, body) { - if (err || res.statusCode != 200) cb(err || body); - cb(null, body ? body.txid : null); + if (err || res.statusCode != 200) return cb(err || body); + return cb(null, body ? body.txid : null); }); }; diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js index ae9de2dd9..fba658556 100644 --- a/js/models/TxProposal.js +++ b/js/models/TxProposal.js @@ -12,7 +12,7 @@ var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); var VERSION = 1; -var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment']; +var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL']; function TxProposal(opts) { @@ -37,6 +37,7 @@ function TxProposal(opts) { this.comment = opts.comment || null; this.readonly = opts.readonly || null; this.merchant = opts.merchant || null; + this.paymentProtocolURL = opts.paymentProtocolURL || null; this._sync(); } diff --git a/js/models/Wallet.js b/js/models/Wallet.js index debb6b82a..22c98aa81 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -426,14 +426,79 @@ Wallet.prototype._getKeyMap = function(txp) { Wallet.prototype._checkSentTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); var tx = txp.builder.build(); - var txid = bitcore.util.formatHashFull(tx.getHash()); + var txHex = tx.serialize().toString('hex'); + + //Use calcHash NOT getHash which could be cached. + var txid = bitcore.util.formatHashFull(tx.calcHash()); this.blockchain.getTransaction(txid, function(err, tx) { if (err) return cb(false); return cb(txid); }); }; +Wallet.prototype._processTxProposalSeen = function(ntxid) { + var txp = this.txProposals.get(ntxid); + if (!txp.getSeen(this.getMyCopayerId())) { + txp.setSeen(this.getMyCopayerId()); + this.sendSeen(ntxid); + } +}; + + +Wallet.prototype._processTxProposalSent = function(ntxid, cb) { + var self = this; + var txp = this.txProposals.get(ntxid); + + this._checkSentTx(ntxid, function(txid) { + if (txid) { + if (!txp.getSent()) { + txp.setSent(txid); + } + } + self.emitAndKeepAlive('txProposalsUpdated'); + if (cb) return cb(null, txid); + }); +}; + + +Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { + var self = this; + var txp = mergeInfo.txp; + var isNew = mergeInfo.new; + var ntxid = mergeInfo.ntxid; + + if (!isNew || !txp.paymentProtocolURL) + return cb(); + + log.info('Received a Payment Protocol TX Proposal'); + self.fetchPaymentTx(txp.paymentProtocolURL, function(err, merchantData) { + if (err) return cb(err); + txp.merchant = merchantData; + return cb(); + }); +}; + +Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) { + if (!mergeInfo) return cb(); + var self = this; + + self._processTxProposalPayPro(mergeInfo, function(err) { + if (err) return cb(err); + + self._processTxProposalSeen(mergeInfo.ntxid); + + var tx = mergeInfo.txp.builder.build(); + if (tx.isComplete()) + self._processTxProposalSent(mergeInfo.ntxid); + else if (mergeInfo.hasChanged) { + self.sendTxProposal(mergeInfo.ntxid); + self.emitAndKeepAlive('txProposalsUpdated'); + } + return cb(); + }); +}; + /** * @desc * Handles a 'TXPROPOSAL' network message @@ -454,34 +519,20 @@ Wallet.prototype._onTxProposal = function(senderId, data) { m.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { log.error('Corrupt TX proposal received from:', senderId, e.toString()); + if (m && m.ntxid) + this.txProposals.deleteOne(m.ntxid); m = null; } - if (m) { - if (!m.txp.getSeen(this.getMyCopayerId())) { - m.txp.setSeen(this.getMyCopayerId()); - this.sendSeen(m.ntxid); + self._processIncomingTxProposal(m, function(err) { + if (err) { + log.error('Corrupt TX proposal received from:', senderId, err.toString()); + if (m && m.ntxid) + self.txProposals.deleteOne(m.ntxid); + m = null; } - - var tx = m.txp.builder.build(); - if (tx.isComplete()) { - this._checkSentTx(m.ntxid, function(txid) { - if (txid) { - if (!m.txp.getSent()) { - m.txp.setSent(txid); - self.emitAndKeepAlive('txProposalsUpdated'); - } - } - }); - } else { - if (m.hasChanged) { - this.sendTxProposal(m.ntxid); - } - } - - this.emitAndKeepAlive('txProposalsUpdated'); - } - this._processProposalEvents(senderId, m); + self._processProposalEvents(senderId, m); + }); }; /** @@ -1420,16 +1471,18 @@ Wallet.prototype.sign = function(ntxid) { Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); - if (txp.merchant) { - return this.sendPaymentTx(ntxid, cb); - } var tx = txp.builder.build(); if (!tx.isComplete()) throw new Error('Tx is not complete. Can not broadcast'); + log.debug('Wallet:' + this.id + ' Broadcasting Transaction'); + + if (txp.merchant) { + return this.sendPaymentTx(ntxid, cb); + } + var scriptSig = tx.ins[0].getScript(); var size = scriptSig.serialize().length; - var txHex = tx.serialize().toString('hex'); log.debug('Wallet:' + this.id + ' Raw transaction: ', txHex); @@ -1446,10 +1499,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) { return cb(txid); } else { log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already'); - self._checkSentTx(ntxid, function(txid) { - if (txid) - self.emitAndKeepAlive('txProposalsUpdated'); - + self._processTxProposalSent(ntxid, function(err, txid) { return cb(txid); }); } @@ -1640,6 +1690,7 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { if (!unspent || !unspent.length) { return cb(new Error('No unspent outputs available.')); } + try { self.createPaymentTxSync(options, merchantData, safeUnspent); } catch (e) { @@ -1765,7 +1816,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { view[i] = pay[i]; } - return Wallet.request({ + var postInfo = { method: 'POST', url: txp.merchant.pr.pd.payment_url, headers: { @@ -1780,7 +1831,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { // be the ArrayBuffer, now you send the View instead). data: view, responseType: 'arraybuffer' - }) + }; + + return Wallet.request(postInfo) .success(function(data, status, headers, config) { data = PayPro.PaymentACK.decode(data); var ack = new PayPro(); @@ -1790,9 +1843,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { .error(function(data, status, headers, config) { log.debug('Sending to server was not met with a returned tx.'); log.debug('XHR status: ' + status); - return self._checkSentTx(ntxid, function(txid) { - if (txid) - self.emitAndKeepAlive('txProposalsUpdated'); + self._processTxProposalSent(ntxid, function(err, txid) { return cb(txid, txp.merchant); }); }); @@ -1822,10 +1873,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { tx = payment.message.transactions[0]; if (!tx) { log.debug('Sending to server was not met with a returned tx.'); - return this._checkSentTx(ntxid, function(txid) { + return this._processTxProposalSeen(ntxid, function(err, txid) { log.debug('[Wallet.js.1613:txid:%s]', txid); - if (txid) - self.emitAndKeepAlive('txProposalUpdated'); return cb(txid, txp.merchant); }); } @@ -1841,7 +1890,7 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { log.debug('It is not returning a copy of the transaction.'); } - var txid = tx.getHash().toString('hex'); + var txid = tx.calcHash().toString('hex'); var txHex = tx.serialize().toString('hex'); log.debug('Raw transaction: ', txHex); @@ -1855,11 +1904,8 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { self.emitAndKeepAlive('txProposalsUpdated'); return cb(txid, txp.merchant); } else { - log.debug('Sent failed. Checking if the TX was sent already'); - self._checkSentTx(ntxid, function(txid) { - if (txid) - self.emitAndKeepAlive('txProposalsUpdated'); - + log.debug('PayPro Sent failed. Checking if the TX was sent already'); + self._processTxProposalSent(ntxid, function(err, txid) { return cb(txid, txp.merchant); }); } @@ -1951,6 +1997,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) }); var selectedUtxos = b.getSelectedUnspent(); + if (selectedUtxos.length > TX_MAX_INS) + throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.length + ' inputs. Aborting'); + + var inputChainPaths = selectedUtxos.map(function(utxo) { return pkr.pathForAddress(utxo.address); }); @@ -1971,6 +2021,12 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) if (!tx.countInputSignatures(0)) throw new Error('Could not sign generated tx'); + var txSize = tx.getSize(); + if (txSize / 1024 > TX_MAX_SIZE_KB) + throw new Error('BIG: Resulting TX is too big ' + txSize + ' bytes. Aborting'); + + + var me = {}; me[myId] = now; var meSeen = {}; @@ -1984,8 +2040,10 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) createdTs: now, builder: b, comment: options.memo, - merchant: merchantData + merchant: merchantData, + paymentProtocolURL: options.uri, })); + return ntxid; }; @@ -2187,7 +2245,7 @@ Wallet.prototype.subscribeToAddresses = function() { var addrInfo = this.publicKeyRing.getAddressesInfo(); this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr')); - log.debug('Subscribed to ' + addrInfo.length + ' addresses'); //TODO + log.debug('Subscribed to ' + addrInfo.length + ' addresses'); }; /** @@ -2386,7 +2444,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) var ntxid; try { ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts); - log.debug('TX Created: ntxid', ntxid); //TODO + log.debug('TX Created: ntxid', ntxid); } catch (e) { return cb(e); } diff --git a/test/Wallet.js b/test/Wallet.js index 1c8c38ab6..f0d3466b9 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -1533,6 +1533,8 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); + w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, @@ -1574,6 +1576,7 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, @@ -1616,6 +1619,7 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, @@ -1649,6 +1653,7 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, @@ -1682,6 +1687,7 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, @@ -1712,6 +1718,7 @@ describe('Wallet model', function() { }, }; + w.txProposals.get = sinon.stub().returns(txp); w.txProposals.merge = sinon.stub().returns({ ntxid: 1, txp: txp, diff --git a/views/home.html b/views/home.html index d59484531..a0ea9543a 100644 --- a/views/home.html +++ b/views/home.html @@ -46,7 +46,12 @@ You can import your current wallets after creating your profile + +
+

http://HOLA 1234

+ +

http://HOLA 1234

Sign in to Copay

-

{{$root.addrInfos[0].addressStr}}

+

{{$root.addrInfos[0].addressStr}}

-
- -
diff --git a/views/receive.html b/views/receive.html index c82c2443b..6ec2c7fe3 100644 --- a/views/receive.html +++ b/views/receive.html @@ -12,9 +12,8 @@
- + - change