Fix Conflicts:
.gitignore index.html js/controllers/backup.js js/controllers/sidebar.js
This commit is contained in:
commit
85aa5842f1
74 changed files with 5237 additions and 200 deletions
|
|
@ -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', []);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
26
js/models/core/BuilderMockV0.js
Normal file
26
js/models/core/BuilderMockV0.js
Normal 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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue