fix conflics

This commit is contained in:
Matias Alejo Garcia 2014-06-19 16:34:37 -03:00
commit 44901cd635
19 changed files with 560 additions and 350 deletions

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('HeaderController',
function($scope, $rootScope, $location, $notification, $http, controllerUtils) {
function($scope, $rootScope, $location, notification, $http, controllerUtils) {
$scope.menu = [
{
'title': 'Addresses',
@ -64,7 +64,7 @@ angular.module('copayApp.controllers').controller('HeaderController',
}
if (currentAddr) {
//var beep = new Audio('sound/transaction.mp3');
$notification.funds('Received fund', currentAddr, receivedFund);
notification.funds('Received fund', currentAddr, receivedFund);
//beep.play();
}
}
@ -72,7 +72,7 @@ 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);
}
});

View file

@ -6,21 +6,16 @@ angular.module('copayApp.controllers').controller('ImportController',
var reader = new FileReader();
var _importBackup = function(encryptedObj) {
Passphrase.getBase64Async($scope.password, function(passphrase){
var w, errMsg;
try {
w = walletFactory.fromEncryptedObj(encryptedObj, passphrase);
} catch(e) {
errMsg = e.message;
}
if (!w) {
$scope.loading = false;
$rootScope.$flashMessage = { message: errMsg || 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
walletFactory.import(encryptedObj, passphrase, function(err, w) {
if (err) {
$scope.loading = false;
$rootScope.$flashMessage = { message: err.errMsg || 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
});
});
};

View file

@ -163,11 +163,34 @@ Insight.prototype.sendRawTransaction = function(rawtx, cb) {
});
};
Insight.prototype.checkActivity = function(addresses, cb) {
if (!addresses) throw new Error('address must be set');
this.getTransactions(addresses, function onResult(txs) {
var flatArray = function (xss) { return xss.reduce(function(r, xs) { return r.concat(xs); }, []); };
var getInputs = function (t) { return t.vin.map(function (vin) { return vin.addr }); };
var getOutputs = function (t) { return flatArray(
t.vout.map(function (vout) { return vout.scriptPubKey.addresses; })
);};
var activityMap = new Array(addresses.length);
var activeAddress = flatArray(txs.map(function(t) { return getInputs(t).concat(getOutputs(t)); }));
activeAddress.forEach(function (addr) {
var index = addresses.indexOf(addr);
if (index != -1) activityMap[index] = true;
});
cb(null, activityMap);
});
};
Insight.prototype._requestNode = function(options, callback) {
if (options.method === 'POST') {
options.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': options.data.length,
};
}

View file

@ -30,7 +30,7 @@ AddressIndex.prototype.toObj = function() {
AddressIndex.prototype.checkRange = function(index, isChange) {
if ((isChange && index > this.changeIndex) ||
(!isChange && index > this.receiveIndex)) {
throw new Error('Out of bounds at index %d isChange: %d', index, isChange);
throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange);
}
};

View file

@ -157,8 +157,6 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
// TODO this could be cached
PublicKeyRing.prototype.getRedeemScript = function (index, isChange) {
this.indexes.checkRange(index, isChange);
var pubKeys = this.getPubKeys(index, isChange);
var script = Script.createMultisig(this.requiredCopayers, pubKeys);
return script;

View file

@ -22,6 +22,11 @@ function TxProposal(opts) {
this.comment = opts.comment || null;
}
TxProposal.prototype.getID = function() {
var ntxid = this.builder.build().getNormalizedHash().toString('hex');
return ntxid;
};
TxProposal.prototype.toObj = function() {
var o = JSON.parse(JSON.stringify(this));
delete o['builder'];
@ -46,6 +51,76 @@ TxProposal.getSentTs = function() {
return this.sentTs;
};
TxProposal.prototype.merge = function(other) {
var ret = {};
ret.events = this.mergeMetadata(other);
ret.hasChanged = this.mergeBuilder(other);
return ret;
};
TxProposal.prototype.mergeBuilder = function(other) {
var b0 = this.builder;
var b1 = other.builder;
// TODO: improve this comparison
var before = JSON.stringify(b0.toObj());
b0.merge(b1);
var after = JSON.stringify(b0.toObj());
return after !== before;
};
TxProposal.prototype.mergeMetadata = function(v1) {
var events = [];
var v0 = this;
var ntxid = this.getID();
Object.keys(v1.seenBy).forEach(function(k) {
if (!v0.seenBy[k]) {
v0.seenBy[k] = v1.seenBy[k];
events.push({
type: 'seen',
cId: k,
txId: ntxid
});
}
});
Object.keys(v1.signedBy).forEach(function(k) {
if (!v0.signedBy[k]) {
v0.signedBy[k] = v1.signedBy[k];
events.push({
type: 'signed',
cId: k,
txId: ntxid
});
}
});
Object.keys(v1.rejectedBy).forEach(function(k) {
if (!v0.rejectedBy[k]) {
v0.rejectedBy[k] = v1.rejectedBy[k];
events.push({
type: 'rejected',
cId: k,
txId: ntxid
});
}
});
if (!v0.sentTxid && v1.sentTxid) {
v0.sentTs = v1.sentTs;
v0.sentTxid = v1.sentTxid;
events.push({
type: 'broadcast',
txId: ntxid
});
}
return events;
};
module.exports = require('soop')(TxProposal);
@ -75,6 +150,7 @@ TxProposals.prototype.getNtxids = function() {
};
TxProposals.prototype.toObj = function(onlyThisNtxid) {
if (onlyThisNtxid) throw new Error();
var ret = [];
for (var id in this.txps) {
@ -92,114 +168,37 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) {
};
};
TxProposals.prototype._startMerge = function(myTxps, theirTxps) {
var fromUs = 0,
fromTheirs = 0,
merged = 0;
var toMerge = {},
ready = {},
events = [];
TxProposals.prototype.merge = function(inTxp) {
var myTxps = this.txps;
for (var hash in theirTxps) {
if (!myTxps[hash]) {
ready[hash] = theirTxps[hash]; // only in theirs;
events.push({type: 'new', cid: theirTxps[hash].creator, tx: hash});
fromTheirs++;
} else {
toMerge[hash] = theirTxps[hash]; // need Merging
merged++;
}
}
var ntxid = inTxp.getID();
var ret = {};
ret.events = [];
ret.events.hasChanged = false;
for (var hash in myTxps) {
if (!toMerge[hash]) {
ready[hash] = myTxps[hash]; // only in myTxps;
fromUs++;
}
}
return {
stats: {
fromUs: fromUs,
fromTheirs: fromTheirs,
merged: merged,
},
ready: ready,
toMerge: toMerge,
events: events
};
};
// TODO add signatures.
TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) {
var toMerge = mergeInfo.toMerge;
var hasChanged = 0;
var events = [];
Object.keys(toMerge).forEach(function(hash) {
var v0 = myTxps[hash];
var v1 = toMerge[hash];
Object.keys(v1.seenBy).forEach(function(k) {
if (!v0.seenBy[k]) {
v0.seenBy[k] = v1.seenBy[k];
events.push({type: 'seen', cId: k, txId: hash});
}
if (myTxps[ntxid]) {
var v0 = myTxps[ntxid];
var v1 = inTxp;
ret = v0.merge(v1);
} else {
this.txps[ntxid] = inTxp;
ret.hasChanged = true;
ret.events.push({
type: 'new',
cid: inTxp.creator,
tx: ntxid
});
Object.keys(v1.signedBy).forEach(function(k) {
if (!v0.signedBy[k]) {
v0.signedBy[k] = v1.signedBy[k];
events.push({type: 'signed', cId: k, txId: hash});
}
});
Object.keys(v1.rejectedBy).forEach(function(k) {
if (!v0.rejectedBy[k]) {
v0.rejectedBy[k] = v1.rejectedBy[k];
events.push({type: 'rejected', cId: k, txId: hash});
}
});
if (!v0.sentTxid && v1.sentTxid) {
v0.sentTs = v1.sentTs;
v0.sentTxid = v1.sentTxid;
events.push({type: 'broadcast', txId: hash});
}
});
return {
events: events.concat(mergeInfo.events),
hasChanged: events.length
};
};
TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) {
var toMerge = mergeInfo.toMerge;
var hasChanged = 0;
for (var hash in toMerge) {
var v0 = myTxps[hash].builder;
var v1 = toMerge[hash].builder;
// TODO: enhance this
var before = JSON.stringify(v0.toObj());
v0.merge(v1);
var after = JSON.stringify(v0.toObj());
if (after !== before) hasChanged++;
}
return ret;
};
TxProposals.prototype.add = function(data) {
var ntxid = data.builder.build().getNormalizedHash().toString('hex');
this.txps[ntxid] = new TxProposal(data);
var txp = new TxProposal(data);
var ntxid = txp.getID();
this.txps[ntxid] = txp;
return ntxid;
};
TxProposals.prototype.setSent = function(ntxid, txid) {
//sent TxProposals are local an not broadcasted.
this.txps[ntxid].setSent(txid);
@ -259,26 +258,5 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
return ret;
};
TxProposals.prototype.merge = function(t) {
if (this.network.name !== t.network.name)
throw new Error('network mismatch in:', t);
var myTxps = this.txps;
var theirTxps = t.txps;
var mergeInfo = this._startMerge(myTxps, theirTxps);
var result = this._mergeMetadata(myTxps, theirTxps, mergeInfo);
result.hasChanged += this._mergeBuilder(myTxps, theirTxps, mergeInfo);
Object.keys(mergeInfo.toMerge).forEach(function(hash) {
mergeInfo.ready[hash] = myTxps[hash];
});
mergeInfo.stats.hasChanged = result.hasChanged;
mergeInfo.stats.events = result.events;
this.txps = mergeInfo.ready;
return mergeInfo.stats;
};
TxProposals.TxProposal = TxProposal;
module.exports = require('soop')(TxProposals);

View file

@ -2,17 +2,24 @@
var imports = require('soop').imports();
var http = require('http');
var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
var async = require('async');
var preconditions = require('preconditions').instance();
var bitcore = require('bitcore');
var bignum = bitcore.Bignum;
var coinUtil = bitcore.util;
var buffertools = bitcore.buffertools;
var Builder = bitcore.TransactionBuilder;
var http = require('http');
var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
var copay = copay || require('../../../copay');
var SecureRandom = bitcore.SecureRandom;
var Base58Check = bitcore.Base58.base58Check;
var AddressIndex = require('./AddressIndex');
var PublicKeyRing = require('./PublicKeyRing');
var TxProposals = require('./TxProposals');
var PrivateKey = require('./PrivateKey');
function Wallet(opts) {
var self = this;
@ -74,7 +81,7 @@ Wallet.prototype.connectToAll = function() {
Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
this.log('RECV INDEXES:', data);
var inIndexes = copay.AddressIndex.fromObj(data.indexes);
var inIndexes = AddressIndex.fromObj(data.indexes);
var hasChanged = this.publicKeyRing.indexes.merge(inIndexes);
if (hasChanged) {
this.emit('publicKeyRingUpdated');
@ -85,7 +92,7 @@ Wallet.prototype._handleIndexes = function(senderId, data, isInbound) {
Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
this.log('RECV PUBLICKEYRING:', data);
var inPKR = copay.PublicKeyRing.fromObj(data.publicKeyRing);
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
var wasIncomplete = !this.publicKeyRing.isComplete();
var hasChanged;
@ -110,31 +117,17 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
};
Wallet.prototype._handleTxProposals = function(senderId, data, isInbound) {
Wallet.prototype._handleTxProposal = function(senderId, data) {
this.log('RECV TXPROPOSAL:', data);
var recipients;
var inTxp = copay.TxProposals.fromObj(data.txProposals);
var ids = inTxp.getNtxids();
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal);
if (ids.length > 1) {
this.emit('badMessage', senderId);
this.log('Received BAD TxProposal messsage FROM:', senderId); //TODO
return;
}
var mergeInfo = this.txProposals.merge(inTxp);
var added = this.addSeenToTxProposals();
this.emit('txProposalsUpdated');
this.store();
var newId = ids[0];
var mergeInfo = this.txProposals.merge(inTxp, true);
var addSeen = this.addSeenToTxProposals();
if (mergeInfo.hasChanged || addSeen) {
this.log('### BROADCASTING txProposals. ');
recipients = null;
this.sendTxProposals(recipients, newId);
}
if (data.lastInBatch) {
this.emit('txProposalsUpdated');
this.store();
}
for (var i = 0; i < mergeInfo.events.length; i++) {
this.emit('txProposalEvent', mergeInfo.events[i]);
}
@ -156,13 +149,13 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
break;
case 'walletReady':
this.sendPublicKeyRing(senderId);
this.sendTxProposals(senderId); // send old
this.sendAllTxProposals(senderId); // send old txps
break;
case 'publicKeyRing':
this._handlePublicKeyRing(senderId, data, isInbound);
break;
case 'txProposals':
this._handleTxProposals(senderId, data, isInbound);
case 'txProposal':
this._handleTxProposal(senderId, data, isInbound);
break;
case 'indexes':
this._handleIndexes(senderId, data, isInbound);
@ -340,9 +333,9 @@ Wallet.prototype.toObj = function() {
Wallet.fromObj = function(o, storage, network, blockchain) {
var opts = JSON.parse(JSON.stringify(o.opts));
opts.publicKeyRing = copay.PublicKeyRing.fromObj(o.publicKeyRing);
opts.txProposals = copay.TxProposals.fromObj(o.txProposals);
opts.privateKey = copay.PrivateKey.fromObj(o.privateKey);
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
opts.txProposals = TxProposals.fromObj(o.txProposals);
opts.privateKey = PrivateKey.fromObj(o.privateKey);
opts.storage = storage;
opts.network = network;
@ -358,25 +351,25 @@ Wallet.prototype.toEncryptedObj = function() {
return this.storage.export(walletObj);
};
Wallet.prototype.sendTxProposals = function(recipients, ntxid) {
this.log('### SENDING txProposals TO:', recipients || 'All', this.txProposals);
var toSend = ntxid ? [ntxid] : this.txProposals.getNtxids();
var last = toSend[toSend];
for (var i in toSend) {
var id = toSend[i];
var lastInBatch = (i == toSend.length - 1);
this.network.send(recipients, {
type: 'txProposals',
txProposals: this.txProposals.toObj(id),
walletId: this.id,
lastInBatch: lastInBatch,
});
Wallet.prototype.sendAllTxProposals = function(recipients) {
var ntxids = this.txProposals.getNtxids();
for (var i in ntxids) {
var ntxid = ntxids[i];
this.sendTxProposal(ntxid, recipients);
}
};
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
preconditions.checkArgument(ntxid);
preconditions.checkState(this.txProposals.txps[ntxid]);
this.log('### SENDING txProposal '+ntxid+' TO:', recipients || 'All', this.txProposals);
this.network.send(recipients, {
type: 'txProposal',
txProposal: this.txProposals.txps[ntxid].toObj(),
walletId: this.id,
});
};
Wallet.prototype.sendWalletReady = function(recipients) {
this.log('### SENDING WalletReady TO:', recipients);
@ -440,14 +433,15 @@ Wallet.prototype.generateAddress = function(isChange, cb) {
Wallet.prototype.getTxProposals = function() {
var ret = [];
var copayers = this.getRegisteredCopayerIds();
for (var k in this.txProposals.txps) {
var i = this.txProposals.getTxProposal(k, copayers);
i.signedByUs = i.signedBy[this.getMyCopayerId()] ? true : false;
i.rejectedByUs = i.rejectedBy[this.getMyCopayerId()] ? true : false;
if (this.totalCopayers - i.rejectCount < this.requiredCopayers)
i.finallyRejected = true;
for (var ntxid in this.txProposals.txps) {
var txp = this.txProposals.getTxProposal(ntxid, copayers);
txp.signedByUs = txp.signedBy[this.getMyCopayerId()] ? true : false;
txp.rejectedByUs = txp.rejectedBy[this.getMyCopayerId()] ? true : false;
if (this.totalCopayers - txp.rejectCount < this.requiredCopayers) {
txp.finallyRejected = true;
}
ret.push(i);
ret.push(txp);
}
return ret;
};
@ -461,7 +455,7 @@ Wallet.prototype.reject = function(ntxid) {
}
txp.rejectedBy[myId] = Date.now();
this.sendTxProposals(null, ntxid);
this.sendTxProposal(ntxid);
this.store();
this.emit('txProposalsUpdated');
};
@ -486,7 +480,7 @@ Wallet.prototype.sign = function(ntxid, cb) {
var ret = false;
if (b.signaturesAdded > before) {
txp.signedBy[myId] = Date.now();
self.sendTxProposals(null, ntxid);
self.sendTxProposal(ntxid);
self.store();
self.emit('txProposalsUpdated');
ret = true;
@ -514,7 +508,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
self.log('BITCOIND txid:', txid);
if (txid) {
self.txProposals.setSent(ntxid, txid);
self.sendTxProposals(null, ntxid);
self.sendTxProposal(ntxid);
self.store();
}
return cb(txid);
@ -641,7 +635,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
var ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
if (ntxid) {
self.sendIndexes();
self.sendTxProposals(null, ntxid);
self.sendTxProposal(ntxid);
self.store();
self.emit('txProposalsUpdated');
}
@ -711,6 +705,71 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
return ntxid;
};
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
var start = self.publicKeyRing.indexes.changeIndex;
self.indexDiscovery(start, true, 20, function(err, changeIndex) {
if (err) return callback(err);
if (changeIndex != -1)
self.publicKeyRing.indexes.changeIndex = changeIndex + 1;
start = self.publicKeyRing.indexes.receiveIndex;
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
if (err) return callback(err);
if (receiveIndex != -1)
self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1;
self.emit('publicKeyRingUpdated');
self.store();
callback();
});
});
}
Wallet.prototype.deriveAddresses = function(index, amout, isChange) {
var ret = new Array(amout);
for(var i = 0; i < amout; i++) {
ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString();
}
return ret;
}
// 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) {
var scanIndex = start;
var lastActive = -1;
var hasActivity = false;
var self = this;
async.doWhilst(
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);
self.blockchain.checkActivity(addresses, function(err, actives){
if (err) throw err;
// Check for new activities in the newlly scanned addresses
var recentActive = actives.reduce(function(r, e, i) {
return e ? scanIndex + i : r;
}, lastActive);
hasActivity = lastActive != recentActive;
lastActive = recentActive;
scanIndex += scanWindow;
next();
});
},
function _while() { return hasActivity; },
function _finnaly(err) {
if (err) return cb(err);
cb(null, lastActive);
}
);
}
Wallet.prototype.disconnect = function() {
this.log('## DISCONNECTING');
this.network.disconnect();

View file

@ -74,6 +74,16 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) {
return w;
};
WalletFactory.prototype.import = function(base64, password, cb) {
var self = this;
var w = self.fromEncryptedObj(base64, password);
w.updateIndexes(function(err) {
if (err) return cb(err);
self.log('Indexes updated');
cb(null, w);
});
}
WalletFactory.prototype.read = function(walletId) {
if (!this._checkRead(walletId))
return false;

View file

@ -1,7 +1,8 @@
'use strict';
var BackupService = function($notification) {
this.notifications = $notification;
var BackupService = function(notification) {
this.notifications = notification;
};
BackupService.prototype.getName = function(wallet) {

View file

@ -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) {
var root = {};
root.getVideoMutedStatus = function(copayer) {
@ -87,7 +87,7 @@ angular.module('copayApp.services')
$rootScope.$digest();
};
$notification.enableHtml5Mode(); // for chrome: if support, enable it
notification.enableHtml5Mode(); // for chrome: if support, enable it
w.on('badMessage', function(peerId) {
$rootScope.$flashMessage = {
@ -126,11 +126,11 @@ angular.module('copayApp.services')
switch (e.type) {
case 'signed':
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
$notification.info('Transaction Update', 'A transaction was signed by ' + user);
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);
notification.info('Transaction Update', 'A transaction was rejected by ' + user);
break;
}
});

View file

@ -1,9 +1,9 @@
'use strict';
angular.module('copayApp.services').
factory('$notification', ['$timeout',function($timeout){
factory('notification', ['$timeout',function($timeout){
var notifications = JSON.parse(localStorage.getItem('$notifications')) || [],
var notifications = JSON.parse(localStorage.getItem('notifications')) || [],
queue = [];
var settings = {
@ -186,7 +186,7 @@ angular.module('copayApp.services').
save: function(){
// Save all the notifications into localStorage
if(settings.localStorage){
localStorage.setItem('$notifications', JSON.stringify(notifications));
localStorage.setItem('notifications', JSON.stringify(notifications));
}
},
@ -201,7 +201,7 @@ angular.module('copayApp.services').
};
}]).
directive('notifications', ['$notification', '$compile', function($notification, $compile){
directive('notifications', function(notification, $compile){
/**
*
* It should also parse the arguments passed to it that specify
@ -245,7 +245,7 @@ angular.module('copayApp.services').
template: html,
link: link,
controller: ['$scope', function NotificationsCtrl( $scope ){
$scope.queue = $notification.getQueue();
$scope.queue = notification.getQueue();
$scope.removeNotification = function(noti){
$scope.queue.splice($scope.queue.indexOf(noti), 1);
@ -254,4 +254,9 @@ angular.module('copayApp.services').
]
};
}]);
});

View file

@ -7,7 +7,9 @@ var Video = function() {
this.mediaConnections = {};
this.localStream = null;
this.onlineSound = new Audio('sound/online.wav');
if (typeof Audio !== 'undefined') {
this.onlineSound = new Audio('sound/online.wav');
}
};
Video.prototype.setOwnPeer = function(peer, wallet, cb) {
@ -64,7 +66,7 @@ Video.prototype._addCall = function(mediaConnection, cb) {
// Wait for stream on the call, then set peer video display
mediaConnection.on('stream', function(stream) {
self.onlineSound.play();
if (self.onlineSound) self.onlineSound.play();
cb(null, peerID, URL.createObjectURL(stream));
});