diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index ddc16260d7dc3129c58f7e80b74353d5086ceeb5..211ab141b1b8db62732af9c5905a9de9a96ee9fa 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -265,7 +265,6 @@ "BTN_MEMBERSHIP_RENEW_DOTS": "Renouveler l'adhésion...", "BTN_MEMBERSHIP_OUT_DOTS": "Arrêter l'adhésion...", "BTN_SEND_IDENTITY_DOTS": "Publier son identitié...", - "BTN_REVOKE": "Revoquer<span class='hidden-xs hidden-sm'> définitivement</span> cette identité...", "BTN_SHOW_DETAILS": "Afficher les infos techniques", "NEW": { "TITLE": "Inscription", @@ -295,6 +294,15 @@ "POPUP_REGISTER": { "TITLE": "Choisissez un pseudonyme", "HELP": "Un pseudonyme est obligatoire pour devenir membre." + }, + "SECURITY":{ + "TITLE": "Compte et sécurité", + "SAVE_KEYS": "Sauvegarder vos identifiants", + "DOWNLOAD_REVOKE": "Sauvegarder un fichier de revocation", + "REVOKE" : "Revoquer cette identité", + "DEFINITELY_REVOKE" : "Revoquer définitivement cette identité", + "REVOCATION": "Révocation" + } }, "TRANSFER": { diff --git a/www/index.html b/www/index.html index dfa57764cc6758776545eb964cbaa7f26f06f11e..ba3ea28f1f11bbeeec4643bd36d7e39e912bc8da 100644 --- a/www/index.html +++ b/www/index.html @@ -41,6 +41,7 @@ <script src="lib/ionic/js/angular/angular-cache.js"></script> <script src="lib/ionic/js/angular/angular-screenmatch.min.js"></script> <script src="lib/ionic/js/angular/angular-bind-notifier.min.js"></script> + <script src="lib/ionic/js/angular/angular-file-saver.bundle.js"></script> <!-- crypto libs --> <script src="js/vendor/scrypt-em.js" async></script> diff --git a/www/js/app.js b/www/js/app.js index 519fdede5b7b322199d95797c6305702121eff39..f1e9ddab1843a1dc1eae04c5a0b62726cd644dff 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -4,7 +4,7 @@ // 'starter' is the name of this angular module example (also set in a <body> attribute in index.html) // the 2nd parameter is an array of 'requires' // 'starter.controllers' is found in controllers.js -angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht.translate', +angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngFileSaver', 'pascalprecht.translate', 'ngApi', 'angular-cache', 'angular.screenmatch', 'angular.bind.notifier', // removeIf(device) // endRemoveIf(device) diff --git a/www/js/controllers/wallet-controllers.js b/www/js/controllers/wallet-controllers.js index e917f8c2685968c9d236b6a9896eac44e482c8be..225a0e592a84930a7fb2076c6fe1184e43bb9568 100644 --- a/www/js/controllers/wallet-controllers.js +++ b/www/js/controllers/wallet-controllers.js @@ -29,10 +29,12 @@ angular.module('cesium.wallet.controllers', ['cesium.services', 'cesium.currency .controller('WalletCtrl', WalletController) .controller('WalletTxErrorCtrl', WalletTxErrorController) + + .controller('WalletSecurityModalCtrl', WalletSecurityModalController) ; function WalletController($scope, $rootScope, $q, $ionicPopup, $timeout, $state, $filter, - UIUtils, csWallet, $translate, $ionicPopover, Modals, csSettings, BMA) { + UIUtils, csWallet, $translate, $ionicPopover, Modals, csSettings, BMA, ModalUtils) { 'ngInject'; $scope.hasCredit = false; @@ -295,49 +297,6 @@ function WalletController($scope, $rootScope, $q, $ionicPopup, $timeout, $state, }); }; - /** - * Revoke identity - */ - $scope.revokeIdentity = function(confirm, confirmAgain) { - $scope.hideActionsPopover(); - - if ($rootScope.walletData.requirements.needSelf) { - return UIUtils.alert.error("ERROR.ONLY_SELF_CAN_EXECUTE_THIS_ACTION"); - } - if (!confirm) { - return UIUtils.alert.confirm("CONFIRM.REVOKE_IDENTITY", 'CONFIRM.POPUP_WARNING_TITLE', { - cssClass: 'warning', - okText: 'COMMON.BTN_CONTINUE', - okType: 'button-assertive' - }) - .then(function(confirm) { - if (confirm) $scope.revokeIdentity(true); // loop with confirm - }); - } - if (!confirmAgain) { - return UIUtils.alert.confirm("CONFIRM.REVOKE_IDENTITY_2", 'CONFIRM.POPUP_TITLE', { - cssClass: 'warning', - okText: 'COMMON.BTN_YES_CONTINUE', - okType: 'button-assertive' - }) - .then(function(confirm) { - if (confirm) $scope.revokeIdentity(true, true); // loop with all confirmation - }); - } - - return UIUtils.loading.show() - .then(function() { - return csWallet.revoke(); - }) - .then(function(){ - UIUtils.toast.show("INFO.REVOCATION_SENT"); - $scope.updateView(); - return UIUtils.loading.hide(); - }) - .catch(function(err) { - UIUtils.onError('ERROR.REVOCATION_FAILED')(err); - }); - }; /** * Fix identity (e.g. when identity expired) @@ -593,15 +552,97 @@ function WalletController($scope, $rootScope, $q, $ionicPopup, $timeout, $state, }); }; + $scope.showSecurityModal = function(){ + $scope.hideActionsPopover(); + return ModalUtils.show('templates/wallet/modal_security.html', 'WalletSecurityModalCtrl'); + }; } +function WalletSecurityModalController($scope, $state, UIUtils, Modals, csWallet){ + $scope.slides = { + slider: null, + options: { + loop: false, + effect: 'slide', + speed: 500 + } + }; + $scope.isLastSlide = false; + $scope.smallscreen = UIUtils.screen.isSmall(); + + $scope.slidePrev = function() { + $scope.slides.slider.unlockSwipes(); + $scope.slides.slider.slidePrev(); + $scope.slides.slider.lockSwipes(); + $scope.isLastSlide = false; + }; + + $scope.slideNext = function() { + $scope.slides.slider.unlockSwipes(); + $scope.slides.slider.slideNext(); + $scope.slides.slider.lockSwipes(); + $scope.isLastSlide = $scope.slides.slider.activeIndex === 5; + }; + + + /** + * Download revocation file + */ + $scope.downloadRevokeFile = function(){ + csWallet.downloadRevocation(); + }; + + /** + * Revoke identity + */ + $scope.revokeIdentity = function(confirm, confirmAgain) { + if ($rootScope.walletData.requirements.needSelf) { + return UIUtils.alert.error("ERROR.ONLY_SELF_CAN_EXECUTE_THIS_ACTION"); + } + if (!confirm) { + return UIUtils.alert.confirm("CONFIRM.REVOKE_IDENTITY", 'CONFIRM.POPUP_WARNING_TITLE', { + cssClass: 'warning', + okText: 'COMMON.BTN_CONTINUE', + okType: 'button-assertive' + }) + .then(function(confirm) { + if (confirm) $scope.revokeIdentity(true); // loop with confirm + }); + } + if (!confirmAgain) { + return UIUtils.alert.confirm("CONFIRM.REVOKE_IDENTITY_2", 'CONFIRM.POPUP_TITLE', { + cssClass: 'warning', + okText: 'COMMON.BTN_YES_CONTINUE', + okType: 'button-assertive' + }) + .then(function(confirm) { + if (confirm) $scope.revokeIdentity(true, true); // loop with all confirmation + }); + } + + return UIUtils.loading.show() + .then(function() { + return csWallet.revoke(); + }) + .then(function(){ + UIUtils.toast.show("INFO.REVOCATION_SENT"); + $scope.updateView(); + return UIUtils.loading.hide(); + }) + .catch(function(err) { + UIUtils.onError('ERROR.REVOCATION_FAILED')(err); + }); + }; + + +} function WalletTxErrorController($scope, $timeout, UIUtils, csWallet) { 'ngInject'; - $scope.$on('$ionicView.enter', function(e) { + $scope.$on('$ionicView.enter', function (e) { $scope.loadWallet() - .then(function() { + .then(function () { $scope.updateView(); $scope.showFab('fab-redo-transfer'); UIUtils.loading.hide(); @@ -609,9 +650,9 @@ function WalletTxErrorController($scope, $timeout, UIUtils, csWallet) { }); // Update view - $scope.updateView = function() { + $scope.updateView = function () { // Set Motion - $timeout(function() { + $timeout(function () { UIUtils.motion.fadeSlideInRight(); // Set Ink UIUtils.ink({selector: '.item'}); @@ -619,25 +660,26 @@ function WalletTxErrorController($scope, $timeout, UIUtils, csWallet) { }; // Updating wallet data - $scope.doUpdate = function() { + $scope.doUpdate = function () { UIUtils.loading.show(); csWallet.refreshData() - .then(function() { - $scope.updateView(); - UIUtils.loading.hide(); - }) - .catch(UIUtils.onError('ERROR.REFRESH_WALLET_DATA')); + .then(function () { + $scope.updateView(); + UIUtils.loading.hide(); + }) + .catch(UIUtils.onError('ERROR.REFRESH_WALLET_DATA')); }; - $scope.filterPositive = function(prop){ - return function(item){ + $scope.filterPositive = function (prop) { + return function (item) { return item[prop] > 0; }; }; - $scope.filterNegative = function(prop){ - return function(item){ + $scope.filterNegative = function (prop) { + return function (item) { return item[prop] < 0; }; }; + } diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js index 80797214e7452fa5d4f91d619d6d834a09614ece..ce188bb4ef5e9aba3d4b47d4c3316bb8a880d1d2 100644 --- a/www/js/services/network-services.js +++ b/www/js/services/network-services.js @@ -350,12 +350,11 @@ angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.se score += (1000000000 * (peer.hasEndpoint('ES_USER_API')? 1 : 0)); } else if (data.sort.type === 'difficulty'){ - score += (1000000000 * (peer.level ? peer.level : 0)); + score += (1000000000 * (peer.difficulty ? peer.difficulty : 0)); } else if (data.sort.type === 'current_block'){ score += (1000000000 * (peer.currentNumber ? peer.currentNumber : 0)); } - score += (100000000 * (peer.online ? 1 : 0)); score += (10000000 * (peer.hasMainConsensusBlock ? 1 : 0)); score += (1000 * (peer.hasConsensusBlock ? currents[peer.buid] : 0)); diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 7a94bcc018fe75051adc70cee9814f09c1ab5534..e448aaab56c1dbec4b69e628fbbfd8e8280bc654 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -3,7 +3,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser 'cesium.settings.services']) -.factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, Api, localStorage, CryptoUtils, BMA, csConfig, csSettings) { +.factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, Api, localStorage, + CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob) { 'ngInject'; factory = function(id) { @@ -1495,6 +1496,14 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser ; }, + downloadRevocation = function(){ + return getRevocationDocument() + .then(function(revocation) { + var revocationFile = new Blob([revocation], {type: 'text/plain; charset=utf-8'}); + FileSaver.saveAs(revocationFile, 'revocation.txt'); + }); + }, + cleanEventsByContext = function(context){ data.events = data.events.reduce(function(res, event) { if (event.context && event.context == context) return res; @@ -1591,6 +1600,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser transfer: transfer, self: self, revoke: revoke, + downloadRevocation: downloadRevocation, membership: { inside: membership(true), out: membership(false) diff --git a/www/lib/ionic/js/angular/angular-file-saver.bundle.js b/www/lib/ionic/js/angular/angular-file-saver.bundle.js new file mode 100644 index 0000000000000000000000000000000000000000..f0ecd84d3e770b73a12b98b5e5d16ddeb9003d92 --- /dev/null +++ b/www/lib/ionic/js/angular/angular-file-saver.bundle.js @@ -0,0 +1,602 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else { + var a = factory(); + for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; + } +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + /* + * + * A AngularJS module that implements the HTML5 W3C saveAs() in browsers that + * do not natively support it + * + * (c) 2015 Philipp Alferov + * License: MIT + * + */ + + module.exports = 'ngFileSaver'; + + angular.module('ngFileSaver', []) + .factory('FileSaver', ['Blob', 'SaveAs', 'FileSaverUtils', __webpack_require__(1)]) + .factory('FileSaverUtils', [__webpack_require__(2)]) + .factory('Blob', ['$window', __webpack_require__(3)]) + .factory('SaveAs', [__webpack_require__(5)]); + + +/***/ }, +/* 1 */ +/***/ function(module, exports) { + + 'use strict'; + + module.exports = function FileSaver(Blob, SaveAs, FileSaverUtils) { + + function save(blob, filename, disableAutoBOM) { + try { + SaveAs(blob, filename, disableAutoBOM); + } catch(err) { + FileSaverUtils.handleErrors(err.message); + } + } + + return { + + /** + * saveAs + * Immediately starts saving a file, returns undefined. + * + * @name saveAs + * @function + * @param {Blob} data A Blob instance + * @param {Object} filename Custom filename (extension is optional) + * @param {Boolean} disableAutoBOM Disable automatically provided Unicode + * text encoding hints + * + * @return {Undefined} + */ + + saveAs: function(data, filename, disableAutoBOM) { + + if (!FileSaverUtils.isBlobInstance(data)) { + FileSaverUtils.handleErrors('Data argument should be a blob instance'); + } + + if (!FileSaverUtils.isString(filename)) { + FileSaverUtils.handleErrors('Filename argument should be a string'); + } + + return save(data, filename, disableAutoBOM); + } + }; + }; + + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + 'use strict'; + + module.exports = function FileSaverUtils() { + return { + handleErrors: function(msg) { + throw new Error(msg); + }, + isString: function(obj) { + return typeof obj === 'string' || obj instanceof String; + }, + isUndefined: function(obj) { + return typeof obj === 'undefined'; + }, + isBlobInstance: function(obj) { + return obj instanceof Blob; + } + }; + }; + + +/***/ }, +/* 3 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + __webpack_require__(4); + + module.exports = function Blob($window) { + return $window.Blob; + }; + + +/***/ }, +/* 4 */ +/***/ function(module, exports) { + + /* Blob.js + * A Blob implementation. + * 2014-07-24 + * + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/dsamarin + * License: MIT + * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md + */ + + /*global self, unescape */ + /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + + /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ + + (function (view) { + "use strict"; + + view.URL = view.URL || view.webkitURL; + + if (view.Blob && view.URL) { + try { + new Blob; + return; + } catch (e) {} + } + + // Internally we use a BlobBuilder implementation to base Blob off of + // in order to support older browsers that only have BlobBuilder + var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { + var + get_class = function(object) { + return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; + } + , FakeBlobBuilder = function BlobBuilder() { + this.data = []; + } + , FakeBlob = function Blob(data, type, encoding) { + this.data = data; + this.size = data.length; + this.type = type; + this.encoding = encoding; + } + , FBB_proto = FakeBlobBuilder.prototype + , FB_proto = FakeBlob.prototype + , FileReaderSync = view.FileReaderSync + , FileException = function(type) { + this.code = this[this.name = type]; + } + , file_ex_codes = ( + "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" + ).split(" ") + , file_ex_code = file_ex_codes.length + , real_URL = view.URL || view.webkitURL || view + , real_create_object_URL = real_URL.createObjectURL + , real_revoke_object_URL = real_URL.revokeObjectURL + , URL = real_URL + , btoa = view.btoa + , atob = view.atob + + , ArrayBuffer = view.ArrayBuffer + , Uint8Array = view.Uint8Array + + , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ + ; + FakeBlob.fake = FB_proto.fake = true; + while (file_ex_code--) { + FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; + } + // Polyfill URL + if (!real_URL.createObjectURL) { + URL = view.URL = function(uri) { + var + uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") + , uri_origin + ; + uri_info.href = uri; + if (!("origin" in uri_info)) { + if (uri_info.protocol.toLowerCase() === "data:") { + uri_info.origin = null; + } else { + uri_origin = uri.match(origin); + uri_info.origin = uri_origin && uri_origin[1]; + } + } + return uri_info; + }; + } + URL.createObjectURL = function(blob) { + var + type = blob.type + , data_URI_header + ; + if (type === null) { + type = "application/octet-stream"; + } + if (blob instanceof FakeBlob) { + data_URI_header = "data:" + type; + if (blob.encoding === "base64") { + return data_URI_header + ";base64," + blob.data; + } else if (blob.encoding === "URI") { + return data_URI_header + "," + decodeURIComponent(blob.data); + } if (btoa) { + return data_URI_header + ";base64," + btoa(blob.data); + } else { + return data_URI_header + "," + encodeURIComponent(blob.data); + } + } else if (real_create_object_URL) { + return real_create_object_URL.call(real_URL, blob); + } + }; + URL.revokeObjectURL = function(object_URL) { + if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { + real_revoke_object_URL.call(real_URL, object_URL); + } + }; + FBB_proto.append = function(data/*, endings*/) { + var bb = this.data; + // decode data to a binary string + if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { + var + str = "" + , buf = new Uint8Array(data) + , i = 0 + , buf_len = buf.length + ; + for (; i < buf_len; i++) { + str += String.fromCharCode(buf[i]); + } + bb.push(str); + } else if (get_class(data) === "Blob" || get_class(data) === "File") { + if (FileReaderSync) { + var fr = new FileReaderSync; + bb.push(fr.readAsBinaryString(data)); + } else { + // async FileReader won't work as BlobBuilder is sync + throw new FileException("NOT_READABLE_ERR"); + } + } else if (data instanceof FakeBlob) { + if (data.encoding === "base64" && atob) { + bb.push(atob(data.data)); + } else if (data.encoding === "URI") { + bb.push(decodeURIComponent(data.data)); + } else if (data.encoding === "raw") { + bb.push(data.data); + } + } else { + if (typeof data !== "string") { + data += ""; // convert unsupported types to strings + } + // decode UTF-16 to binary string + bb.push(unescape(encodeURIComponent(data))); + } + }; + FBB_proto.getBlob = function(type) { + if (!arguments.length) { + type = null; + } + return new FakeBlob(this.data.join(""), type, "raw"); + }; + FBB_proto.toString = function() { + return "[object BlobBuilder]"; + }; + FB_proto.slice = function(start, end, type) { + var args = arguments.length; + if (args < 3) { + type = null; + } + return new FakeBlob( + this.data.slice(start, args > 1 ? end : this.data.length) + , type + , this.encoding + ); + }; + FB_proto.toString = function() { + return "[object Blob]"; + }; + FB_proto.close = function() { + this.size = 0; + delete this.data; + }; + return FakeBlobBuilder; + }(view)); + + view.Blob = function(blobParts, options) { + var type = options ? (options.type || "") : ""; + var builder = new BlobBuilder(); + if (blobParts) { + for (var i = 0, len = blobParts.length; i < len; i++) { + if (Uint8Array && blobParts[i] instanceof Uint8Array) { + builder.append(blobParts[i].buffer); + } + else { + builder.append(blobParts[i]); + } + } + } + var blob = builder.getBlob(type); + if (!blob.slice && blob.webkitSlice) { + blob.slice = blob.webkitSlice; + } + return blob; + }; + + var getPrototypeOf = Object.getPrototypeOf || function(object) { + return object.__proto__; + }; + view.Blob.prototype = getPrototypeOf(new view.Blob()); + }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); + + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + module.exports = function SaveAs() { + return __webpack_require__(6).saveAs || function() {}; + }; + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_RESULT__;/* FileSaver.js + * A saveAs() FileSaver implementation. + * 1.3.2 + * 2016-06-16 18:25:19 + * + * By Eli Grey, http://eligrey.com + * License: MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ + + /*global self */ + /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + + /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + + var saveAs = saveAs || (function(view) { + "use strict"; + // IE <10 is explicitly unsupported + if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + return; + } + var + doc = view.document + // only get URL when necessary in case Blob.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = "download" in save_link + , click = function(node) { + var event = new MouseEvent("click"); + node.dispatchEvent(event); + } + , is_safari = /constructor/i.test(view.HTMLElement) || view.safari + , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) + , throw_outside = function(ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + , arbitrary_revoke_timeout = 1000 * 40 // in ms + , revoke = function(file) { + var revoker = function() { + if (typeof file === "string") { // file is an object URL + get_URL().revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + }; + setTimeout(revoker, arbitrary_revoke_timeout); + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , auto_bom = function(blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + } + return blob; + } + , FileSaver = function(blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob); + } + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , force = type === force_saveable_type + , object_url + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader(); + reader.onloadend = function() { + var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + var popup = view.open(url, '_blank'); + if(!popup) view.location.href = url; + url=undefined; // release reference before dispatching + filesaver.readyState = filesaver.DONE; + dispatch_all(); + }; + reader.readAsDataURL(blob); + filesaver.readyState = filesaver.INIT; + return; + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob); + } + if (force) { + view.location.href = object_url; + } else { + var opened = view.open(object_url, "_blank"); + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url; + } + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + revoke(object_url); + } + ; + filesaver.readyState = filesaver.INIT; + + if (can_use_save_link) { + object_url = get_URL().createObjectURL(blob); + setTimeout(function() { + save_link.href = object_url; + save_link.download = name; + click(save_link); + dispatch_all(); + revoke(object_url); + filesaver.readyState = filesaver.DONE; + }); + return; + } + + fs_error(); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || "download", no_auto_bom); + } + ; + // IE 10+ (native saveAs) + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function(blob, name, no_auto_bom) { + name = name || blob.name || "download"; + + if (!no_auto_bom) { + blob = auto_bom(blob); + } + return navigator.msSaveOrOpenBlob(blob, name); + }; + } + + FS_proto.abort = function(){}; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + return saveAs; + }( + typeof self !== "undefined" && self + || typeof window !== "undefined" && window + || this.content + )); + // `self` is undefined in Firefox for Android content script context + // while `this` is nsIContentFrameMessageManager + // with an attribute `content` that corresponds to the window + + if (typeof module !== "undefined" && module.exports) { + module.exports.saveAs = saveAs; + } else if (("function" !== "undefined" && __webpack_require__(7) !== null) && (__webpack_require__(8) !== null)) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { + return saveAs; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports) { + + module.exports = function() { throw new Error("define cannot be used indirect"); }; + + +/***/ }, +/* 8 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; + + /* WEBPACK VAR INJECTION */}.call(exports, {})) + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/www/templates/wallet/modal_security.html b/www/templates/wallet/modal_security.html new file mode 100644 index 0000000000000000000000000000000000000000..e6fce021b19b5e3115b90810b76bc2cf6c289da7 --- /dev/null +++ b/www/templates/wallet/modal_security.html @@ -0,0 +1,358 @@ +<ion-modal-view class="modal-full-height"> + + <ion-header-bar class="bar-positive"> + + <button class="button button-clear visible-xs" + ng-if="!slides.slider.activeIndex" + ng-click="closeModal()" translate>COMMON.BTN_CANCEL + </button> + <button class="button button-icon button-clear icon ion-ios-arrow-back buttons header-item" + ng-click="slidePrev()" + ng-if="slides.slider.activeIndex"> + </button> + + <h1 class="title" translate>ACCOUNT.SECURITY.TITLE</h1> + + <button class="button button-clear icon-right visible-xs" + ng-if="!isLastSlide && slides.slider.activeIndex > 1" + ng-click="slideNext()"> + <span translate>COMMON.BTN_NEXT</span> + <i class="icon ion-ios-arrow-right"></i> + </button> + </ion-header-bar> + + + <ion-slides options="slides.options" slider="slides.slider"> + + <!-- STEP 1 --> + <ion-slide-page> + <ion-content class="has-header padding"> + <div class="list"> + <a class="item card item-icon-right stable-bg padding ink" + ng-click=""> + <i class="icon ion-ios-arrow-right"></i> + <span ng-bind-html="'ACCOUNT.SECURITY.SAVE_KEYS' | translate"></span> + </a> + <a class="item card item-icon-right stable-bg padding ink" + ng-click=""> + <span ng-bind-html="'ACCOUNT.SECURITY.REVOCATION' | translate"></span> + <h4 class="gray"></h4> + <i class="icon dark ion-ios-arrow-right"></i> + </a> + </div> + + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + </div> + </ion-content> + </ion-slide-page> + + <!-- STEP 2: revocation --> + <ion-slide-page> + <ion-content class="has-header padding"> + <h3 translate>ACCOUNT.SECURITY.REVOCATION</h3> + <div class="list"> + <a class="item card item-icon-right stable-bg padding ink" + ng-click="downloadRevokeFile()"> + <i class="icon ion-android-archive"></i> + <span ng-bind-html="'ACCOUNT.SECURITY.DOWNLOAD_REVOKE' | translate"></span> + </a> + <a class="item card item-icon-right stable-bg padding ink" + ng-click="revokeIdentity()"> + <i class="icon ion-minus-circled assertive"></i> + <span ng-bind-html="'ACCOUNT.SECURITY.DEFINITELY_REVOKE' | translate"></span> + </a> + </div> + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + </div> + </ion-content> + </ion-slide-page> + + <!-- STEP 3: save keys --> + <ion-slide-page> + <ion-content class="has-header" scroll="false"> + <form name="saltForm" novalidate="" ng-submit="doNext('saltForm')"> + + <div class="list" + ng-init="setForm(saltForm, 'saltForm')"> + + <div class="item item-text-wrap text-center padding hidden-xs" > + <a class="pull-right icon-help" ng-click="showHelpModal('join-salt')"></a> + <span translate>ACCOUNT.SECURITY.SAVE_KEYS</span> + </div> + + <!-- salt --> + <div class="item item-input" + ng-class="{ 'item-input-error': saltForm.$submitted && saltForm.username.$invalid}"> + <span class="input-label" translate>LOGIN.SALT</span> + <input ng-if="!showUsername" + name="username" type="password" placeholder="{{'LOGIN.SALT_HELP' | translate}}" + ng-change="formDataChanged()" + ng-model="formData.username" + ng-minlength="8" + required> + <input ng-if="showUsername" + name="username" type="text" placeholder="{{'LOGIN.SALT_HELP' | translate}}" + ng-change="formDataChanged()" + ng-model="formData.username" + ng-minlength="8" + required> + </div> + <div class="form-errors" + ng-show="saltForm.$submitted && saltForm.username.$error" + ng-messages="saltForm.username.$error"> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 8}"></span> + </div> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + </div> + + <!-- confirm salt --> + <div class="item item-input" + ng-class="{ 'item-input-error': saltForm.$submitted && saltForm.confirmSalt.$invalid}"> + <span class="input-label pull-right" translate>ACCOUNT.NEW.SALT_CONFIRM</span> + <input ng-if="!showUsername" + name="confirmUsername" type="password" + placeholder="{{'ACCOUNT.NEW.SALT_CONFIRM_HELP' | translate}}" + ng-model="formData.confirmUsername" + compare-to="formData.username"> + <input ng-if="showUsername" + name="confirmUsername" type="text" + placeholder="{{'ACCOUNT.NEW.SALT_CONFIRM_HELP' | translate}}" + ng-model="formData.confirmUsername" + compare-to="formData.username"> + </div> + <div class="form-errors" + ng-show="saltForm.$submitted && saltForm.confirmUsername.$error" + ng-messages="saltForm.confirmUsername.$error"> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT"></span> + </div> + <div class="form-error" ng-message="maxlength"> + <span translate="ERROR.FIELD_TOO_LONG"></span> + </div> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + <div class="form-error" ng-message="compareTo"> + <span translate="ERROR.SALT_NOT_CONFIRMED"></span> + </div> + </div> + + <!-- Show values --> + <div class="item item-toggle dark"> + <span translate>COMMON.SHOW_VALUES</span> + <label class="toggle toggle-royal"> + <input type="checkbox" ng-model="showUsername"> + <div class="track"> + <div class="handle"></div> + </div> + </label> + </div> + + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + <button class="button button-calm icon-right ion-chevron-right ink" type="submit" translate> + COMMON.BTN_NEXT + <i class="icon ion-arrow-right-a"></i> + </button> + </div> + </div> + </form> + </ion-content> + </ion-slide-page> + + <!-- STEP 4: password--> + <ion-slide-page> + <ion-content class="has-header" scroll="false"> + <form name="passwordForm" novalidate="" ng-submit="doNext('passwordForm')"> + + <div class="item item-text-wrap text-center padding hidden-xs" > + <a class="pull-right icon-help" ng-click="showHelpModal('join-password')"></a> + <span translate>ACCOUNT.NEW.PASSWORD_WARNING</span> + </div> + + <div class="list" + ng-init="setForm(passwordForm, 'passwordForm')"> + + <!-- password --> + <div class="item item-input" + ng-class="{ 'item-input-error': passwordForm.$submitted && passwordForm.password.$invalid}"> + <span class="input-label" translate>LOGIN.PASSWORD</span> + <input ng-if="!showPassword" + name="password" type="password" placeholder="{{'LOGIN.PASSWORD_HELP' | translate}}" + ng-model="formData.password" + ng-change="formDataChanged()" + ng-minlength="8" + required> + <input ng-if="showPassword" + name="text" type="text" placeholder="{{'LOGIN.PASSWORD_HELP' | translate}}" + ng-model="formData.password" + ng-change="formDataChanged()" + ng-minlength="8" + required> + </div> + <div class="form-errors" + ng-show="passwordForm.$submitted && passwordForm.password.$error" + ng-messages="passwordForm.password.$error"> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 8}"></span> + </div> + <div class="form-error" ng-message="maxlength"> + <span translate="ERROR.FIELD_TOO_LONG"></span> + </div> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + </div> + + <!-- confirm password --> + <div class="item item-input" + ng-class="{ 'item-input-error': passwordForm.$submitted && passwordForm.confirmPassword.$invalid}"> + <span class="input-label" translate>ACCOUNT.NEW.PASSWORD_CONFIRM</span> + <input ng-if="!showPassword" + name="confirmPassword" type="password" + placeholder="{{'ACCOUNT.NEW.PASSWORD_CONFIRM_HELP' | translate}}" + ng-model="formData.confirmPassword" + compare-to="formData.password"> + <input ng-if="showPassword" + name="confirmPassword" type="text" + placeholder="{{'ACCOUNT.NEW.PASSWORD_CONFIRM_HELP' | translate}}" + ng-model="formData.confirmPassword" + compare-to="formData.password"> + </div> + <div class="form-errors" + ng-show="passwordForm.$submitted && passwordForm.confirmPassword.$error" + ng-messages="passwordForm.confirmPassword.$error"> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT"></span> + </div> + <div class="form-error" ng-message="maxlength"> + <span translate="ERROR.FIELD_TOO_LONG"></span> + </div> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + <div class="form-error" ng-message="compareTo"> + <span translate="ERROR.PASSWORD_NOT_CONFIRMED"></span> + </div> + </div> + + <!-- Show values --> + <div class="item item-toggle dark"> + <span translate>COMMON.SHOW_VALUES</span> + <label class="toggle toggle-royal"> + <input type="checkbox" ng-model="showPassword"> + <div class="track"> + <div class="handle"></div> + </div> + </label> + </div> + </div> + + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + <button class="button button-calm icon-right ion-chevron-right ink" type="submit" translate> + COMMON.BTN_NEXT + </button> + </div> + + <div class="padding hidden-xs"> + </div> + </form> + </ion-content> + </ion-slide-page> + + <!-- STEP 5: pseudo--> + <ion-slide-page ng-if="formData.isMember"> + <ion-content class="has-header" scroll="false"> + <form name="pseudoForm" novalidate="" ng-submit="doNext('pseudoForm')"> + + <div class="item item-text-wrap text-center padding hidden-xs" > + <a class="pull-right icon-help" ng-click="showHelpModal('join-pseudo')"></a> + <span translate>ACCOUNT.NEW.PSEUDO_WARNING</span> + </div> + + <div class="list" + ng-init="setForm(pseudoForm, 'pseudoForm')"> + + <!-- pseudo --> + <div class="item item-input" + ng-if="formData.isMember" + ng-class="{'item-input-error': pseudoForm.$submitted && pseudoForm.pseudo.$invalid}"> + <span class="input-label" translate>ACCOUNT.NEW.PSEUDO</span> + <input name="pseudo" type="text" placeholder="{{'ACCOUNT.NEW.PSEUDO_HELP' | translate}}" + ng-model="formData.pseudo" + ng-minlength="3" + ng-maxlength="100" + required> + </div> + <div class="form-errors" + ng-if="formData.isMember" + ng-show="pseudoForm.$submitted && pseudoForm.pseudo.$error" + ng-messages="pseudoForm.pseudo.$error"> + <div class="form-error" ng-message="minlength"> + <span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 3}"></span> + </div> + <div class="form-error" ng-message="maxlength"> + <span translate="ERROR.FIELD_TOO_LONG_WITH_LENGTH" translate-values="{maxLength: 100}"></span> + </div> + <div class="form-error" ng-message="required"> + <span translate="ERROR.FIELD_REQUIRED"></span> + </div> + </div> + + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + <button class="button button-calm icon-right ion-chevron-right ink" type="submit" translate> + COMMON.BTN_NEXT + </button> + </div> + </div> + </form> + </ion-content> + </ion-slide-page> + + <!-- STEP 6: last slide --> + <ion-slide-page> + <ion-content class="has-header" scroll="false"> + + <div class="padding text-center" translate>ACCOUNT.NEW.LAST_SLIDE_CONGRATULATION</div> + + <div class="list"> + + <ion-item class="item item-text-wrap item-border"> + <div class="dark pull-right padding-right" ng-if="formData.computing"> + <ion-spinner icon="android"></ion-spinner> + </div> + <span class="input-label" translate>COMMON.PUBKEY</span> + <span class="gray text-no-wrap" ng-if="formData.computing" translate> + ACCOUNT.NEW.COMPUTING_PUBKEY + </span> + <span class="gray text-no-wrap" ng-if="formData.pubkey"> + {{formData.pubkey}} + </span> + </ion-item> + </div> + + <div class="padding hidden-xs text-right"> + <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL + </button> + <button class="button button-positive ink" ng-click="doNewAccount()" translate> + COMMON.BTN_SEND + <i class="icon ion-android-send"></i> + </button> + </div> + </ion-content> + </ion-slide-page> + + </ion-slides> +</ion-modal-view> diff --git a/www/templates/wallet/popover_actions.html b/www/templates/wallet/popover_actions.html index 4b0c4c79dd8e2e0b2be228628c23ba73d3eed6db..366e276e8214593b3ff22ca2820453f010d4b736 100644 --- a/www/templates/wallet/popover_actions.html +++ b/www/templates/wallet/popover_actions.html @@ -61,11 +61,19 @@ </a> <!-- revocation --> - <a class="item item-icon-left assertive ink" + <!--<a class="item item-icon-left assertive ink"--> + <!--ng-if="!walletData.requirements.needSelf"--> + <!--ng-click="revokeIdentity()">--> + <!--<i class="icon ion-minus-circled"></i>--> + <!--<span ng-bind-html="'ACCOUNT.BTN_REVOKE' | translate"></span>--> + + <!--</a>--> + + <a class="item item-icon-left ink" ng-if="!walletData.requirements.needSelf" - ng-click="revokeIdentity()"> - <i class="icon ion-minus-circled"></i> - <span ng-bind-html="'ACCOUNT.BTN_REVOKE' | translate"></span> + ng-click="showSecurityModal()"> + <i class="icon ion-android-lock"></i> + <span ng-bind-html="'ACCOUNT.SECURITY.TITLE' | translate"></span> </a>