diff --git a/cordova/build.sh b/cordova/build.sh
index 40d6916d4..c4be3d920 100755
--- a/cordova/build.sh
+++ b/cordova/build.sh
@@ -106,6 +106,12 @@ if [ ! -d $PROJECT ]; then
cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=bitcoin
checkOK
+ cordova plugin add phonegap-plugin-push@1.2.3
+ checkOK
+
+ cordova plugin add https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin.git --variable URL_SCHEME=bitcoin
+ checkOK
+
cordova plugin add cordova-plugin-inappbrowser
checkOK
diff --git a/package.json b/package.json
index 7a392c4c9..82906f9fa 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"url": "https://github.com/bitpay/copay/issues"
},
"dependencies": {
- "bitcore-wallet-client": "1.2.0",
+ "bitcore-wallet-client": "1.3.0",
"express": "^4.11.2",
"fs": "0.0.2",
"grunt": "^0.4.5",
diff --git a/public/views/preferencesGlobal.html b/public/views/preferencesGlobal.html
index 2fd500373..a2994f6c2 100644
--- a/public/views/preferencesGlobal.html
+++ b/public/views/preferencesGlobal.html
@@ -48,6 +48,10 @@
Use Unconfirmed Funds
+
+
+ Enable notifications
+
diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js
index dfb408a65..cd82e3482 100644
--- a/src/js/controllers/index.js
+++ b/src/js/controllers/index.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, isMobile, addressbookService) {
+angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, pushNotificationsService, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, isMobile, addressbookService) {
var self = this;
var SOFT_CONFIRMATION_LIMIT = 12;
self.isCordova = isCordova;
@@ -11,6 +11,16 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.updatingTxHistory = {};
self.prevState = 'walletHome';
+ document.addEventListener('deviceready', function() {
+ if (isMobile.Android() || isMobile.iOS()) {
+ storageService.getDeviceToken(function(err, token) {
+ $timeout(function() {
+ if (!token) pushNotificationsService.pushNotificationsInit();
+ }, 5000);
+ });
+ }
+ });
+
function strip(number) {
return (parseFloat(number.toPrecision(12)));
};
@@ -21,15 +31,24 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.menu = [{
'title': gettext('Receive'),
- 'icon': {false:'icon-receive', true: 'icon-receive-active'},
+ 'icon': {
+ false: 'icon-receive',
+ true: 'icon-receive-active'
+ },
'link': 'receive'
}, {
'title': gettext('Activity'),
- 'icon': {false:'icon-activity',true: 'icon-activity-active'},
+ 'icon': {
+ false: 'icon-activity',
+ true: 'icon-activity-active'
+ },
'link': 'walletHome'
}, {
'title': gettext('Send'),
- 'icon': {false:'icon-send', true: 'icon-send-active'},
+ 'icon': {
+ false: 'icon-send',
+ true: 'icon-send-active'
+ },
'link': 'send'
}];
@@ -300,7 +319,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r
return cb(null, opts.walletStatus);
else {
self.updateError = false;
- return fc.getStatus({ twoStep : true }, function(err, ret) {
+ return fc.getStatus({
+ twoStep: true
+ }, function(err, ret) {
if (err) {
self.updateError = bwsError.msg(err, gettext('Could not update Wallet'));
} else {
@@ -393,7 +414,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.availableMaxBalance = strip((self.availableBalanceSat - feeToSendMaxSat) * self.satToUnit);
self.feeToSendMaxStr = profileService.formatAmount(feeToSendMaxSat) + ' ' + self.unitName;
}
-
+
if (cb) return cb(self.currentFeePerKb, self.availableMaxBalance, self.feeToSendMaxStr);
});
}
@@ -1263,6 +1284,18 @@ angular.module('copayApp.controllers').controller('indexController', function($r
go.walletHome();
});
+ $rootScope.$on('Local/SubscribeNotifications', function(event) {
+ pushNotificationsService.enableNotifications();
+ });
+
+ $rootScope.$on('Local/UnsubscribeNotifications', function(event, walletId, cb) {
+ pushNotificationsService.unsubscribe(walletId, function(err, response) {
+ if (err) $log.warn('Error: ' + err.code);
+ $log.debug('Unsubscribed: ' + response);
+ return cb();
+ });
+ });
+
self.debouncedUpdate = lodash.throttle(function() {
self.updateAll({
quiet: true
@@ -1464,5 +1497,5 @@ angular.module('copayApp.controllers').controller('indexController', function($r
$rootScope.$apply();
});
});
-
+
});
diff --git a/src/js/controllers/preferencesGlobal.js b/src/js/controllers/preferencesGlobal.js
index 741d37963..36184f34a 100644
--- a/src/js/controllers/preferencesGlobal.js
+++ b/src/js/controllers/preferencesGlobal.js
@@ -1,8 +1,8 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesGlobalController',
- function($scope, $rootScope, $log, configService, uxLanguage) {
-
+ function($scope, $rootScope, $log, configService, isMobile, uxLanguage, pushNotificationsService) {
+
this.init = function() {
var config = configService.getSync();
this.unitName = config.wallet.settings.unitName;
@@ -10,12 +10,16 @@ angular.module('copayApp.controllers').controller('preferencesGlobalController',
this.selectedAlternative = {
name: config.wallet.settings.alternativeName,
isoCode: config.wallet.settings.alternativeIsoCode
- };
+ };
$scope.spendUnconfirmed = config.wallet.spendUnconfirmed;
$scope.glideraEnabled = config.glidera.enabled;
$scope.glideraTestnet = config.glidera.testnet;
+ $scope.notifications = config.notifications ? config.notifications.enabled : true;
};
+ if (isMobile.Android() || isMobile.iOS()) $scope.mobile = true;
+ else $scope.mobile = false;
+
var unwatchSpendUnconfirmed = $scope.$watch('spendUnconfirmed', function(newVal, oldVal) {
if (newVal == oldVal) return;
var opts = {
@@ -29,6 +33,22 @@ angular.module('copayApp.controllers').controller('preferencesGlobalController',
});
});
+ var unwatchNotification = $scope.$watch('notifications', function(newVal, oldVal) {
+ if (newVal == oldVal) return;
+ var opts = {
+ pushNotifications: {
+ enabled: newVal
+ }
+ };
+ configService.set(opts, function(err) {
+ if (opts.pushNotifications.enabled)
+ pushNotificationsService.enableNotifications();
+ else
+ pushNotificationsService.disableNotifications();
+ if (err) $log.debug(err);
+ });
+ });
+
var unwatchGlideraEnabled = $scope.$watch('glideraEnabled', function(newVal, oldVal) {
if (newVal == oldVal) return;
var opts = {
diff --git a/src/js/services/configService.js b/src/js/services/configService.js
index 9f391237b..0d8484ac9 100644
--- a/src/js/services/configService.js
+++ b/src/js/services/configService.js
@@ -41,6 +41,21 @@ angular.module('copayApp.services').factory('configService', function(storageSer
rates: {
url: 'https://insight.bitpay.com:443/api/rates',
},
+
+ pushNotifications: {
+ enabled: true,
+ config: {
+ android: {
+ senderID: '1036948132229',
+ },
+ ios: {
+ alert: 'true',
+ badge: 'true',
+ sound: 'true',
+ },
+ windows: {},
+ }
+ },
};
var configCache = null;
@@ -70,7 +85,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode;
}
if (!configCache.glidera) {
- configCache.glidera = defaultConfig.glidera;
+ configCache.glidera = defaultConfig.glidera;
}
} else {
diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js
index 8c4a284eb..1d1724938 100644
--- a/src/js/services/profileService.js
+++ b/src/js/services/profileService.js
@@ -118,7 +118,7 @@ angular.module('copayApp.services')
root.bindProfile = function(profile, cb) {
root.profile = profile;
-
+
configService.get(function(err) {
$log.debug('Preferences read');
if (err) return cb(err);
@@ -128,20 +128,19 @@ angular.module('copayApp.services')
root._setFocus(focusedWalletId, function() {
$rootScope.$emit('Local/ProfileBound');
root.isDisclaimerAccepted(function(val) {
- if (!val) {
+ if (!val) {
return cb(new Error('NONAGREEDDISCLAIMER: Non agreed disclaimer'));
- }
- else {
+ } else {
return cb();
}
});
});
});
});
-
+
};
- root.loadAndBindProfile = function(cb) {
+ root.loadAndBindProfile = function(cb) {
storageService.getProfile(function(err, profile) {
if (err) {
$rootScope.$emit('Local/DeviceError', err);
@@ -305,34 +304,38 @@ angular.module('copayApp.services')
root.deleteWalletFC = function(opts, cb) {
var fc = root.focusedClient;
var walletId = fc.credentials.walletId;
- $log.debug('Deleting Wallet:', fc.credentials.walletName);
- fc.removeAllListeners();
- root.profile.credentials = lodash.reject(root.profile.credentials, {
- walletId: walletId
- });
+ $rootScope.$emit('Local/UnsubscribeNotifications', walletId, function() {
- delete root.walletClients[walletId];
- root.focusedClient = null;
+ $log.debug('Deleting Wallet:', fc.credentials.walletName);
- storageService.clearLastAddress(walletId, function(err) {
- if (err) $log.warn(err);
- });
+ fc.removeAllListeners();
+ root.profile.credentials = lodash.reject(root.profile.credentials, {
+ walletId: walletId
+ });
- storageService.removeTxHistory(walletId, function(err) {
- if (err) $log.warn(err);
- });
+ delete root.walletClients[walletId];
+ root.focusedClient = null;
- storageService.clearBackupFlag(walletId, function(err) {
- if (err) $log.warn(err);
- });
+ storageService.clearLastAddress(walletId, function(err) {
+ if (err) $log.warn(err);
+ });
- $timeout(function() {
- root.setWalletClients();
- root.setAndStoreFocus(null, function() {
- storageService.storeProfile(root.profile, function(err) {
- if (err) return cb(err);
- return cb();
+ storageService.removeTxHistory(walletId, function(err) {
+ if (err) $log.warn(err);
+ });
+
+ storageService.clearBackupFlag(walletId, function(err) {
+ if (err) $log.warn(err);
+ });
+
+ $timeout(function() {
+ root.setWalletClients();
+ root.setAndStoreFocus(null, function() {
+ storageService.storeProfile(root.profile, function(err) {
+ if (err) return cb(err);
+ return cb();
+ });
});
});
});
@@ -368,6 +371,7 @@ angular.module('copayApp.services')
return cb(gettext('Wallet already in Copay' + ": ") + w.walletName);
}
+ var config = configService.getSync();
var defaults = configService.getDefaults();
var bwsFor = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
@@ -398,6 +402,8 @@ angular.module('copayApp.services')
handleImport(function() {
root.setAndStoreFocus(walletId, function() {
storageService.storeProfile(root.profile, function(err) {
+ if (config.pushNotifications.enabled)
+ $rootScope.$emit('Local/SubscribeNotifications');
return cb(err, walletId);
});
});
@@ -542,16 +548,14 @@ angular.module('copayApp.services')
if (err) $log.error(err);
return cb(true);
});
- }
- else {
+ } else {
return cb();
}
});
- }
- else {
+ } else {
return cb();
}
- });
+ });
};
root.importLegacyWallet = function(username, password, blob, cb) {
@@ -664,7 +668,8 @@ angular.module('copayApp.services')
name: config.aliasFor[c.walletId] || c.walletName,
id: c.walletId,
network: c.network,
- color: config.colorFor[c.walletId] || '#4A90E2'
+ color: config.colorFor[c.walletId] || '#4A90E2',
+ copayerId: c.copayerId
};
});
ret = lodash.filter(ret, function(w) {
diff --git a/src/js/services/pushNotificationsService.js b/src/js/services/pushNotificationsService.js
new file mode 100644
index 000000000..ca4b72370
--- /dev/null
+++ b/src/js/services/pushNotificationsService.js
@@ -0,0 +1,74 @@
+'use strict';
+angular.module('copayApp.services')
+ .factory('pushNotificationsService', function($http, $log, isMobile, profileService, storageService, configService, lodash) {
+ var root = {};
+ var defaults = configService.getDefaults();
+
+ root.pushNotificationsInit = function() {
+ var push = PushNotification.init(defaults.pushNotifications.config);
+
+ push.on('registration', function(data) {
+ $log.debug('Starting push notification registration');
+ storageService.setDeviceToken(data.registrationId, function() {
+ root.enableNotifications();
+ });
+ });
+
+ push.on('notification', function(data) {
+ $log.debug('Push notification event: ', data.message);
+ /* data.message,
+ data.title,
+ data.count,
+ data.sound,
+ data.image,
+ data.additionalData
+ */
+ });
+
+ push.on('error', function(e) {
+ $log.warn('Error trying to push notifications: ', e);
+ });
+ };
+
+ root.enableNotifications = function() {
+ storageService.getDeviceToken(function(err, token) {
+ lodash.forEach(profileService.getWallets('testnet'), function(wallet) {
+ var opts = {};
+ opts.type = isMobile.iOS() ? "ios" : isMobile.Android() ? "android" : null;
+ opts.token = token;
+ root.subscribe(opts, wallet.id, function(err, response) {
+ if (err) $log.warn('Error: ' + err.code);
+ $log.debug('Suscribed: ' + JSON.stringify(response));
+ });
+ });
+ });
+ }
+
+ root.disableNotifications = function() {
+ lodash.forEach(profileService.getWallets('testnet'), function(wallet) {
+ root.unsubscribe(wallet.id, function(err, response) {
+ if (err) $log.warn('Error: ' + err.code);
+ $log.debug('Unsubscribed: ' + response);
+ });
+ });
+ }
+
+ root.subscribe = function(opts, walletId, cb) {
+ var walletClient = profileService.getClient(walletId);
+ walletClient.pushNotificationsSubscribe(opts, function(err, resp) {
+ if (err) return cb(err);
+ return cb(null, resp);
+ });
+ }
+
+ root.unsubscribe = function(walletId, cb) {
+ var walletClient = profileService.getClient(walletId);
+ walletClient.pushNotificationsUnsubscribe(function(err, resp) {
+ if (err) return cb(err);
+ return cb(null, resp);
+ });
+ }
+
+ return root;
+
+ });
diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js
index 3d00f82a9..940a4943d 100644
--- a/src/js/services/storageService.js
+++ b/src/js/services/storageService.js
@@ -231,6 +231,14 @@ angular.module('copayApp.services')
storage.get('addressbook-' + network, cb);
};
+ root.setDeviceToken = function(token, cb) {
+ storage.set('token', token, cb);
+ }
+
+ root.getDeviceToken = function(cb) {
+ storage.get('token', cb);
+ }
+
root.removeAddressbook = function(network, cb) {
storage.remove('addressbook-' + network, cb);
};