Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • chrome-manifest-v3
  • develop
  • feat/force-migration-check
  • feat/improve-network-scan
  • feature/android_api_19
  • feature/encrypted_comment
  • feature/migrate-cordova-13
  • gitlab_migration_1
  • master
  • rml8
  • 0.0.1.ES.alpha1
  • 0.0.2
  • 0.1.13
  • 0.1.14
  • 0.1.15
  • 0.1.16
  • 0.1.17
  • 0.1.18
  • 0.1.19
  • 0.1.20
  • 0.1.21
  • 0.1.22
  • 0.1.23
  • 0.1.24
  • 0.1.25
  • 0.1.26
  • 0.1.27
  • 0.1.28
  • 0.1.4
  • 0.1.7
  • 0.1.8
  • 0.2.0
  • 0.2.1
  • v0.10.0
  • v0.10.1
  • v0.10.2
  • v0.11.0
  • v0.11.1
  • v0.11.2
  • v0.11.3
  • v0.11.4
  • v0.11.5
  • v0.11.6
  • v0.11.7
  • v0.11.8
  • v0.12.0
  • v0.12.1
  • v0.12.2
  • v0.12.3
  • v0.12.4
  • v0.12.5
  • v0.12.6
  • v0.12.7
  • v0.12.8
  • v0.12.9
  • v0.13.0
  • v0.14.0
  • v0.14.1
  • v0.15.0
  • v0.15.1
  • v0.15.2
  • v0.15.3
  • v0.15.4
  • v0.15.5
  • v0.15.6
  • v0.15.7
  • v0.16.0
  • v0.16.1
  • v0.17.0
  • v0.17.1
  • v0.17.2
  • v0.17.3
  • v0.17.4
  • v0.17.5
  • v0.17.6
  • v0.18.0
  • v0.18.1
  • v0.18.2
  • v0.18.3
  • v0.19.0
  • v0.19.1
  • v0.19.2
  • v0.19.3
  • v0.19.4
  • v0.19.5
  • v0.19.6
  • v0.2.10
  • v0.2.12
  • v0.2.13
  • v0.2.14
  • v0.2.3
  • v0.2.4
  • v0.2.5
  • v0.2.6
  • v0.2.7
  • v0.2.8
  • v0.2.9
  • v0.3.0
  • v0.3.1
  • v0.3.10
  • v0.3.11
  • v0.3.12
  • v0.3.13
  • v0.3.14
  • v0.3.15
  • v0.3.16
  • v0.3.17
  • v0.3.2
  • v0.3.3
  • v0.3.4
110 results

Target

Select target project
  • cordeliaze/cesium
  • pfouque06/cesium
  • wellno1/cesium
  • 1000i100/cesium
  • vincentux/cesium
  • calbasi/cesium
  • thomasbromehead/cesium
  • matograine/cesium
  • clients/cesium-grp/cesium
  • cedricmenec/cesium
  • Pamplemousse/cesium
  • etienneleba/cesium
  • tnntwister/cesium
  • scanlegentil/cesium
  • morvanc/cesium
  • yyy/cesium
  • Axce/cesium
  • Bertrandbenj/cesium
  • Lupus/cesium
  • elmau/cesium
  • MartinDelille/cesium
  • tykayn/cesium
  • numeropi/cesium
  • Vivakvo/cesium
  • pokapow/cesium
  • pini-gh/cesium
  • anam/cesium
  • RavanH/cesium
  • bpresles/cesium
  • am97/cesium
  • tuxmain/cesium
  • jytou/cesium
  • oliviermaurice/cesium
  • 666titi999/cesium
  • Yvv/cesium
35 results
Select Git revision
  • Vivakvo-master-patch-80805
  • Vivakvo-master-patch-99327
  • dev
  • gitlab_migration_1
  • issue_4
  • issue_780
  • master
  • patch-1
  • patch-10
  • patch-11
  • patch-12
  • patch-13
  • patch-14
  • patch-15
  • patch-16
  • patch-17
  • patch-18
  • patch-19
  • patch-2
  • patch-21
  • patch-22
  • patch-23
  • patch-24
  • patch-25
  • patch-26
  • patch-3
  • patch-4
  • patch-5
  • patch-6
  • patch-7
  • patch-8
  • patch-9
  • rml8
  • undefined
  • 0.0.1.ES.alpha1
  • 0.0.2
  • 0.1.13
  • 0.1.14
  • 0.1.15
  • 0.1.16
  • 0.1.17
  • 0.1.18
  • 0.1.19
  • 0.1.20
  • 0.1.21
  • 0.1.22
  • 0.1.23
  • 0.1.24
  • 0.1.25
  • 0.1.26
  • 0.1.27
  • 0.1.28
  • 0.1.4
  • 0.1.7
  • 0.1.8
  • 0.2.0
  • 0.2.1
  • v0.10.0
  • v0.10.1
  • v0.10.2
  • v0.11.0
  • v0.11.1
  • v0.11.2
  • v0.11.3
  • v0.11.4
  • v0.11.5
  • v0.11.6
  • v0.11.7
  • v0.11.8
  • v0.12.0
  • v0.12.1
  • v0.12.2
  • v0.12.3
  • v0.12.4
  • v0.12.5
  • v0.12.6
  • v0.12.7
  • v0.12.8
  • v0.12.9
  • v0.13.0
  • v0.14.0
  • v0.14.1
  • v0.15.0
  • v0.15.1
  • v0.15.2
  • v0.15.3
  • v0.15.4
  • v0.15.5
  • v0.15.6
  • v0.15.7
  • v0.16.0
  • v0.16.1
  • v0.17.0
  • v0.17.1
  • v0.17.2
  • v0.17.3
  • v0.17.4
  • v0.17.5
  • v0.17.6
  • v0.18.0
  • v0.18.1
  • v0.18.2
  • v0.18.3
  • v0.19.0
  • v0.19.1
  • v0.19.2
  • v0.19.3
  • v0.19.4
  • v0.19.5
  • v0.19.6
  • v0.2.10
  • v0.2.12
  • v0.2.13
  • v0.2.14
  • v0.2.3
  • v0.2.4
  • v0.2.5
  • v0.2.6
  • v0.2.7
  • v0.2.8
  • v0.2.9
  • v0.3.0
  • v0.3.1
  • v0.3.10
  • v0.3.11
  • v0.3.12
  • v0.3.13
  • v0.3.14
  • v0.3.15
  • v0.3.16
  • v0.3.17
  • v0.3.2
  • v0.3.3
  • v0.3.4
134 results
Show changes
Showing
with 4660 additions and 25692 deletions
angular.module('cesium.cache.services', ['angular-cache']) angular.module('cesium.cache.services', ['angular-cache'])
.factory('csCache', function($http, csSettings, CacheFactory) { .factory('csCache', function($rootScope, $http, $window, csSettings, CacheFactory) {
'ngInject'; 'ngInject';
var var
constants = { constants = {
LONG: 1 * 60 * 60 * 1000 /*1 hour*/, SHORT: csSettings.defaultSettings.cacheTimeMs, // around 1min
MEDIUM: 5 * 60 * 1000 /*5 min*/, MEDIUM: 5 * 60 * 1000, // 5 min
SHORT: csSettings.defaultSettings.cacheTimeMs LONG: 1 * 60 * 60 * 1000, // 1 hour
VERY_LONG: 54000000 // 15 days
}, },
cacheNames = [] storageMode = getSettingsStorageMode(),
cacheNames = [],
listeners = []
; ;
function getOrCreateCache(prefix, maxAge, onExpire){ function getSettingsStorageMode(settings) {
prefix = prefix || 'csCache-'; settings = settings || csSettings.data;
maxAge = maxAge || constants.SHORT; return settings && settings.useLocalStorage && settings.persistCache && $window.localStorage ? 'localStorage' : 'memory';
var cacheName = prefix + maxAge;
if (!onExpire) {
if (!cacheNames[cacheName]) {
cacheNames[cacheName] = true;
} }
return CacheFactory.get(cacheName) ||
CacheFactory.createCache(cacheName, { function getCacheOptions(options) {
maxAge: maxAge, options = options || {};
deleteOnExpire: 'aggressive', options.storageMode = getSettingsStorageMode();
//cacheFlushInterval: 60 * 60 * 1000, // clear itself every hour options.deleteOnExpire = (options.storageMode === 'localStorage' || options.onExpire) ? 'aggressive' : 'passive';
recycleFreq: Math.max(maxAge - 1000, 5 * 60 * 1000 /*5min*/), options.cacheFlushInterval = options.deleteOnExpire === 'passive' ?
storageMode: 'memory' (60 * 60 * 1000) : // If passive mode, remove all items every hour
// FIXME : enable this when cache is cleaning on rollback null;
//csSettings.data.useLocalStorage ? 'localStorage' : 'memory' return options;
});
} }
else {
function getOrCreateCache(prefix, maxAge, onExpire){
prefix = prefix || '';
maxAge = maxAge || constants.SHORT;
var cacheName = prefix + ((maxAge / 1000) + 's');
// If onExpire fn, generate a new cache key
var cache;
if (onExpire && typeof onExpire == 'function') {
var counter = 1; var counter = 1;
while (CacheFactory.get(cacheName + counter)) { while (CacheFactory.get(cacheName + counter)) {
counter++; counter++;
} }
cacheName = cacheName + counter; cacheName = cacheName + counter;
if (!cacheNames[cacheName]) {
cacheNames[cacheName] = true;
} }
return CacheFactory.createCache(cacheName, { else {
cache = CacheFactory.get(cacheName);
}
// Add to cache names map
if (!cacheNames[cacheName]) cacheNames[cacheName] = true;
// Already exists: use it
if (cache) return cache;
// Not exists yet: create a new cache
var options = getCacheOptions({
maxAge: maxAge, maxAge: maxAge,
deleteOnExpire: 'aggressive', onExpire: onExpire || null
//cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour
recycleFreq: maxAge,
onExpire: onExpire,
storageMode: 'memory'
// FIXME : enable this when cache is cleaning on rollback
//csSettings.data.useLocalStorage ? 'localStorage' : 'memory'
}); });
} console.debug("[cache] Creating cache {{0}} with {storageMode: {1}}...".format(cacheName, options.storageMode));
return CacheFactory.createCache(cacheName, options);
} }
function clearAllCaches() { function clearAllCaches() {
console.debug("[cache] cleaning all caches"); console.debug("[cache] Cleaning all caches...");
_.forEach(_.keys(cacheNames), function(cacheName) { _.forEach(_.keys(cacheNames), function(cacheName) {
var cache = CacheFactory.get(cacheName); var cache = CacheFactory.get(cacheName);
if (cache) { if (cache) {
...@@ -66,7 +76,7 @@ angular.module('cesium.cache.services', ['angular-cache']) ...@@ -66,7 +76,7 @@ angular.module('cesium.cache.services', ['angular-cache'])
function clearFromPrefix(cachePrefix) { function clearFromPrefix(cachePrefix) {
_.forEach(_.keys(cacheNames), function(cacheName) { _.forEach(_.keys(cacheNames), function(cacheName) {
if (cacheName.startsWith(cachePrefix)) { if (cacheName.startsWith(cachePrefix)) {
var cache = CacheFactory.get(cacheNames); var cache = CacheFactory.get(cacheName);
if (cache) { if (cache) {
cache.removeAll(); cache.removeAll();
} }
...@@ -74,14 +84,35 @@ angular.module('cesium.cache.services', ['angular-cache']) ...@@ -74,14 +84,35 @@ angular.module('cesium.cache.services', ['angular-cache'])
}); });
} }
function onSettingsChanged(settings) {
var newStorageMode = getSettingsStorageMode(settings);
var hasChanged = (newStorageMode !== storageMode);
if (hasChanged) {
storageMode = newStorageMode;
console.debug("[cache] Updating caches with {storageMode: {0}}".format(storageMode));
_.forEach(_.keys(cacheNames), function(cacheName) {
var cache = CacheFactory.get(cacheName);
if (cache) {
cache.setOptions(getCacheOptions(), true);
}
});
}
}
function addListeners() {
listeners = [
// Listen for settings changed (e.g. the storage mode)
csSettings.api.data.on.changed($rootScope, onSettingsChanged, this)
];
}
addListeners();
return { return {
get: getOrCreateCache, get: getOrCreateCache,
clear: clearFromPrefix, clear: clearFromPrefix,
clearAll: clearAllCaches, clearAll: clearAllCaches,
constants: { constants: constants
LONG : constants.LONG,
SHORT: constants.SHORT
}
}; };
}) })
; ;
...@@ -91,9 +91,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -91,9 +91,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
CryptoAbstractService.prototype.async_load_scrypt = function(on_ready, options) { CryptoAbstractService.prototype.async_load_scrypt = function(on_ready, options) {
var that = this; var that = this;
if (scrypt_module_factory !== null){ if (scrypt_module_factory !== null){scrypt_module_factory(on_ready, options);}
on_ready(scrypt_module_factory(options.requested_total_memory));
}
else {$timeout(function(){that.async_load_scrypt(on_ready, options);}, 100);} else {$timeout(function(){that.async_load_scrypt(on_ready, options);}, 100);}
}; };
...@@ -106,18 +104,11 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -106,18 +104,11 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
CryptoAbstractService.prototype.async_load_base64 = function(on_ready) { CryptoAbstractService.prototype.async_load_base64 = function(on_ready) {
var that = this; var that = this;
if (Base64 !== null) {on_ready(Base64);} if (Base64 !== null) {on_ready(Base64);}
else {$timetout(function(){that.async_load_base64(on_ready);}, 100);} else {$timeout(function(){that.async_load_base64(on_ready);}, 100);}
};
CryptoAbstractService.prototype.async_load_sha256 = function(on_ready) {
var that = this;
if (sha256 !== null){return on_ready(sha256);}
else {$timeout(function(){that.async_load_sha256(on_ready);}, 100);}
}; };
CryptoAbstractService.prototype.seed_from_signSk = function(signSk) { CryptoAbstractService.prototype.seed_from_signSk = function(signSk) {
var that = this; var seed = new Uint8Array(this.constants.SEED_LENGTH);
var seed = new Uint8Array(that.constants.SEED_LENGTH);
for (var i = 0; i < seed.length; i++) seed[i] = signSk[i]; for (var i = 0; i < seed.length; i++) seed[i] = signSk[i];
return seed; return seed;
}; };
...@@ -133,16 +124,6 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -133,16 +124,6 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
return $q.when(nonce); return $q.when(nonce);
}; };
} }
else {
// TODO: add a default function ?
//CryptoAbstractService.prototype.random_nonce = function() {
// var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
// var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// for(var i = 0; i < length; i++) {
// text += possible.charAt(Math.floor(Math.random() * possible.length));
// }
//}
}
function FullJSServiceFactory() { function FullJSServiceFactory() {
this.id = 'FullJS'; this.id = 'FullJS';
...@@ -224,18 +205,47 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -224,18 +205,47 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
*/ */
this.box_keypair_from_sign = function (signKeyPair) { this.box_keypair_from_sign = function (signKeyPair) {
if (signKeyPair.boxSk && signKeyPair.boxPk) return $q.when(signKeyPair); if (signKeyPair.boxSk && signKeyPair.boxPk) return $q.when(signKeyPair);
return $q.when(that.nacl.crypto_box_keypair_from_sign_sk(signKeyPair.signSk)); return $q(function (resolve, reject) {
try {
// TODO: waiting for a new version of js-nacl, with missing functions expose
//resolve(that.nacl.crypto_box_keypair_from_sign_sk(signKeyPair.signSk);
resolve(crypto_box_keypair_from_sign_sk(signKeyPair.signSk));
}
catch(err) {
reject(err);
}
});
}; };
/** /**
* Compute the box public key, from a sign public key * Compute the box public key, from a sign public key
*/ */
this.box_pk_from_sign = function (signPk) { this.box_pk_from_sign = function (signPk) {
return $q.when(that.nacl.crypto_box_pk_from_sign_pk(signPk)); return $q(function(resolve, reject) {
try {
// TODO: waiting for a new version of js-nacl, with missing functions expose
//resolve(that.nacl.crypto_box_pk_from_sign_pk(signPk));
resolve(crypto_box_pk_from_sign_pk(signPk));
}
catch(err) {
reject(err);
}
});
}; };
this.box_sk_from_sign = function (signSk) { this.box_sk_from_sign = function (signSk) {
return $q.when(that.nacl.crypto_box_sk_from_sign_sk(signSk)); return $q(function(resolve, reject) {
try {
// TODO: waiting for a new version of js-nacl, with missing functions expose
//resolve(that.nacl.crypto_box_sk_from_sign_sk(signSk));
resolve(crypto_box_sk_from_sign_sk(signSk));
}
catch(err) {
reject(err);
}
});
}; };
/** /**
...@@ -247,17 +257,14 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -247,17 +257,14 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
resolve(message); resolve(message);
return; return;
} }
var messageBin = that.util.decode_utf8(message); var messageBin = that.nacl.encode_utf8(message);
if (typeof recipientPk === "string") { if (typeof recipientPk === "string") {
recipientPk = that.util.decode_base58(recipientPk); recipientPk = that.util.decode_base58(recipientPk);
} }
//console.debug('Original message: ' + message);
try { try {
var ciphertextBin = that.nacl.crypto_box(messageBin, nonce, recipientPk, senderSk); var ciphertextBin = that.nacl.crypto_box(messageBin, nonce, recipientPk, senderSk);
var ciphertext = that.util.encode_base64(ciphertextBin); var ciphertext = that.util.encode_base64(ciphertextBin);
//console.debug('Encrypted message: ' + ciphertext);
resolve(ciphertext); resolve(ciphertext);
} }
catch (err) { catch (err) {
...@@ -275,6 +282,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -275,6 +282,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
resolve(cypherText); resolve(cypherText);
return; return;
} }
var ciphertextBin = that.util.decode_base64(cypherText); var ciphertextBin = that.util.decode_base64(cypherText);
if (typeof senderPk === "string") { if (typeof senderPk === "string") {
senderPk = that.util.decode_base58(senderPk); senderPk = that.util.decode_base58(senderPk);
...@@ -282,10 +290,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -282,10 +290,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
try { try {
var message = that.nacl.crypto_box_open(ciphertextBin, nonce, senderPk, recipientSk); var message = that.nacl.crypto_box_open(ciphertextBin, nonce, senderPk, recipientSk);
that.util.array_to_string(message, function (result) { resolve(that.nacl.decode_utf8(message));
//console.debug('Decrypted text: ' + result);
resolve(result);
});
} }
catch (err) { catch (err) {
reject(err); reject(err);
...@@ -317,6 +322,16 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -317,6 +322,16 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
}); });
}; };
/**
* Get pubkey (sign), from salt+password (Scrypt auth)
*/
this.scryptPubkey = function(salt, password, scryptParams) {
return that.scryptKeypair(salt, password, scryptParams)
.then(function(keypair){
return that.util.encode_base58(keypair.signPk);
});
};
/** /**
* Create key pairs from a seed * Create key pairs from a seed
*/ */
...@@ -394,7 +409,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -394,7 +409,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
var naclOptions = {}; var naclOptions = {};
var scryptOptions = {}; var scryptOptions = {};
if (ionic.Platform.grade.toLowerCase()!='a') { if (ionic.Platform.grade.toLowerCase()!='a') {
console.info('Reduce NaCl memory to 16mb, because plateform grade is not [a] but [{0}]'.format(ionic.Platform.grade)); console.info('Reduce NaCl memory to 16mb, because platform grade is not [a] but [{0}]'.format(ionic.Platform.grade));
naclOptions.requested_total_memory = 16 * 1048576; // 16 Mo naclOptions.requested_total_memory = 16 * 1048576; // 16 Mo
} }
var loadedLib = 0; var loadedLib = 0;
...@@ -418,7 +433,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -418,7 +433,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
that.base58 = lib; that.base58 = lib;
checkAllLibLoaded(); checkAllLibLoaded();
}); });
this.async_load_base64(function(lib) { that.async_load_base64(function(lib) {
that.base64 = lib; that.base64 = lib;
checkAllLibLoaded(); checkAllLibLoaded();
}); });
...@@ -436,387 +451,134 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -436,387 +451,134 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
pack: that.box, pack: that.box,
open: that.box_open open: that.box_open
}; };
}
FullJSServiceFactory.prototype = new CryptoAbstractService();
/* -----------------------------------------------------------------------------------------------------------------
* Service that use Cordova MiniSodium plugin
* ----------------------------------------------------------------------------------------------------------------*/
/***
* Factory for crypto, using Cordova plugin
*/
function CordovaServiceFactory() {
this.id = 'MiniSodium';
// libraries handlers
this.nacl = null; // the cordova plugin
this.base58= null;
this.sha256= null;
var that = this;
// functions /*--
this.util = this.util || {}; start WORKAROUND - Publish missing functions (see PR js-nacl: https://github.com/tonyg/js-nacl/pull/54)
this.util.decode_utf8 = function(s) { -- */
return that.nacl.to_string(s);
};
this.util.encode_utf8 = function(s) {
return that.nacl.from_string(s);
};
this.util.encode_base58 = function(a) {
return that.base58.encode(a);
};
this.util.decode_base58 = function(a) {
var i;
var d = that.base58.decode(a);
var b = new Uint8Array(d.length);
for (i = 0; i < d.length; i++) b[i] = d[i];
return b;
};
this.util.decode_base64 = function (a) {
return that.nacl.from_base64(a);
};
this.util.encode_base64 = function (b) {
return that.nacl.to_base64(b);
};
this.util.hash_sha256 = function(message) {
return $q.when(that.sha256(message).toUpperCase());
};
this.util.random_nonce = function() {
var nonce = new Uint8Array(that.constants.crypto_secretbox_NONCEBYTES);
that.crypto.getRandomValues(nonce);
return $q.when(nonce);
};
this.util.crypto_hash_sha256 = function (message) {
return that.nacl.from_hex(that.sha256(message));
};
this.util.crypto_scrypt = function(password, salt, N, r, p, seedLength) { function crypto_box_keypair_from_sign_sk(sk) {
var deferred = $q.defer(); var ska = check_injectBytes("crypto_box_keypair_from_sign_sk", "sk", sk,
that.nacl.crypto_pwhash_scryptsalsa208sha256_ll( that.nacl.nacl_raw._crypto_sign_secretkeybytes());
password, var skb = new Target(that.nacl.nacl_raw._crypto_box_secretkeybytes());
salt, check("_crypto_sign_ed25519_sk_to_curve25519",
N, that.nacl.nacl_raw._crypto_sign_ed25519_sk_to_curve25519(skb.address, ska));
r, FREE(ska);
p, return that.nacl.crypto_box_keypair_from_raw_sk(skb.extractBytes());
seedLength,
function (err, seed) {
if (err) { deferred.reject(err); return;}
deferred.resolve(seed);
} }
);
return deferred.promise;
};
/**
* Create key pairs (sign and box), from salt+password (Scrypt), using cordova
*/
this.scryptKeypair = function(salt, password, scryptParams) {
var deferred = $q.defer();
that.nacl.crypto_pwhash_scryptsalsa208sha256_ll(
that.nacl.from_string(password),
that.nacl.from_string(salt),
scryptParams && scryptParams.N || that.constants.SCRYPT_PARAMS.DEFAULT.N,
scryptParams && scryptParams.r || that.constants.SCRYPT_PARAMS.DEFAULT.r,
scryptParams && scryptParams.p || that.constants.SCRYPT_PARAMS.DEFAULT.p,
that.constants.SEED_LENGTH,
function (err, seed) {
if (err) { deferred.reject(err); return;}
that.nacl.crypto_sign_seed_keypair(seed, function (err, signKeypair) {
if (err) { deferred.reject(err); return;}
var result = {
signPk: signKeypair.pk,
signSk: signKeypair.sk
};
that.box_keypair_from_sign(result)
.then(function(boxKeypair) {
result.boxPk = boxKeypair.pk;
result.boxSk = boxKeypair.sk;
deferred.resolve(result);
})
.catch(function(err) {
deferred.reject(err);
});
});
function crypto_box_pk_from_sign_pk(pk) {
var pka = check_injectBytes("crypto_box_pk_from_sign_pk", "pk", pk,
that.nacl.nacl_raw._crypto_sign_publickeybytes());
var pkb = new Target(that.nacl.nacl_raw._crypto_box_publickeybytes());
check("_crypto_sign_ed25519_pk_to_curve25519",
that.nacl.nacl_raw._crypto_sign_ed25519_pk_to_curve25519(pkb.address, pka));
FREE(pka);
return pkb.extractBytes();
} }
);
return deferred.promise;
};
/**
* Create key pairs from a seed
*/
this.seedKeypair = function(seed) {
var deferred = $q.defer();
that.nacl.crypto_sign_seed_keypair(seed, function (err, signKeypair) {
if (err) { deferred.reject(err); return;}
deferred.resolve({
signPk: signKeypair.pk,
signSk: signKeypair.sk
});
});
return deferred.promise;
};
/**
* Get sign PK from salt+password (Scrypt), using cordova
*/
this.scryptSignPk = function(salt, password, scryptParams) {
var deferred = $q.defer();
that.nacl.crypto_pwhash_scryptsalsa208sha256_ll(
that.nacl.from_string(password),
that.nacl.from_string(salt),
scryptParams && scryptParams.N || that.constants.SCRYPT_PARAMS.DEFAULT.N,
scryptParams && scryptParams.r || that.constants.SCRYPT_PARAMS.DEFAULT.r,
scryptParams && scryptParams.p || that.constants.SCRYPT_PARAMS.DEFAULT.p,
that.constants.SEED_LENGTH,
function (err, seed) {
if (err) { deferred.reject(err); return;}
that.nacl.crypto_sign_seed_keypair(seed, function (err, signKeypair) {
if (err) { deferred.reject(err); return;}
deferred.resolve(signKeypair.pk);
});
function crypto_box_sk_from_sign_sk(sk) {
var ska = check_injectBytes("crypto_box_sk_from_sign_sk", "sk", sk,
that.nacl.nacl_raw._crypto_sign_secretkeybytes());
var skb = new Target(that.nacl.nacl_raw._crypto_box_secretkeybytes());
check("_crypto_sign_ed25519_sk_to_curve25519",
that.nacl.nacl_raw._crypto_sign_ed25519_sk_to_curve25519(skb.address, ska));
FREE(ska);
return skb.extractBytes();
} }
);
return deferred.promise;
};
/**
* Verify a signature of a message, for a pubkey
*/
this.verify = function (message, signature, pubkey) {
var deferred = $q.defer();
that.nacl.crypto_sign_verify_detached(
that.nacl.from_base64(signature),
that.nacl.from_string(message),
that.nacl.from_base64(pubkey),
function(err, verified) {
if (err) { deferred.reject(err); return;}
deferred.resolve(verified);
});
return deferred.promise;
};
/**
* Sign a message, from a key pair
*/
this.sign = function(message, keypair) {
var deferred = $q.defer();
that.nacl.crypto_sign( function check_length(function_name, what, thing, expected_length) {
that.nacl.from_string(message), // message if (thing.length !== expected_length) {
keypair.signSk, // sk throw {message: "nacl." + function_name + " expected " +
function(err, signedMsg) { expected_length + "-byte " + what + " but got length " + thing.length};
if (err) { deferred.reject(err); return;}
var sig;
if (signedMsg.length > that.constants.crypto_sign_BYTES) {
sig = new Uint8Array(that.constants.crypto_sign_BYTES);
for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i];
} }
else {
sig = signedMsg;
} }
var signature = that.nacl.to_base64(sig);
deferred.resolve(signature);
});
return deferred.promise;
};
/**
* Compute the box key pair, from a sign key pair
*/
this.box_keypair_from_sign = function(signKeyPair) {
if (signKeyPair.boxSk && signKeyPair.boxPk) return $q.when(signKeyPair);
var deferred = $q.defer();
var result = {};
that.nacl.crypto_sign_ed25519_pk_to_curve25519(signKeyPair.signPk, function(err, boxPk) {
if (err) { deferred.reject(err); return;}
result.boxPk = boxPk;
if (result.boxSk) deferred.resolve(result);
});
that.nacl.crypto_sign_ed25519_sk_to_curve25519(signKeyPair.signSk, function(err, boxSk) {
if (err) { deferred.reject(err); return;}
result.boxSk = boxSk;
if (result.boxPk) deferred.resolve(result);
});
return deferred.promise; function check(function_name, result) {
}; if (result !== 0) {
throw {message: "nacl_raw." + function_name + " signalled an error"};
/**
* Compute the box public key, from a sign public key
*/
this.box_pk_from_sign = function(signPk) {
var deferred = $q.defer();
that.nacl.crypto_sign_ed25519_pk_to_curve25519(signPk, function(err, boxPk) {
if (err) { deferred.reject(err); return;}
deferred.resolve(boxPk);
});
return deferred.promise;
};
/**
* Compute the box secret key, from a sign secret key
*/
this.box_sk_from_sign = function(signSk) {
var deferred = $q.defer();
that.nacl.crypto_sign_ed25519_sk_to_curve25519(signSk, function(err, boxSk) {
if (err) { deferred.reject(err); return;}
deferred.resolve(boxSk);
});
return deferred.promise;
};
/**
* Encrypt a message, from a key pair
*/
this.box = function(message, nonce, recipientPk, senderSk) {
if (!message) {
return $q.reject('No message');
} }
var deferred = $q.defer();
var messageBin = that.nacl.from_string(message);
if (typeof recipientPk === "string") {
recipientPk = that.util.decode_base58(recipientPk);
} }
that.nacl.crypto_box_easy(messageBin, nonce, recipientPk, senderSk, function(err, ciphertextBin) { function check_injectBytes(function_name, what, thing, expected_length, leftPadding) {
if (err) { deferred.reject(err); return;} check_length(function_name, what, thing, expected_length);
var ciphertext = that.util.encode_base64(ciphertextBin); return injectBytes(thing, leftPadding);
//console.debug('Encrypted message: ' + ciphertext);
deferred.resolve(ciphertext);
});
return deferred.promise;
};
/**
* Decrypt a message, from a key pair
*/
this.box_open = function(cypherText, nonce, senderPk, recipientSk) {
if (!cypherText) {
return $q.reject('No cypherText');
} }
var deferred = $q.defer();
var ciphertextBin = that.nacl.from_base64(cypherText); function injectBytes(bs, leftPadding) {
if (typeof senderPk === "string") { var p = leftPadding || 0;
senderPk = that.util.decode_base58(senderPk); var address = MALLOC(bs.length + p);
that.nacl.nacl_raw.HEAPU8.set(bs, address + p);
for (var i = address; i < address + p; i++) {
that.nacl.nacl_raw.HEAPU8[i] = 0;
} }
return address;
// Avoid crash if content has not the minimal length - Fix #346
if (ciphertextBin.length < that.constants.crypto_box_MACBYTES) {
deferred.reject('Invalid cypher content length');
return;
} }
that.nacl.crypto_box_open_easy(ciphertextBin, nonce, senderPk, recipientSk, function(err, message) { function MALLOC(nbytes) {
if (err) { deferred.reject(err); return;} var result = that.nacl.nacl_raw._malloc(nbytes);
that.util.array_to_string(message, function(result) { if (result === 0) {
//console.debug('Decrypted text: ' + result); throw {message: "malloc() failed", nbytes: nbytes};
deferred.resolve(result); }
}); return result;
}); }
return deferred.promise; function FREE(pointer) {
}; that.nacl.nacl_raw._free(pointer);
}
this.load = function() { function free_all(addresses) {
var deferred = $q.defer(); for (var i = 0; i < addresses.length; i++) {
if (!window.plugins || !window.plugins.MiniSodium) { FREE(addresses[i]);
deferred.reject("Cordova plugin 'MiniSodium' not found. Please load Full JS implementation instead."); }
} }
else {
that.nacl = window.plugins.MiniSodium;
var loadedLib = 0; function extractBytes(address, length) {
var checkAllLibLoaded = function() { var result = new Uint8Array(length);
loadedLib++; result.set(that.nacl.nacl_raw.HEAPU8.subarray(address, address + length));
if (loadedLib == 2) { return result;
that.loaded = true;
deferred.resolve();
} }
};
that.async_load_base58(function(lib) { function Target(length) {
that.base58 = lib; this.length = length;
checkAllLibLoaded(); this.address = MALLOC(length);
});
that.async_load_sha256(function(lib) {
that.sha256 = lib;
checkAllLibLoaded();
});
} }
return deferred.promise; Target.prototype.extractBytes = function (offset) {
var result = extractBytes(this.address + (offset || 0), this.length - (offset || 0));
FREE(this.address);
this.address = null;
return result;
}; };
// Shortcuts /*--
this.util.hash = that.util.hash_sha256; end of WORKAROUND
this.box = { -- */
keypair: {
fromSignKeypair: that.box_keypair_from_sign,
skFromSignSk: that.box_sk_from_sign,
pkFromSignPk: that.box_pk_from_sign
},
pack: that.box,
open: that.box_open
};
} }
CordovaServiceFactory.prototype = new CryptoAbstractService(); FullJSServiceFactory.prototype = new CryptoAbstractService();
/* ----------------------------------------------------------------------------------------------------------------- /* -----------------------------------------------------------------------------------------------------------------
* Create service instance * Create service instance
* ----------------------------------------------------------------------------------------------------------------*/ * ----------------------------------------------------------------------------------------------------------------*/
var service = new CryptoAbstractService(); var service = new FullJSServiceFactory();
var isDevice = true;
// removeIf(android)
// removeIf(ios)
isDevice = false;
// endRemoveIf(ios)
// endRemoveIf(android)
//console.debug("[crypto] Created CryptotUtils service. device=" + isDevice); //console.debug("[crypto] Created CryptoUtils service.");
ionicReady().then(function() { ionicReady().then(function() {
console.debug('[crypto] Starting...'); console.debug('[crypto] Starting...');
var now = Date.now(); var now = Date.now();
console.debug('[crypto] Has crypto.getRandomValues ? ' + (crypto && crypto.getRandomValues && true || false));
var serviceImpl;
// Use Cordova plugin implementation, when exists
if (isDevice && window.plugins && window.plugins.MiniSodium && crypto && crypto.getRandomValues) {
console.debug('[crypto] Loading \'MiniSodium\' implementation...');
serviceImpl = new CordovaServiceFactory();
}
else {
console.debug('[crypto] Loading \'FullJS\' implementation...'); console.debug('[crypto] Loading \'FullJS\' implementation...');
serviceImpl = new FullJSServiceFactory();
}
// Load (async lib) // Load (async lib)
serviceImpl.load() service.load()
.catch(function(err) { .catch(function(err) {
console.error(err); console.error('[crypto] Failed to load implementation: ' + (err && err.message || err), err);
throw err; throw err;
}) })
.then(function() { .then(function() {
service.copy(serviceImpl);
console.debug('[crypto] Loaded \'{0}\' implementation in {1}ms'.format(service.id, Date.now() - now)); console.debug('[crypto] Loaded \'{0}\' implementation in {1}ms'.format(service.id, Date.now() - now));
}); });
...@@ -893,6 +655,15 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -893,6 +655,15 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
BAD_CHECKSUM: 3002 BAD_CHECKSUM: 3002
}; };
function ready() {
if (CryptoUtils.isLoaded()) return $q.when();
return $timeout(ready, 100);
}
function isStarted() {
return CryptoUtils.isLoaded();
}
/* -- keyfile -- */ /* -- keyfile -- */
function readKeyFile(file, options) { function readKeyFile(file, options) {
...@@ -936,7 +707,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -936,7 +707,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
} }
// Type: PubSec // Type: PubSec
if (type == 'PubSec') { if (type === 'PubSec') {
// Read Pub field // Read Pub field
matches = regexp.FILE.PUB.exec(content); matches = regexp.FILE.PUB.exec(content);
...@@ -954,7 +725,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -954,7 +725,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
} }
// Type: WIF or EWIF // Type: WIF or EWIF
else if (type == 'WIF' || type == 'EWIF') { else if (type === 'WIF' || type === 'EWIF') {
matches = regexp.FILE.DATA.exec(content); matches = regexp.FILE.DATA.exec(content);
if (!matches) { if (!matches) {
return $q.reject('Missing [Data] field in file. This is required for WIF or EWIF format'); return $q.reject('Missing [Data] field in file. This is required for WIF or EWIF format');
...@@ -997,15 +768,15 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -997,15 +768,15 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
options.type = options.type || (data_int8[0] == 1 && 'WIF') || (data_int8[0] == 2 && 'EWIF'); options.type = options.type || (data_int8[0] == 1 && 'WIF') || (data_int8[0] == 2 && 'EWIF');
// Type: WIF // Type: WIF
if (options.type == 'WIF') { if (options.type === 'WIF') {
return parseWIF_v1(data_base58); return parseWIF_v1(data_base58);
} }
// Type: EWIF // Type: EWIF
if (options.type == 'EWIF') { if (options.type === 'EWIF') {
// If not set, resolve password using the given callback // If not set, resolve password using the given callback
if (typeof options.password == "function") { if (typeof options.password === "function") {
//console.debug("[crypto] [EWIF] Executing 'options.password()' to resolve the password..."); //console.debug("[crypto] [EWIF] Executing 'options.password()' to resolve the password...");
options.password = options.password(); options.password = options.password();
if (!options.password) { if (!options.password) {
...@@ -1038,12 +809,12 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1038,12 +809,12 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
var wif_int8 = CryptoUtils.util.decode_base58(wif_base58); var wif_int8 = CryptoUtils.util.decode_base58(wif_base58);
// Check identifier byte = 0x01 // Check identifier byte = 0x01
if (wif_int8[0] != 1) { if (wif_int8[0] !== 1) {
return $q.reject({message: 'Invalid WIF v1 format: expected [0x01] as first byte'}); return $q.reject({message: 'Invalid WIF v1 format: expected [0x01] as first byte'});
} }
// Check length // Check length
if (wif_int8.length != constants.WIF.DATA_LENGTH) { if (wif_int8.length !== constants.WIF.DATA_LENGTH) {
return $q.reject({message: 'Invalid WIF v1 format: Data must be a '+constants.WIF.DATA_LENGTH+' bytes array, encoded in base 58.'}); return $q.reject({message: 'Invalid WIF v1 format: Data must be a '+constants.WIF.DATA_LENGTH+' bytes array, encoded in base 58.'});
} }
...@@ -1053,7 +824,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1053,7 +824,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
// Compute expected checksum // Compute expected checksum
var expectedChecksum = CryptoUtils.util.crypto_hash_sha256(CryptoUtils.util.crypto_hash_sha256(wif_int8_no_checksum)).slice(0,2); var expectedChecksum = CryptoUtils.util.crypto_hash_sha256(CryptoUtils.util.crypto_hash_sha256(wif_int8_no_checksum)).slice(0,2);
if (CryptoUtils.util.encode_base58(checksum) != CryptoUtils.util.encode_base58(expectedChecksum)) { if (CryptoUtils.util.encode_base58(checksum) !== CryptoUtils.util.encode_base58(expectedChecksum)) {
$q.reject({message: 'Invalid WIF format: bad checksum'}); $q.reject({message: 'Invalid WIF format: bad checksum'});
} }
...@@ -1065,7 +836,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1065,7 +836,7 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
var ewif_int8 = CryptoUtils.util.decode_base58(ewif_base58); var ewif_int8 = CryptoUtils.util.decode_base58(ewif_base58);
// Check identifier byte = 0x02 // Check identifier byte = 0x02
if (ewif_int8[0] != 2) { if (ewif_int8[0] !== 2) {
return $q.reject({message: 'Invalid EWIF v1 format: Expected [0x02] as first byte'}); return $q.reject({message: 'Invalid EWIF v1 format: Expected [0x02] as first byte'});
} }
...@@ -1260,13 +1031,20 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1260,13 +1031,20 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
} }
} }
/* -- Useful functions -- */
/**
/* -- usefull methods -- */ * Compute the pubkey checksum (see Duniter RFC0016)
*/
function pkChecksum(pubkey) { function pkChecksum(pubkey) {
var signPk_int8 = CryptoUtils.util.decode_base58(pubkey); // Remove leading '1' (see https://forum.duniter.org/t/format-de-checksum/7616)
return CryptoUtils.util.encode_base58(CryptoUtils.util.crypto_hash_sha256(CryptoUtils.util.crypto_hash_sha256(signPk_int8))).substring(0,3); var signPk_int8 = pubkey && pubkey.length === 44 && pubkey.charAt(0) === '1' ?
CryptoUtils.util.decode_base58(pubkey.substr(1)) :
CryptoUtils.util.decode_base58(pubkey);
return CryptoUtils.util.encode_base58(
CryptoUtils.util.crypto_hash_sha256(
CryptoUtils.util.crypto_hash_sha256(signPk_int8))
).substring(0,3);
} }
/* -- box (pack/unpack a record) -- */ /* -- box (pack/unpack a record) -- */
...@@ -1448,6 +1226,8 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1448,6 +1226,8 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
return { return {
errorCodes: errorCodes, errorCodes: errorCodes,
constants: constants, constants: constants,
ready: ready,
isStarted: isStarted,
// copy CryptoUtils // copy CryptoUtils
util: angular.extend({ util: angular.extend({
pkChecksum: pkChecksum pkChecksum: pkChecksum
...@@ -1461,6 +1241,10 @@ angular.module('cesium.crypto.services', ['cesium.utils.services']) ...@@ -1461,6 +1241,10 @@ angular.module('cesium.crypto.services', ['cesium.utils.services'])
getKeypair: getBoxKeypair, getKeypair: getBoxKeypair,
pack: packRecordFields, pack: packRecordFields,
open: openRecordFields open: openRecordFields
},
scrypt: {
keypair: CryptoUtils.scryptKeypair,
pubkey: CryptoUtils.scryptPubkey
} }
}; };
}) })
......
...@@ -4,12 +4,6 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -4,12 +4,6 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
.factory('csCurrency', function($rootScope, $q, $timeout, BMA, Api, csSettings) { .factory('csCurrency', function($rootScope, $q, $timeout, BMA, Api, csSettings) {
'ngInject'; 'ngInject';
var defaultBMA = BMA;
function CsCurrency(id, BMA) {
BMA = BMA || defaultBMA;
var var
constants = { constants = {
// Avoid to many call on well known currencies // Avoid to many call on well known currencies
...@@ -25,7 +19,7 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -25,7 +19,7 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
started = false, started = false,
startPromise, startPromise,
listeners, listeners,
api = new Api(this, "csCurrency-" + id); api = new Api(this, "csCurrency");
function powBase(amount, base) { function powBase(amount, base) {
return base <= 0 ? amount : amount * Math.pow(10, base); return base <= 0 ? amount : amount * Math.pow(10, base);
...@@ -215,25 +209,51 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -215,25 +209,51 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
function ready() { function ready() {
if (started) return $q.when(data); if (started) return $q.when(data);
return startPromise || start(); return (startPromise || start());
} }
function stop() { function stop(options) {
if (!started && !startPromise) return $q.when();
console.debug('[currency] Stopping...'); console.debug('[currency] Stopping...');
removeListeners(); removeListeners();
// Clean data
if (!options || options.emitEvent !== false) {
resetData(); resetData();
} }
function restart() { return $q.when();
stop(); }
return $timeout(start, 200);
function restart(startDelayMs) {
return stop()
.then(function () {
if (startDelayMs === 0) return start();
return $timeout(start, startDelayMs || 200);
});
}
function start(bmaAlive) {
if (startPromise) return startPromise;
if (started) return $q.when(data);
if (!bmaAlive) {
return BMA.ready()
.then(function(alive) {
if (alive) return start(alive); // Loop
return $timeout(start, 500); // Loop, after a delay, because BMA node seems to be not alive...
});
} }
function start() {
console.debug('[currency] Starting...'); console.debug('[currency] Starting...');
var now = Date.now(); var now = Date.now();
startPromise = BMA.ready() startPromise = BMA.ready()
.then(function(started) {
if (started) return true;
return $timeout(function() {return start(true);}, 500);
})
// Load data // Load data
.then(loadData) .then(loadData)
...@@ -268,7 +288,7 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -268,7 +288,7 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
var now = moment().utc().unix(); var now = moment().utc().unix();
if (cache) { if (cache) {
if (currentBlock && (now - currentBlock.receivedAt) < 60/*1min*/) { if (currentBlock && currentBlock.receivedAt && (now - currentBlock.receivedAt) < 60/*1min*/) {
//console.debug('[currency] Use current block #'+ currentBlock.number +' from cache (age='+ (now - currentBlock.receivedAt) + 's)'); //console.debug('[currency] Use current block #'+ currentBlock.number +' from cache (age='+ (now - currentBlock.receivedAt) + 's)');
return currentBlock; return currentBlock;
} }
...@@ -279,11 +299,11 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -279,11 +299,11 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
} }
} }
return BMA.blockchain.current() return BMA.blockchain.current(false)
.catch(function(err){ .catch(function(err){
// Special case for currency init (root block not exists): use fixed values // Special case for currency init (root block not exists): use fixed values
if (err && err.ucode == BMA.errorCodes.NO_CURRENT_BLOCK) { if (err && err.ucode == BMA.errorCodes.NO_CURRENT_BLOCK) {
return {number: 0, hash: BMA.constants.ROOT_BLOCK_HASH, medianTime: moment().utc().unix()}; return {number: 0, hash: BMA.constants.ROOT_BLOCK_HASH, medianTime: now};
} }
throw err; throw err;
}) })
...@@ -351,11 +371,4 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services']) ...@@ -351,11 +371,4 @@ angular.module('cesium.currency.services', ['ngApi', 'cesium.bma.services'])
return getData(); return getData();
} }
}; };
}
var service = new CsCurrency('default');
service.instance = function(id, bma) {
return new CsCurrency(id, bma);
};
return service;
}); });
...@@ -5,7 +5,10 @@ angular.module('cesium.desktop.services', ['cesium.device.services', 'cesium.set ...@@ -5,7 +5,10 @@ angular.module('cesium.desktop.services', ['cesium.device.services', 'cesium.set
.factory('csDesktop', function($rootScope, Device) { .factory('csDesktop', function($rootScope, Device) {
'ngInject'; 'ngInject';
Device.ready()
.then(function() {
if (!Device.isDesktop()) return; if (!Device.isDesktop()) return;
console.info("[desktop-service] Starting desktop service...");
console.info("Starting desktop mode..."); console.debug("[desktop-service] TODO: manage menu and other specific stuff here");
});
}); });
...@@ -2,30 +2,37 @@ var App; ...@@ -2,30 +2,37 @@ var App;
angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.settings.services']) angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.settings.services'])
.factory('Device', .factory('Device', function ($rootScope, $translate, $timeout, $ionicPopup, $q, Api, csConfig,
function($rootScope, $translate, $ionicPopup, $q,
// removeIf(no-device) // removeIf(no-device)
$cordovaClipboard, $cordovaBarcodeScanner, $cordovaCamera, $cordovaClipboard, $cordovaBarcodeScanner, $cordovaCamera, $cordovaNetwork,
// endRemoveIf(no-device) // endRemoveIf(no-device)
ionicReady) { ionicReady, UIUtils, Blob, FileSaver) {
'ngInject'; 'ngInject';
var var
CONST = { CONST = {
MAX_HEIGHT: 400, MAX_HEIGHT: 400,
MAX_WIDTH: 400 MAX_WIDTH: 400,
UTF8_BOM_CHAR: new Uint8Array([0xEF, 0xBB, 0xBF]) // UTF-8 BOM
}, },
api = new Api(this, "Device"),
exports = { exports = {
// workaround to quickly no is device or not (even before the ready() event) // workaround to quickly no is device or not (even before the ready() event)
enable: true enable: true
}, },
cache = {}, cache = {},
started = false, started = false,
startPromise; startPromise,
listeners = {
online: undefined,
offline: undefined
}
;
// removeIf(device) // removeIf(device)
// workaround to quickly no is device or not (even before the ready() event) // workaround to quickly no is device or not (even before the ready() event)
exports.enable = false; exports.enable = false;
// endRemoveIf(device) // endRemoveIf(device)
function getPicture(options) { function getPicture(options) {
...@@ -81,7 +88,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -81,7 +88,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
} }
function scan(n) { function scan(n) {
if (!exports.enable) { if (!exports.barcode.enable) {
return $q.reject("Barcode scanner not enable. Please call 'ionicReady()' once before use (e.g in app.js)."); return $q.reject("Barcode scanner not enable. Please call 'ionicReady()' once before use (e.g in app.js).");
} }
var deferred = $q.defer(); var deferred = $q.defer();
...@@ -90,8 +97,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -90,8 +97,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
if (!result.cancelled) { if (!result.cancelled) {
console.debug('[device] barcode scanner scan: ' + result.text); console.debug('[device] barcode scanner scan: ' + result.text);
deferred.resolve(result.text); // make sure to convert into String deferred.resolve(result.text); // make sure to convert into String
} } else {
else {
console.debug('[device] barcode scanner scan: CANCELLED'); console.debug('[device] barcode scanner scan: CANCELLED');
deferred.resolve(); deferred.resolve();
} }
...@@ -143,6 +149,304 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -143,6 +149,304 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
cordova.plugins.Keyboard.close(); cordova.plugins.Keyboard.close();
} }
}; };
exports.network = {
constants: {
NONE: 'none',
CELL: 'cellular',
CELL_2G: '2g',
CELL_3G: '3g',
CELL_4G: '4g',
CELL_5G: '5g',
ETHERNET: 'ethernet',
WIFI: 'wifi',
UNKOWN: 'unknown',
},
connectionType: function () {
var type;
try {
// If mobile: use the Cordova network plugin
if (exports.network.enable) {
type = navigator.connection.type || Connection.CELL_2G;
console.debug('[device] Network plugin connection type: {0}'.format(type));
return type;
}
// Continue using browser API
if (navigator.onLine === false) {
return exports.network.constants.NONE;
}
var connection = navigator.connection;
type = connection && connection.effectiveType || exports.network.constants.UNKNOWN;
// Si la vitesse de liaison descendante est de 0 mais que le type est '4g', cela signifie probablement que nous sommes hors ligne
if (connection && connection.downlink === 0) {
//console.debug('[device] Navigator connection type: none (downlink=0)');
return exports.network.constants.NONE;
}
else {
//console.debug('[device] Navigator connection type: ' + type);
switch (type) {
case 'slow-2g':
case '2g':
return exports.network.constants.CELL_2G;
case '3g':
return exports.network.constants.CELL_3G;
case '4g':
if (exports.isDesktop()) return exports.network.constants.ETHERNET;
return exports.network.constants.CELL_4G;
case '5g':
if (exports.isDesktop()) return exports.network.constants.ETHERNET;
return exports.network.constants.CELL_5G;
case 'unknown':
if (exports.isDesktop()) return exports.network.constants.ETHERNET;
return exports.network.constants.UNKNOWN;
case 'none':
return exports.network.constants.NONE;
default:
return type;
}
}
}
catch(err) {
console.error('[device] Cannot get connection type: ' + (err && err.message || err), err);
return 'unknown';
}
},
isOnline: function () {
try {
if (exports.network.enable) {
return navigator.connection.type !== Connection.NONE;
}
return angular.isDefined(navigator.onLine) ? navigator.onLine : true;
} catch (err) {
console.error('[device] Cannot check if online: ' + (err && err.message || err), err);
return true;
}
},
isOffline: function () {
try {
if (exports.network.enable) {
return navigator.connection.type === Connection.NONE;
}
return angular.isDefined(navigator.onLine) ? !navigator.onLine : false;
} catch (err) {
console.error('[device] Cannot check if offline: ' + (err && err.message || err), err);
return true;
}
return false;
},
timeout: function (defaultTimeout) {
defaultTimeout = defaultTimeout || csConfig.timeout;
var timeout;
try {
var connectionType = exports.network.connectionType();
switch (connectionType) {
case exports.network.constants.ETHERNET:
case exports.network.constants.WIFI:
case exports.network.constants.CELL: // (e.g. iOS)
case exports.network.constants.CELL_4G:
case exports.network.constants.CELL_5G:
timeout = 4000; // 4s
break;
case exports.network.constants.CELL_3G:
timeout = 5000; // 5s
break;
case exports.network.constants.CELL_2G:
timeout = 10000; // 10s
break;
case exports.network.constants.NONE:
timeout = 0;
break;
case exports.network.constants.UNKNOWN:
default:
console.warn('[device] Fallback to default timeout ({0}ms) - connectionType: {1}'.format(defaultTimeout, connectionType));
timeout = defaultTimeout;
break;
}
// DEBUG
//console.debug('[device] Using timeout: {1}ms (connection type: \'{0}\')'.format(connectionType, timeout));
return timeout;
} catch (err) {
console.error('[device] Error while trying to get connection type: ' + (err && err.message || err));
return defaultTimeout;
}
}
};
exports.downloader = {
enable: false,
download: function(request) {
return $q(function(resolve, reject) {
if (!exports.downloader.enable) return reject("Cordova Downloaded plugin is not enable!");
if (!request) return reject("Missing request argument");
console.debug('[device] Downloading file from request: ' + JSON.stringify(request));
cordova.plugins.Downloader.download(request, function(location) {
console.info("[device] Successfully download file at '{0}'".format(location));
resolve(location);
}, function(err) {
console.error('[device] Cannot download, from given request', request);
reject(err);
});
});
}
};
exports.file = {
enable: false,
started: false,
ready: function() {
if (exports.file.started) return $q.when();
return $q(function(resolve) {
window.addEventListener('filePluginIsReady', function() {
console.debug('[device] [file] Plugin is ready');
// DEBUG: dump available directory
Object.keys(cordova.file).forEach(function(key) {
console.debug('[device] [file] - cordova.file.{0}: '.format(key) + cordova.file[key]);
});
exports.file.started = true;
resolve();
}, false);
});
},
save: function(content, options) {
var filename = options && options.filename || 'export.txt';
var charset = (options && options.encoding || 'utf-8').toLowerCase();
var type = options && options.type || 'text/plain';
var withBOM = charset === 'utf-8' && (!options || options.withBOM !== false);
var showToast = options && options.showToast || false;
// Use Cordova file plugin
if (exports.file.enable) {
var directory = options && options.directory || (exports.isAndroid() ?
cordova.file.externalRootDirectory + 'Download' :
cordova.file.documentsDirectory);
var fullPath = (directory.endsWith('/') ? directory : (directory + '/')) + filename;
console.debug("[device] [file] Saving file '{0}' (using Cordova)...".format(fullPath));
return $q(function (resolve, reject) {
var onError = function (err) {
console.error("[device] [file] Error while creating file '{0}': {1}".format(fullPath, err ? JSON.stringify(err): 'Unknown error'));
reject(err || 'Cannot create file ' + filename);
};
window.resolveLocalFileSystemURL(directory, function (directoryEntry) {
directoryEntry.getFile(filename, {create: true}, function (fileEntry) {
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function () {
console.debug("[device] [file] Successfully save file '{0}'".format(fullPath));
resolve(fullPath);
};
fileWriter.onerror = function (e) {
onError();
};
var blob = new Blob(
// Add UTF-8 BOM character (if enable)
withBOM ? [CONST.UTF8_BOM_CHAR, content] : [content],
{type: "{0};charset={1};".format(type, charset)});
fileWriter.write(blob);
}, onError);
}, onError);
}, onError);
})
.then(function (uri) {
if (showToast) {
UIUtils.toast.show('INFO.FILE_DOWNLOADED', 1000);
}
return uri;
});
}
// Fallback to browser download
else {
console.debug("[device] [file] Saving file '{0}'...".format(filename));
var blob = new Blob(
// Add UTF-8 BOM character (if enable)
withBOM ? [CONST.UTF8_BOM_CHAR, content] : [content],
{type: "{0};charset={1};".format(type, charset)});
return FileSaver.saveAs(blob, filename, true /*disable auto BOM*/);
}
},
uri: {
getFilename: function (uri) {
if (!uri) return uri;
var filename = uri.trim();
// Get last part (or all string, if no '/')
var lastSlashIndex = filename.lastIndexOf('/');
if (lastSlashIndex !== -1 && lastSlashIndex !== uri.length - 1) {
filename = filename.substring(lastSlashIndex + 1);
}
// Remove query params
var queryParamIndex = filename.indexOf('?');
if (queryParamIndex !== -1) {
filename = filename.substring(0, queryParamIndex);
}
return filename;
},
getDirectory: function (uri) {
if (!uri) return uri;
var directory = uri.trim();
// Already a folder
if (directory.endsWith('/')) return directory;
// Get part before the last slash
var lastSlashIndex = directory.lastIndexOf('/');
if (lastSlashIndex !== -1 && lastSlashIndex !== directory.length - 1) {
return directory.substring(0, lastSlashIndex+1);
}
return directory;
}
}
};
function getLastIntent() {
var deferred = $q.defer();
window.plugins.launchmyapp.getLastIntent(
deferred.resolve,
deferred.reject);
return deferred.promise;
}
// WARN: Need by cordova-plugin-customurlscheme
window.handleOpenURL = function (intent) {
if (intent) {
console.info('[device] Received new intent: ', intent);
cache.lastIntent = intent; // Remember, for last()
api.intent.raise.new(intent);
}
};
exports.intent = {
enable: false,
last: function () {
return $q.when(cache.lastIntent);
},
clear: function () {
cache.lastIntent = undefined;
}
};
// Numerical keyboard - fix #30 // Numerical keyboard - fix #30
exports.keyboard.digit = { exports.keyboard.digit = {
...@@ -159,7 +463,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -159,7 +463,7 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
var paths = (modelPath || '').split('.'); var paths = (modelPath || '').split('.');
var property = paths.length && paths[paths.length - 1]; var property = paths.length && paths[paths.length - 1];
paths.reduce(function (res, path) { paths.reduce(function (res, path) {
if (path == property) { if (path === property) {
res[property] = value; res[property] = value;
return; return;
} }
...@@ -202,16 +506,38 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -202,16 +506,38 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
} }
}; };
exports.isAndroid = function () {
return !!navigator.userAgent.match(/Android/i) || ionic.Platform.is("android");
};
exports.isOSX = function () {
return !!navigator.userAgent.match(/Macintosh/i) || ionic.Platform.is("osx");
};
exports.isIOS = function () { exports.isIOS = function () {
return !!navigator.userAgent.match(/iPhone | iPad | iPod/i) || ionic.Platform.isIOS(); return !!navigator.userAgent.match(/iPhone | iPad | iPod/i) || (!!navigator.userAgent.match(/Mobile/i) && !!navigator.userAgent.match(/Macintosh/i)) || ionic.Platform.isIOS();
};
exports.isWindows = function () {
return !!navigator.userAgent.match(/Windows/i) || ionic.Platform.is("windows");
};
exports.isUbuntu = function () {
return !!navigator.userAgent.match(/Ubuntu|Linux x86_64/i) || ionic.Platform.is("ubuntu");
}; };
exports.isDesktop = function () { exports.isDesktop = function () {
if (!angular.isDefined(cache.isDesktop)) { if (!angular.isDefined(cache.isDesktop)) {
try { try {
cache.isDesktop = !exports.enable && (
exports.isUbuntu() ||
exports.isWindows() ||
exports.isOSX() ||
// Should have NodeJs and NW // Should have NodeJs and NW
cache.isDesktop = !exports.enable && !!process && !!nw && !!nw.App; (!!process && !!nw && !!nw.App)
);
} catch (err) { } catch (err) {
// If error (e.g. 'process not defined')
cache.isDesktop = false; cache.isDesktop = false;
} }
} }
...@@ -228,31 +554,99 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -228,31 +554,99 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
}; };
exports.start = function () { exports.start = function () {
startPromise = ionicReady() startPromise = ionicReady()
.then(function () { .then(function () {
exports.enable = window.cordova && cordova && cordova.plugins; exports.enable = window.cordova && cordova && !!cordova.plugins || false;
if (exports.enable) { if (exports.enable) {
console.debug('[device] Cordova plugins: ' + Object.keys(cordova.plugins));
console.debug('[device] Windows plugins: ' + Object.keys(window.plugins));
exports.camera.enable = !!navigator.camera; exports.camera.enable = !!navigator.camera;
exports.keyboard.enable = cordova && cordova.plugins && !!cordova.plugins.Keyboard; exports.keyboard.enable = cordova && cordova.plugins && !!cordova.plugins.Keyboard || false;
exports.barcode.enable = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner; exports.barcode.enable = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner && (!exports.isOSX() || exports.isIOS()) || false;
exports.clipboard.enable = cordova && cordova.plugins && !!cordova.plugins.clipboard; exports.clipboard.enable = cordova && cordova.plugins && !!cordova.plugins.clipboard || false;
exports.intent.enable = window && !!window.plugins.launchmyapp || false;
exports.clipboard.enable = cordova && cordova.plugins && !!cordova.plugins.clipboard || false;
exports.network.enable = navigator.connection && !!navigator.connection.type || false;
exports.file.enable = !!cordova.file && (exports.isAndroid() || exports.isIOS());
if (exports.keyboard.enable) { if (exports.keyboard.enable) {
angular.extend(exports.keyboard, cordova.plugins.Keyboard); angular.extend(exports.keyboard, cordova.plugins.Keyboard);
} }
console.debug('[device] Ionic platform ready, with [camera: {0}] [barcode scanner: {1}] [keyboard: {2}] [clipboard: {3}]' console.info('[device] Ionic platform ready, with {camera: {0}, barcode: {1}, keyboard: {2}, clipboard: {3}, intent: {4}, network: {5}, file: {6}}'
.format(exports.camera.enable, exports.barcode.enable, exports.keyboard.enable, exports.clipboard.enable)); .format(exports.camera.enable,
exports.barcode.enable,
exports.keyboard.enable,
exports.clipboard.enable,
exports.intent.enable,
exports.network.enable,
exports.file.enable
));
if (cordova.InAppBrowser) { if (cordova.InAppBrowser) {
console.debug('[device] Enabling InAppBrowser'); console.debug('[device] Enabling InAppBrowser');
window.open = cordova.InAppBrowser.open;
} }
// Add network listeners, using cordova network plugin
if (exports.network.enable) {
// Override constants, because it depends on OS version
exports.network.constants.NONE = Connection.NONE;
exports.network.constants.CELL = Connection.CELL;
exports.network.constants.CELL_2G = Connection.CELL_2G;
exports.network.constants.CELL_3G = Connection.CELL_3G;
exports.network.constants.CELL_4G = Connection.CELL_4G;
exports.network.constants.WIFI = Connection.WIFI;
exports.network.constants.ETHERNET = Connection.ETHERNET;
exports.network.constants.UNKNOWN = Connection.UNKNOWN;
var previousConnectionType;
document.addEventListener('offline', function () {
console.info('[device] Network is offline');
api.network.raise.offline();
previousConnectionType = 'none';
}, false);
document.addEventListener('online', function () {
console.info('[device] Network is online');
if (!previousConnectionType || previousConnectionType === 'none') {
api.network.raise.online();
previousConnectionType = exports.network.connectionType();
} }
else { else {
var connectionType = exports.network.connectionType();
if (connectionType !== previousConnectionType) {
console.info('[device] Network connection type changed: ' + connectionType);
api.network.raise.changed(connectionType);
}
}
}, false);
}
} else {
console.debug('[device] Ionic platform ready - no device detected.'); console.debug('[device] Ionic platform ready - no device detected.');
// Add network listeners, using browser events
window.addEventListener('offline', function () {
console.info('[device] Network is offline');
api.network.raise.offline();
}, false);
window.addEventListener('online', function () {
console.info('[device] Network is online');
api.network.raise.online();
}, false);
// Listen connection type change
if (navigator.connection) {
navigator.connection.addEventListener('change', function() {
var connectionType = exports.network.connectionType();
console.info('[device] Network connection type changed: ' + connectionType);
api.network.raise.changed(connectionType);
});
}
} }
started = true; started = true;
...@@ -262,6 +656,14 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti ...@@ -262,6 +656,14 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
return startPromise; return startPromise;
}; };
api.registerEvent('intent', 'new');
api.registerEvent('network', 'offline');
api.registerEvent('network', 'online');
api.registerEvent('network', 'changed');
// Export the event api (see ngApi)
exports.api = api;
return exports; return exports;
}) })
......
angular.module('cesium.http.services', ['cesium.cache.services']) angular.module('cesium.http.services', ['cesium.cache.services'])
.factory('csHttp', function($http, $q, $timeout, $window, csSettings, csCache, Device) { .factory('csHttp', function($http, $q, $timeout, $window, $translate, csConfig, csSettings, csCache, Device) {
'ngInject'; 'ngInject';
var timeout = csSettings.data.timeout;
var var
sockets = [], sockets = [],
cachePrefix = 'csHttp' defaultCachePrefix = 'csHttp-',
; allCachePrefixes = {},
regexp = {
if (!timeout) { POSITIVE_INTEGER: /^\d+$/,
timeout=4000; // default VERSION_PART_REGEXP: /^[0-9]+|alpha[0-9]+|beta[0-9]+|rc[0-9]+|[0-9]+-SNAPSHOT$/
},
errorCodes = {
TIMEOUT: -1, // Timeout reached
FORBIDDEN: 403,
NOT_FOUND: 404,
TOO_MANY_REQUESTS: 429
} }
;
function getServer(host, port) { function getServer(host, port) {
// Remove port if 80 or 443 // Hide port if =80 or =443
return !host ? null : (host + (port && port != 80 && port != 443 ? ':' + port : '')); return !host ? null : (host + (port && port != 80 && port != 443 ? (':' + port) : ''));
} }
function getUrl(host, port, path, useSsl) { function getUrl(host, port, path, useSsl) {
var protocol = (port == 443 || useSsl) ? 'https' : 'http'; var protocol = (port == 443 || useSsl) ? 'https' : 'http';
return protocol + '://' + getServer(host, port) + (path ? path : ''); // Add starting slash to path
path = path && path !== '' && !path.startsWith('/') ? ('/' + path) : (path || '');
return protocol + '://' + getServer(host, port) + path;
} }
function getWsUrl(host, port, path, useSsl) { function getWsUrl(host, port, path, useSsl) {
var protocol = (port == 443 || useSsl) ? 'wss' : 'ws'; var protocol = (port == 443 || useSsl) ? 'wss' : 'ws';
return protocol + '://' + getServer(host, port) + (path ? path : ''); path = path && path !== '' && !path.startsWith('/') ? ('/' + path) : (path || '');
return protocol + '://' + getServer(host, port) + path;
}
function processError(reject, data, url, status, config, startTime) {
// Detected timeout error
var urlWithParenthesis = url ? ' ('+url+')' : '';
var reachTimeout = status === -1 && (config && config.timeout > 0 && startTime > 0) && (Date.now() - startTime) >= config.timeout;
if (reachTimeout) {
console.error('[http] Request timeout on [{0}] after waiting {1}ms'.format(url, config.timeout));
$translate('ERROR.TIMEOUT_REACHED', {timeout: config.timeout, url: url || ''})
.then(function(message) {
reject({ucode: errorCodes.TIMEOUT, message: message});
})
.catch(function() {
// No translation: use hardcoded message
reject({ucode: errorCodes.TIMEOUT, message: 'Request timeout ({0})'.format(url)});
});
} }
else if (data && data.message) {
function processError(reject, data, url, status) {
if (data && data.message) {
reject(data); reject(data);
} }
else { else {
if (status == 404) { if (status === errorCodes.FORBIDDEN) {
reject({ucode: 404, message: 'Resource not found' + (url ? ' ('+url+')' : '')}); reject({ucode: errorCodes.FORBIDDEN, message: 'Resource is forbidden' + urlWithParenthesis});
}
else if (status === errorCodes.NOT_FOUND) {
reject({ucode: errorCodes.NOT_FOUND, message: 'Resource not found' + urlWithParenthesis});
}
else if (status === errorCodes.TOO_MANY_REQUESTS) {
console.error('[http] Too many request' + urlWithParenthesis);
$translate('ERROR.TOO_MANY_REQUESTS', {url: url || ''})
.then(function(message) {
reject({ucode: errorCodes.TOO_MANY_REQUESTS, message: message});
})
.catch(function() {
// No translation: use hardcoded message
reject({ucode: errorCodes.TOO_MANY_REQUESTS, message: 'Too many requests' + urlWithParenthesis});
});
} }
else if (url) { else if (url) {
reject('Error while requesting [' + url + ']'); console.error('[http] Get HTTP error {status: {0}}'.format(status) + urlWithParenthesis);
reject('Error while requesting network' + urlWithParenthesis);
} }
else { else {
reject('Unknown error from node'); reject('Unknown HTTP error');
} }
} }
} }
function prepare(url, params, config, callback) { function prepare(url, params, config, callback) {
var pkeys = [], queryParams = {}, newUri = url; var pkeys = [], queryParams = {}, newUri = url;
if (typeof params == 'object') { if (typeof params === 'object') {
pkeys = _.keys(params); pkeys = _.keys(params);
} }
_.forEach(pkeys, function(pkey){ _.forEach(pkeys, function(pkey){
var prevURI = newUri; var prevURI = newUri;
newUri = newUri.replace(':' + pkey, params[pkey]); newUri = newUri.replace(':' + pkey, params[pkey]);
if (prevURI == newUri) { if (prevURI === newUri) {
queryParams[pkey] = params[pkey]; queryParams[pkey] = params[pkey];
} }
}); });
...@@ -69,43 +106,50 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -69,43 +106,50 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
return $q.reject('[http] invalid URL from host: ' + host); return $q.reject('[http] invalid URL from host: ' + host);
} }
var url = getUrl(host, port, path, useSsl); var url = getUrl(host, port, path, useSsl);
return function(params) { return function(params, config) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
var config = { var mergedConfig = {
timeout: forcedTimeout || timeout, timeout: forcedTimeout || csConfig.timeout,
responseType: 'json' responseType: 'json'
}; };
if (typeof config === 'string') angular.merge(mergedConfig, config);
prepare(url, params, config, function(url, config) { prepare(url, params, mergedConfig, function(url, config) {
var startTime = Date.now();
$http.get(url, config) $http.get(url, config)
.success(function(data, status, headers, config) { .success(function(data) {
resolve(data); resolve(data);
}) })
.error(function(data, status, headers, config) { .error(function(data, status) {
processError(reject, data, url, status); processError(reject, data, url, status, config, startTime);
}); });
}); });
}); });
}; };
} }
function getResourceWithCache(host, port, path, useSsl, maxAge, autoRefresh, forcedTimeout) { function getResourceWithCache(host, port, path, useSsl, maxAge, autoRefresh, forcedTimeout, cachePrefix) {
var url = getUrl(host, port, path, useSsl); var url = getUrl(host, port, path, useSsl);
cachePrefix = cachePrefix || defaultCachePrefix;
maxAge = maxAge || csCache.constants.LONG; maxAge = maxAge || csCache.constants.LONG;
allCachePrefixes[cachePrefix] = true;
//console.debug('[http] will cache ['+url+'] ' + maxAge + 'ms' + (autoRefresh ? ' with auto-refresh' : '')); //console.debug('[http] will cache ['+url+'] ' + maxAge + 'ms' + (autoRefresh ? ' with auto-refresh' : ''));
return function(params) { return function(params) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
var config = { var config = {
timeout: forcedTimeout || timeout, timeout: forcedTimeout || csConfig.timeout,
responseType: 'json' responseType: 'json'
}; };
if (autoRefresh) { // redo the request if need if (autoRefresh) { // redo the request if need
config.cache = csCache.get(cachePrefix, maxAge, function (key, value) { config.cache = csCache.get(cachePrefix, maxAge, function(key, value, done) {
console.debug('[http] Refreshing cache for ['+key+'] '); console.debug('[http] Refreshing cache for {{0}} '.format(key));
$http.get(key, config) $http.get(key, config)
.success(function (data) { .success(function (data) {
config.cache.put(key, data); config.cache.put(key, data);
if (done) done(key, data);
}); });
}); });
} }
...@@ -114,12 +158,13 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -114,12 +158,13 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
} }
prepare(url, params, config, function(url, config) { prepare(url, params, config, function(url, config) {
var startTime = Date.now();
$http.get(url, config) $http.get(url, config)
.success(function(data) { .success(function(data) {
resolve(data); resolve(data);
}) })
.error(function(data, status) { .error(function(data, status) {
processError(reject, data, url, status); processError(reject, data, url, status, config, startTime);
}); });
}); });
}); });
...@@ -128,36 +173,40 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -128,36 +173,40 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
function postResource(host, port, path, useSsl, forcedTimeout) { function postResource(host, port, path, useSsl, forcedTimeout) {
var url = getUrl(host, port, path, useSsl); var url = getUrl(host, port, path, useSsl);
return function(data, params) { return function(data, params, config) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
var config = { var mergedConfig = {
timeout: forcedTimeout || timeout, timeout: forcedTimeout || csConfig.timeout, // We use a large timeout, when post, and NOT the settings timeout
headers : {'Content-Type' : 'application/json;charset=UTF-8'} headers : {'Content-Type' : 'application/json;charset=UTF-8'}
}; };
if (typeof config === 'object') angular.merge(mergedConfig, config);
prepare(url, params, config, function(url, config) { prepare(url, params, mergedConfig, function(url, config) {
var startTime = Date.now();
$http.post(url, data, config) $http.post(url, data, config)
.success(function(data) { .success(function(data) {
resolve(data); resolve(data);
}) })
.error(function(data, status) { .error(function(data, status) {
processError(reject, data, url, status); processError(reject, data, url, status, config, startTime);
}); });
}); });
}); });
}; };
} }
function ws(host, port, path, useSsl, timeout) { function ws(host, port, path, useSsl, forcedTimeout) {
if (!path) { if (!path) {
console.error('calling csHttp.ws without path argument'); console.error('calling csHttp.ws without path argument');
throw 'calling csHttp.ws without path argument'; throw 'calling csHttp.ws without path argument';
} }
var uri = getWsUrl(host, port, path, useSsl); var uri = getWsUrl(host, port, path, useSsl);
timeout = timeout || csSettings.data.timeout; var timeout = forcedTimeout || csConfig.timeout;
function _waitOpen(self) { function _waitOpen(self) {
if (!self.delegate) throw new Error('Websocket not opened'); if (!self.delegate) {
throw new Error('Websocket {0} was closed!'.format(uri));
}
if (self.delegate.readyState == 1) { if (self.delegate.readyState == 1) {
return $q.when(self.delegate); return $q.when(self.delegate);
} }
...@@ -227,7 +276,7 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -227,7 +276,7 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
_open(self, null, params); _open(self, null, params);
} }
else if (closeEvent) { else if (closeEvent) {
console.debug('[http] TODO -- Unexpected close of websocket [{0}]: error code: '.format(path), closeEvent); console.debug('[http] Unexpected close of websocket [{0}]: error code: '.format(path), closeEvent && closeEvent.code || closeEvent);
// Force new connection // Force new connection
self.delegate = null; self.delegate = null;
...@@ -307,29 +356,77 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -307,29 +356,77 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
// See doc : https://gist.github.com/jlong/2428561 // See doc : https://gist.github.com/jlong/2428561
function parseUri(uri) { function parseUri(uri) {
var protocol; var protocol, hostname;
if (uri.startsWith('duniter://')) {
protocol = 'duniter';
uri = uri.replace('duniter://', 'http://');
}
// Use a <a> element to parse
var parser = document.createElement('a'); var parser = document.createElement('a');
// G1 URI (see G1lien)
if (uri.startsWith('june:') || uri.startsWith('web+june:')) {
protocol = 'june:';
var path = uri.replace(/^(web\+june|june):(\/\/)?/, '');
// Store hostname here, because parse will apply a lowercase
hostname = path;
if (hostname.indexOf('/') !== -1) {
hostname = hostname.substring(0, path.indexOf('/'));
}
if (hostname.indexOf('?') !== -1) {
hostname = hostname.substring(0, path.indexOf('?'));
}
// Avoid checksum to be parsed as an port (integer): remove it from the path (see issue #1001)
if (hostname.indexOf(':') !== -1) {
// Removing checksum from the path, to be parseable
path = hostname.substring(0, path.indexOf(':')) + path.substring(hostname.length);
}
// Clean path (parsable by the <a> element)
parser.href = 'https://' + path;
}
else {
parser.href = uri; parser.href = uri;
}
var pathname = parser.pathname; var pathname = parser.pathname;
if (pathname && pathname.startsWith('/')) { if (pathname && pathname.startsWith('/')) {
pathname = pathname.substring(1); pathname = pathname.substring(1);
} }
var searchParams;
if (parser.search && parser.search.startsWith('?')) {
searchParams = parser.search.substring(1).split('&')
.reduce(function(res, searchParam) {
if (searchParam.indexOf('=') !== -1) {
var key = searchParam.substring(0, searchParam.indexOf('='));
var value = searchParam.substring(searchParam.indexOf('=') + 1);
try {
res[key] = decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component.
res[key] = value;
}
}
else {
res[searchParam] = true; // default value
}
return res;
}, {});
}
var result = { var result = {
protocol: protocol ? protocol : parser.protocol, protocol: protocol ? protocol : parser.protocol,
hostname: parser.hostname, hostname: hostname ? hostname : parser.hostname,
host: parser.host, host: parser.host,
port: parser.port, port: parser.port,
username: parser.username, username: parser.username,
password: parser.password, password: parser.password,
pathname: pathname, pathname: pathname,
pathSegments: pathname ? pathname.split('/') : [],
search: parser.search, search: parser.search,
searchParams: searchParams,
hash: parser.hash hash: parser.hash
}; };
parser.remove(); parser.remove();
...@@ -349,13 +446,13 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -349,13 +446,13 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
var parts = parseUri(uri); var parts = parseUri(uri);
if (!parts.protocol && options.type) { if (!parts.protocol && options.type) {
parts.protocol = (options.type == 'email') ? 'mailto:' : parts.protocol = (options.type === 'email') ? 'mailto:' :
((options.type == 'phone') ? 'tel:' : ''); ((options.type === 'phone') ? 'tel:' : '');
uri = parts.protocol + uri; uri = parts.protocol + uri;
} }
// On desktop, open into external tool // On desktop, open into external tool
if (parts.protocol == 'mailto:' && Device.isDesktop()) { if (parts.protocol === 'mailto:' && Device.isDesktop()) {
try { try {
nw.Shell.openExternal(uri); nw.Shell.openExternal(uri);
return; return;
...@@ -365,10 +462,10 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -365,10 +462,10 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
} }
} }
// Check if device is enable, on special tel: or mailto: protocole // Check if device is enable, on special tel: or mailto: protocol
var validProtocol = (parts.protocol == 'mailto:' || parts.protocol == 'tel:') && Device.enable; var validProtocol = (Device.enable && (parts.protocol === 'mailto:' || parts.protocol === 'tel:'));
if (!validProtocol) { if (!validProtocol) {
if (options.onError && typeof options.onError == 'function') { if (options.onError && typeof options.onError === 'function') {
options.onError(uri); options.onError(uri);
} }
return; return;
...@@ -451,10 +548,15 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -451,10 +548,15 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
// First, validate both numbers are true version numbers // First, validate both numbers are true version numbers
function validateParts(parts) { function validateParts(parts) {
for (var i = 0; i < parts.length; ++i) { for (var i = 0; i < parts.length; i++) {
if (!isPositiveInteger(parts[i])) { var isNumber = regexp.POSITIVE_INTEGER.test(parts[i]);
return false; // First part MUST be an integer
} if (i === 0 && !isNumber) return false;
// If not integer, should be 'alpha', 'beta', etc.
if (!isNumber && !regexp.VERSION_PART_REGEXP.test(parts[i])) return false;
// Convert string to int (need by compare operators)
if (isNumber) parts[i] = parseInt(parts[i]);
} }
return true; return true;
} }
...@@ -484,15 +586,23 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -484,15 +586,23 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
} }
function isVersionCompatible(minVersion, actualVersion) { function isVersionCompatible(minVersion, actualVersion) {
console.debug('[http] Checking actual version [{0}] is compatible with min expected version [{1}]'.format(actualVersion, minVersion)); var result = compareVersionNumbers(minVersion, actualVersion) <= 0;
return compareVersionNumbers(minVersion, actualVersion) <= 0; //console.debug('[http] Duniter version {0} is {1}compatible (min expected version {2})'.format(actualVersion, result ? '': 'NOT ', minVersion));
return result;
} }
var cache = angular.copy(csCache.constants); function clearCache(cachePrefix) {
cache.clear = function() { cachePrefix = cachePrefix || defaultCachePrefix;
console.debug('[http] Cleaning cache...'); console.debug("[http] Cleaning cache {prefix: '{0}'}...".format(cachePrefix));
csCache.clear(cachePrefix); csCache.clear(cachePrefix);
}; }
function clearAllCache() {
console.debug('[http] Cleaning all caches...');
_.keys(allCachePrefixes).forEach(function(cachePrefix) {
csCache.clear(cachePrefix);
});
}
return { return {
get: getResource, get: getResource,
...@@ -513,7 +623,10 @@ angular.module('cesium.http.services', ['cesium.cache.services']) ...@@ -513,7 +623,10 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
compare: compareVersionNumbers, compare: compareVersionNumbers,
isCompatible: isVersionCompatible isCompatible: isVersionCompatible
}, },
cache: cache cache: angular.merge({
clear: clearCache,
clearAll: clearAllCache
}, csCache.constants)
}; };
}) })
; ;
...@@ -6,7 +6,7 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -6,7 +6,7 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
}) })
.controller('AboutModalCtrl', function ($scope, UIUtils, csHttp) { .controller('AboutModalCtrl', function ($scope, csConfig, UIUtils, csHttp, filterTranslations) {
'ngInject'; 'ngInject';
$scope.openLink = function(event, uri, options) { $scope.openLink = function(event, uri, options) {
...@@ -19,6 +19,9 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -19,6 +19,9 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
return csHttp.uri.open(uri, options); return csHttp.uri.open(uri, options);
}; };
// Change UTC date into user date
$scope.buildDate = moment(csConfig.build).format(filterTranslations.DATE_PATTERN || 'YYYY-MM-DD HH:mm');
}) })
.factory('ModalUtils', function($ionicModal, $rootScope, $q, $injector, $controller, $timeout, Device) { .factory('ModalUtils', function($ionicModal, $rootScope, $q, $injector, $controller, $timeout, Device) {
...@@ -65,6 +68,8 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -65,6 +68,8 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
return $scope.modal.remove() return $scope.modal.remove()
.then(function() { .then(function() {
// Workaround modal-open not removed
document.body.classList.remove('modal-open');
$scope.deferred.resolve(result); $scope.deferred.resolve(result);
return result; return result;
}); });
...@@ -94,7 +99,10 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -94,7 +99,10 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
$timeout(function() { $timeout(function() {
$scope.deferred.resolve(); $scope.deferred.resolve();
return $scope.modal.remove(); return $scope.modal.remove().then(function() {
// Workaround modal-open not removed
document.body.classList.remove('modal-open');
});
}, ($scope.modal.hideDelay || 320) + 20); }, ($scope.modal.hideDelay || 320) + 20);
} }
}); });
...@@ -106,11 +114,11 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -106,11 +114,11 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
options = options ? options : {} ; options = options ? options : {} ;
options.animation = options.animation || 'slide-in-up'; options.animation = options.animation || 'slide-in-up';
// var focusFirstInput = false; var focusFirstInput = false;
// // removeIf(device) // removeIf(device)
// focusFirstInput = angular.isDefined(options.focusFirstInput) ? options.focusFirstInput : false; focusFirstInput = angular.isDefined(options.focusFirstInput) ? options.focusFirstInput : false;
// // endRemoveIf(device) // endRemoveIf(device)
// options.focusFirstInput = focusFirstInput; options.focusFirstInput = focusFirstInput;
// If modal has a controller // If modal has a controller
if (controller) { if (controller) {
...@@ -224,6 +232,11 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -224,6 +232,11 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
parameters); parameters);
} }
function showCertificationCheckList(parameters) {
return ModalUtils.show('templates/wot/modal_certification_checklist.html', 'WotCertificationChecklistCtrl',
parameters);
}
function showSelectPubkeyIdentity(parameters) { function showSelectPubkeyIdentity(parameters) {
return ModalUtils.show('templates/wot/modal_select_pubkey_identity.html', 'WotSelectPubkeyIdentityModalCtrl', return ModalUtils.show('templates/wot/modal_select_pubkey_identity.html', 'WotSelectPubkeyIdentityModalCtrl',
parameters); parameters);
...@@ -286,6 +299,7 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -286,6 +299,7 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
showHelp: showHelp, showHelp: showHelp,
showAccountSecurity: showAccountSecurity, showAccountSecurity: showAccountSecurity,
showLicense: showLicense, showLicense: showLicense,
showCertificationCheckList: showCertificationCheckList,
showSelectPubkeyIdentity: showSelectPubkeyIdentity, showSelectPubkeyIdentity: showSelectPubkeyIdentity,
showSelectWallet: showSelectWallet, showSelectWallet: showSelectWallet,
showPassword: showPassword showPassword: showPassword
...@@ -293,18 +307,41 @@ angular.module('cesium.modal.services', ['cesium.utils.services']) ...@@ -293,18 +307,41 @@ angular.module('cesium.modal.services', ['cesium.utils.services'])
}) })
.factory('csPopovers', function($rootScope, $translate, $ionicPopup, $timeout, UIUtils) { .factory('csPopovers', function($rootScope, $translate, $ionicPopup, $timeout, UIUtils, $controller) {
'ngInject'; 'ngInject';
function showSelectWallet(event, options) { function showSelectWallet(event, options) {
options = options || {}; options = options || {};
var parameters = options.parameters || {};
delete options.parameters;
var scope = options.scope && options.scope.$new() || $rootScope.$new(true); var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
scope.parameters = options; options.scope = scope;
delete options.scope; options.templateUrl = 'templates/wallet/list/popover_wallets.html';
return UIUtils.popover.show(event, angular.merge({ options.autoremove = true;
templateUrl :'templates/wallet/list/popover_wallets.html',
autoremove: true // Initialize the popover controller, with parameters
}, options)); angular.extend(this, $controller('WalletSelectPopoverCtrl', {$scope: options.scope, parameters: parameters}));
var afterShowSaved = options.afterShow;
options.afterShow = function(popover) {
// Add a missing method, to close the popover
scope.closePopover = function(res) {
popover.scope.closePopover(res);
};
// Execute default afterShow fn, if any
if (afterShowSaved) afterShowSaved(popover);
};
// Show the popover
return UIUtils.popover.show(event, options)
.then(function(res) {
// Then destroy the scope
scope.$destroy();
return res;
});
} }
return { return {
......
angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', 'cesium.http.services']) angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', 'cesium.http.services'])
.factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, BMA, csHttp, csCurrency, Api) { .factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, csSettings, BMA, Device, csHttp, csCurrency, Api) {
'ngInject'; 'ngInject';
function CsNetwork(id) {
var var
interval, interval,
constants = { constants = {
...@@ -13,9 +11,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -13,9 +11,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
MAX_BLOCK_OFFSET: 1000 MAX_BLOCK_OFFSET: 1000
}, },
isHttpsMode = $window.location.protocol === 'https:', isHttpsMode = $window.location.protocol === 'https:',
api = new Api(this, "csNetwork-" + id), api = new Api(this, "csNetwork"),
startPromise,
data = { data = {
pid: 0, // = not started
bma: null, bma: null,
listeners: [], listeners: [],
loading: true, loading: true,
...@@ -43,7 +43,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -43,7 +43,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
searchingPeersOnNetwork: false, searchingPeersOnNetwork: false,
difficulties: null, difficulties: null,
ws2pHeads: null, ws2pHeads: null,
timeout: csConfig.timeout timeout: csConfig.timeout, // Max timeout
startTime: null,
autoRefresh: true,
flushIntervalMs: 1000,
withSandboxes: null
}, },
// Return the block uid // Return the block uid
...@@ -51,7 +55,18 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -51,7 +55,18 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return block && [block.number, block.hash].join('-'); return block && [block.number, block.hash].join('-');
}, },
// Return the block uid
buidBlockNumber = function(buid) {
return (typeof buid === 'string') && parseInt(buid.split('-')[0]);
},
remainingTime = function() {
return Math.max(0, data.timeout - (Date.now() - data.startTime));
},
resetData = function() { resetData = function() {
// Keep previous PID
//data.pid = 0;
data.bma = null; data.bma = null;
data.listeners = []; data.listeners = [];
data.peers.splice(0); data.peers.splice(0);
...@@ -79,7 +94,24 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -79,7 +94,24 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
data.searchingPeersOnNetwork = false; data.searchingPeersOnNetwork = false;
data.difficulties = null; data.difficulties = null;
data.ws2pHeads = null; data.ws2pHeads = null;
data.timeout = csConfig.timeout; data.timeout = data.timeout || getDeviceTimeout(); // Keep it is already set
data.startTime = null;
data.autoRefresh = true;
data.flushIntervalMs = null;
},
/**
* Compute a timeout, depending on connection type (wifi, ethernet, cell, etc.)
*/
getDeviceTimeout = function () {
// Using timeout from settings
if (csSettings.data.expertMode && csSettings.data.timeout > 0) {
console.debug('[network] Using user defined timeout: {0}ms'.format(csSettings.data.timeout));
return csSettings.data.timeout;
}
// Computing timeout from the connection type
return Device.network.timeout();
}, },
hasPeers = function() { hasPeers = function() {
...@@ -118,10 +150,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -118,10 +150,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
}) })
.catch(function(err) { .catch(function(err) {
// When too many request, retry in 3s // When too many request, retry in 3s
if (err && err.ucode == BMA.errorCodes.HTTP_LIMITATION) { if (err && err.ucode === BMA.errorCodes.HTTP_LIMITATION) {
return $timeout(function() { return $timeout(function() {
return loadW2spHeads(); if (remainingTime() > 0) return loadW2spHeads();
}, 3000); }, BMA.constants.LIMIT_REQUEST_DELAY);
} }
console.error(err); // can occur on duniter v1.6 console.error(err); // can occur on duniter v1.6
data.ws2pHeads = {}; data.ws2pHeads = {};
...@@ -139,10 +171,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -139,10 +171,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
}) })
.catch(function(err) { .catch(function(err) {
// When too many request, retry in 3s // When too many request, retry in 3s
if (err && err.ucode == BMA.errorCodes.HTTP_LIMITATION) { if (err && err.ucode === BMA.errorCodes.HTTP_LIMITATION) {
return $timeout(function() { return $timeout(function() {
return loadDifficulties(); if (remainingTime() > 0) return loadDifficulties();
}, 3000); }, BMA.constants.LIMIT_REQUEST_DELAY);
} }
console.error(err); console.error(err);
data.difficulties = {}; data.difficulties = {};
...@@ -155,6 +187,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -155,6 +187,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
data.loading = true; data.loading = true;
data.bma = data.bma || BMA; data.bma = data.bma || BMA;
var newPeers = []; var newPeers = [];
var pid = data.pid;
if (interval) { if (interval) {
$interval.cancel(interval); $interval.cancel(interval);
...@@ -168,12 +201,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -168,12 +201,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
else if (data.loading && !data.searchingPeersOnNetwork) { else if (data.loading && !data.searchingPeersOnNetwork) {
data.loading = false; data.loading = false;
$interval.cancel(interval); $interval.cancel(interval);
// The peer lookup end, we can make a clean final report // The peer lookup end, we can make a clean final report
sortPeers(true/*update main buid*/); sortPeers(true/*update main buid*/);
console.debug('[network] Finish: {0} peers found.'.format(data.peers.length)); console.debug('[network] [#{0}] {1} peer(s) found.'.format(pid, data.peers.length));
} }
}, 1000); }, data.flushIntervalMs || 1000);
var initJobs = [ var initJobs = [
// Load uids // Load uids
...@@ -197,7 +232,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -197,7 +232,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return $q.all(initJobs) return $q.all(initJobs)
.then(function() { .then(function() {
return data.bma.network.peers(); return data.bma && data.bma.network.peers();
}) })
.then(function(res){ .then(function(res){
if (!res || !res.peers || !res.peers.length) return; if (!res || !res.peers || !res.peers.length) return;
...@@ -208,9 +243,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -208,9 +243,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
_.forEach(res.peers, function(json) { _.forEach(res.peers, function(json) {
// Exclude, if not UP or on a too old block // Exclude, if not UP or on a too old block
if (json.status !== 'UP') return; if (json.status !== 'UP') return;
json.blockNumber = json.block && parseInt(json.block.split('-')[0]);
// Exclude if too old peering document
json.blockNumber = buidBlockNumber(json.block);
if (json.blockNumber && json.blockNumber < data.minOnlineBlockNumber) { if (json.blockNumber && json.blockNumber < data.minOnlineBlockNumber) {
console.debug("[network] Exclude a too old peer document, on pubkey {0}".format(json.pubkey.substring(0,6))); console.debug("[network] [#{0}] Exclude a too old peering document, on pubkey {1}".format(pid, json.pubkey.substring(0,8)));
return; return;
} }
...@@ -220,7 +257,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -220,7 +257,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
_.forEach(json.endpoints||[], function(ep) { _.forEach(json.endpoints||[], function(ep) {
if (ep.startsWith('WS2P')) { if (ep.startsWith('WS2P')) {
var key = json.pubkey + '-' + ep.split(' ')[1]; var key = json.pubkey + '-' + ep.split(' ')[1];
if (data.ws2pHeads[key]) { if (data.ws2pHeads && data.ws2pHeads[key]) {
data.ws2pHeads[key].hasEndpoint = true; data.ws2pHeads[key].hasEndpoint = true;
} }
} }
...@@ -231,14 +268,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -231,14 +268,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
var privateWs2pHeads = _.values(data.ws2pHeads); var privateWs2pHeads = _.values(data.ws2pHeads);
if (privateWs2pHeads && privateWs2pHeads.length) { if (privateWs2pHeads && privateWs2pHeads.length) {
var privateEPCount = 0; var privateEPCount = 0;
//console.debug("[http] Found WS2P endpoints without endpoint:", data.ws2pHeads); //console.debug("[network] Found WS2P endpoints without endpoint:", data.ws2pHeads);
_.forEach(privateWs2pHeads, function(head) { _.forEach(privateWs2pHeads, function(head) {
if (!head.hasEndPoint) { if (!head.hasEndPoint) {
var currentNumber = head.buid && parseInt(head.buid.split('-')[0]); var currentNumber = buidBlockNumber(head.buid);
// Exclude if on a too old block // Exclude if on a too old block
if (currentNumber && currentNumber < data.minOnlineBlockNumber) { if (currentNumber && currentNumber < data.minOnlineBlockNumber) {
console.debug("[network] Exclude a too old WS2P message, on pubkey {0}".format(head.pubkey.substring(0,6))); console.debug("[network] [#{0}] Exclude a too old WS2P message, on pubkey {1}".format(pid, head.pubkey.substring(0,8)));
return; return;
} }
...@@ -272,7 +309,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -272,7 +309,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
}); });
if (privateEPCount) { if (privateEPCount) {
console.debug("[http] Found {0} WS2P endpoints without endpoint (private ?)".format(privateEPCount)); console.debug("[network] [#{0}] Found {1} WS2P endpoints without endpoint (private ?)".format(pid, privateEPCount));
} }
} }
...@@ -287,7 +324,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -287,7 +324,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
} }
}) })
.then(function(){ .then(function(){
if (!isStarted()) return; // Skip if stopped
data.searchingPeersOnNetwork = false; data.searchingPeersOnNetwork = false;
data.loading = false;
if (newPeers.length) {
flushNewPeersAndSort(newPeers, true/*update main buid*/);
}
return data.peers;
}) })
.catch(function(err){ .catch(function(err){
console.error(err); console.error(err);
...@@ -345,11 +388,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -345,11 +388,14 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
list = list || data.newPeers; list = list || data.newPeers;
// Analyze the peer document, and exclude using the online filter // Analyze the peer document, and exclude using the online filter
json.blockNumber = json.block && parseInt(json.block.split('-')[0]); json.blockNumber = buidBlockNumber(json.block);
json.oldBlock = (json.status === 'UP' && json.blockNumber && json.blockNumber < data.minOnlineBlockNumber); json.oldBlock = (json.status === 'UP' && json.blockNumber && json.blockNumber < data.minOnlineBlockNumber);
delete json.version; // DO not keep peering document version
var peers = createPeerEntities(json); var peers = createPeerEntities(json);
var hasUpdates = false; var hasUpdates = false;
var pid = data.pid;
var running = true;
var jobs = peers.reduce(function(jobs, peer) { var jobs = peers.reduce(function(jobs, peer) {
var existingPeer = _.findWhere(data.peers, {id: peer.id}); var existingPeer = _.findWhere(data.peers, {id: peer.id});
...@@ -359,35 +405,52 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -359,35 +405,52 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return jobs.concat( return jobs.concat(
refreshPeer(peer) refreshPeer(peer)
.then(function (refreshedPeer) { .then(function (refreshedPeer) {
running = running && data.listeners && data.listeners.length > 0;
if (!running) return; // Skip if stopped
var api = refreshedPeer &&
refreshedPeer.bma && (
(refreshedPeer.bma.useBma && 'BMA') ||
(refreshedPeer.bma.useGva && 'GVA') ||
(refreshedPeer.bma.useWs2p && 'WS2P')
) || 'null';
if (existingPeer) { if (existingPeer) {
// remove existing peers, when reject or offline // remove existing peers, when reject or offline
if (!refreshedPeer || (refreshedPeer.online !== data.filter.online && data.filter.online !== 'all')) { if (!refreshedPeer || (refreshedPeer.online !== data.filter.online && data.filter.online !== 'all')) {
console.debug('[network] Peer [{0}] removed (cause: {1})'.format(peer.server, !refreshedPeer ? 'filtered' : (refreshedPeer.online ? 'UP': 'DOWN'))); var existingIndex = data.peers.indexOf(existingPeer);
data.peers.splice(data.peers.indexOf(existingPeer), 1); if (existingIndex !== -1) {
if (!stop) console.debug('[network] [#{0}] Peer [{1}] removed (cause: {2})'.format(pid, peer.server, !refreshedPeer ? 'filtered' : (refreshedPeer.online ? 'UP' : 'DOWN')));
data.peers.splice(existingIndex, 1);
hasUpdates = true; hasUpdates = true;
} }
}
else if (refreshedPeer.buid !== existingMainBuid){ else if (refreshedPeer.buid !== existingMainBuid){
console.debug('[network] {0} endpoint [{1}] new current block'.format( console.debug('[network] [#{0}] {1} endpoint [{2}] new current block'.format(
refreshedPeer.bma && (refreshedPeer.bma.useBma ? 'BMA' : 'WS2P') || 'null', pid,
api,
refreshedPeer.server)); refreshedPeer.server));
hasUpdates = true; hasUpdates = true;
} }
else if (existingOnline !== refreshedPeer.online){ else if (existingOnline !== refreshedPeer.online){
console.debug('[network] {0} endpoint [{1}] is now {2}'.format( console.debug('[network] [#{0}] {1} endpoint [{2}] is now {3}'.format(
refreshedPeer.bma && (refreshedPeer.bma.useBma ? 'BMA' : 'WS2P') || 'null', pid,
api,
refreshedPeer.server, refreshedPeer.server,
refreshedPeer.online ? 'UP' : 'DOWN')); refreshedPeer.online ? 'UP' : 'DOWN'));
hasUpdates = true; hasUpdates = true;
} }
else { else {
console.debug("[network] {0} endpoint [{1}] unchanged".format( console.debug("[network] [#{0}] {1} endpoint [{2}] unchanged".format(
refreshedPeer.bma && (refreshedPeer.bma.useBma ? 'BMA' : 'WS2P') || 'null', pid,
api,
refreshedPeer.server)); refreshedPeer.server));
} }
} }
else if (refreshedPeer && (refreshedPeer.online === data.filter.online || data.filter.online === 'all')) { else if (refreshedPeer && (refreshedPeer.online === data.filter.online || data.filter.online === 'all')) {
console.debug("[network] {0} endpoint [{1}] is {2}".format( console.debug("[network] [#{0}] {1} endpoint [{2}] is {3}".format(
refreshedPeer.bma && (refreshedPeer.bma.useBma ? 'BMA' : 'WS2P') || 'null', pid,
api,
refreshedPeer.server, refreshedPeer.server,
refreshedPeer.online ? 'UP' : 'DOWN' refreshedPeer.online ? 'UP' : 'DOWN'
)); ));
...@@ -399,7 +462,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -399,7 +462,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
}, []); }, []);
return (jobs.length === 1 ? jobs[0] : $q.all(jobs)) return (jobs.length === 1 ? jobs[0] : $q.all(jobs))
.then(function() { .then(function() {
return hasUpdates; return !stop && hasUpdates;
}); });
}, },
...@@ -432,7 +495,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -432,7 +495,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
peer.server = peer.getServer(); peer.server = peer.getServer();
peer.dns = peer.getDns(); peer.dns = peer.getDns();
peer.buid = peer.buid || peer.block; peer.buid = peer.buid || peer.block;
peer.blockNumber = peer.buid && parseInt(peer.buid.split('-')[0]); peer.blockNumber = buidBlockNumber(peer.buid);
peer.uid = peer.pubkey && data.uidsByPubkeys[peer.pubkey]; peer.uid = peer.pubkey && data.uidsByPubkeys[peer.pubkey];
peer.id = peer.keyID(); peer.id = peer.keyID();
return [peer]; return [peer];
...@@ -454,7 +517,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -454,7 +517,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
delete data.ws2pHeads[ws2pHeadKey]; delete data.ws2pHeads[ws2pHeadKey];
if (head) { if (head) {
peer.buid = head.buid; peer.buid = head.buid;
peer.currentNumber=head.buid && parseInt(head.buid.split('-')[0]); peer.currentNumber = buidBlockNumber(head.buid);
peer.version = head.version; peer.version = head.version;
peer.powPrefix = head.powPrefix; peer.powPrefix = head.powPrefix;
} }
...@@ -468,7 +531,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -468,7 +531,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
} }
// Cesium running in SSL: Do not try to access not SSL node, // Cesium running in SSL: Do not try to access not SSL node,
if (!peer.bma.useWs2p && isHttpsMode && !peer.bma.useSsl) { if (peer.bma.useBma && isHttpsMode && !peer.bma.useSsl) {
peer.online = (peer.status === 'UP'); peer.online = (peer.status === 'UP');
peer.buid = constants.UNKNOWN_BUID; peer.buid = constants.UNKNOWN_BUID;
delete peer.version; delete peer.version;
...@@ -480,8 +543,8 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -480,8 +543,8 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return $q.when(peer); return $q.when(peer);
} }
// Do not try to access TOR or WS2P endpoints // Do not try to access TOR, WS2P or GVA endpoints
if (peer.bma.useTor || peer.bma.useWs2p) { if (peer.bma.useTor || peer.bma.useWs2p || peer.bma.useGva) {
peer.online = (peer.status === 'UP'); peer.online = (peer.status === 'UP');
peer.buid = constants.UNKNOWN_BUID; peer.buid = constants.UNKNOWN_BUID;
delete peer.version; delete peer.version;
...@@ -492,11 +555,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -492,11 +555,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return $q.when(peer); return $q.when(peer);
} }
peer.api = peer.api || BMA.lightInstance(peer.getHost(), peer.getPort(), peer.isSsl(), data.timeout); var timeout = Math.max(1000, remainingTime()); // >= 500ms
peer.api = peer.api || BMA.lightInstance(peer.getHost(), peer.getPort(), peer.getPath(), peer.isSsl(), timeout);
// Get current block // Get current block
return peer.api.blockchain.current() return peer.api.blockchain.current(false/*no cache*/)
.then(function(block) { .then(function(block) {
if (!block) throw new Error('Wrong response for /blockchain/current (empty)');
peer.currentNumber = block.number; peer.currentNumber = block.number;
peer.online = true; peer.online = true;
peer.buid = buid(block); peer.buid = buid(block);
...@@ -516,13 +581,19 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -516,13 +581,19 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
} }
if (!peer.secondTry) { if (!peer.secondTry) {
var bma = peer.bma || peer.getBMA(); var bma = peer.bma || peer.getBMA();
// Retry using DNS (instead of IP v4 or v6)
if (bma.dns && peer.server.indexOf(bma.dns) === -1) { if (bma.dns && peer.server.indexOf(bma.dns) === -1) {
var secondTryTimeout = remainingTime();
// try again, using DNS instead of IPv4 / IPV6 // try again, using DNS instead of IPv4 / IPV6
if (secondTryTimeout > 0) {
peer.secondTry = true; peer.secondTry = true;
peer.api = BMA.lightInstance(bma.dns, bma.port, bma.useSsl); peer.api = BMA.lightInstance(bma.dns, bma.port, bma.path, bma.useSsl, secondTryTimeout);
return refreshPeer(peer); // recursive call return refreshPeer(peer); // recursive call
} }
} }
}
peer.buid = null; peer.buid = null;
peer.blockNumber = null; peer.blockNumber = null;
...@@ -532,12 +603,12 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -532,12 +603,12 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
return peer; return peer;
}) })
.then(function(peer) { .then(function(peer) {
// Exit if offline, or not expert mode or too small device // Exit if offline, or not expert mode
if (!data.filter.online || !peer || !peer.online || !data.expertMode) return peer; if (!peer || (data.filter.online && !peer.online)) return peer;
var jobs = []; var jobs = [];
// Get hardship (only for a member peer) // Get hardship (only for a member peer, and expert mode)
if (peer.uid) { if (peer.uid && data.expertMode) {
jobs.push(peer.api.blockchain.stats.hardship({pubkey: peer.pubkey}) jobs.push(peer.api.blockchain.stats.hardship({pubkey: peer.pubkey})
.then(function (res) { .then(function (res) {
peer.difficulty = res ? res.level : null; peer.difficulty = res ? res.level : null;
...@@ -547,16 +618,52 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -547,16 +618,52 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
})); }));
} }
// Get Version // Get software, version and storage
if (data.expertMode || data.filter.bma) {
jobs.push(peer.api.node.summary() jobs.push(peer.api.node.summary()
.then(function (res) { .then(function (res) {
peer.software = res && res.duniter && res.duniter.software || undefined; peer.software = res && res.duniter && res.duniter.software || undefined;
peer.version = res && res.duniter && res.duniter.version || '?'; peer.version = res && res.duniter && res.duniter.version || '?';
peer.storage = res && res.duniter && res.duniter.storage || {transactions: false, wotwizard: false};
}) })
.catch(function () { .catch(function () {
peer.software = undefined; peer.software = undefined;
peer.version = '?'; // continue peer.version = '?';
peer.storage = {transactions: false, wotwizard: false};
// continue
})); }));
}
// Get sandboxes
if (data.expertMode && data.withSandboxes !== false) {
jobs.push(peer.api.node.sandboxes()
.catch(function(err) {
return {}; // continue
})
.then(function(res) {
peer.sandboxes = res || {};
peer.sandboxes.identities = peer.sandboxes.identities || {};
peer.sandboxes.memberships = peer.sandboxes.memberships || {};
peer.sandboxes.transactions = peer.sandboxes.transactions || {};
var full = false;
Object.keys(peer.sandboxes).forEach(function (key) {
var sandbox = peer.sandboxes[key];
sandbox.free = sandbox.free || -1;
sandbox.size = sandbox.size || 0;
if (sandbox.free >= 0 && sandbox.free <= sandbox.size) {
sandbox.count = sandbox.size - sandbox.free;
sandbox.percentage = (sandbox.count / sandbox.size) * 100;
}
full = full || sandbox.free === 0;
});
// Mark as full, if one sandbox is full
peer.sandboxes.full = full;
})
);
}
if (!jobs.length) return peer;
return $q.all(jobs) return $q.all(jobs)
.then(function(){ .then(function(){
...@@ -568,18 +675,21 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -568,18 +675,21 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
flushNewPeersAndSort = function(newPeers, updateMainBuid) { flushNewPeersAndSort = function(newPeers, updateMainBuid) {
newPeers = newPeers || data.newPeers; newPeers = newPeers || data.newPeers;
if (!newPeers.length) return; if (!newPeers.length) return;
var ids = _.map(data.peers, function(peer){ var ids = _.pluck(data.peers, 'id');
return peer.id;
});
var hasUpdates = false; var hasUpdates = false;
var newPeersAdded = 0; var newPeersAdded = 0;
_.forEach(newPeers.splice(0), function(peer) { _.forEach(newPeers.splice(0), function(peer) {
if (!ids[peer.id]) { if (!ids.includes(peer.id)) {
data.peers.push(peer); data.peers.push(peer);
ids[peer.id] = peer; ids.push(peer.id);
hasUpdates = true; hasUpdates = true;
newPeersAdded++; newPeersAdded++;
} }
else {
// Skip if exists
//data.peers[ids.indexOf(peer.id)] = peer;
//hasUpdates = true;
}
}); });
if (hasUpdates) { if (hasUpdates) {
console.debug('[network] Flushing {0} new peers...'.format(newPeersAdded)); console.debug('[network] Flushing {0} new peers...'.format(newPeersAdded));
...@@ -664,12 +774,16 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -664,12 +774,16 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
if (data.expertMode) { if (data.expertMode) {
score += (100 * (peer.difficulty ? (10000-peer.difficulty) : 0)); score += (100 * (peer.difficulty ? (10000-peer.difficulty) : 0));
score += (1 * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0)); score += (1 * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0));
score += (0.001 * (!peer.uid ? computeScoreAlphaValue(peer.pubkey, 3, true) : 0));
} }
else { else {
score += (100 * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0)); score += (100 * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0));
score += (1 * (!peer.uid ? computeScoreAlphaValue(peer.pubkey, 2, true) : 0)); score += (0.001 * (!peer.uid ? computeScoreAlphaValue(peer.pubkey, 3, true) : 0));
} }
score += (peer.isBma() ? (peer.isSsl() ? 0.01 : 0.001) :0); // If many endpoints: BMAS first, then BMA score += (0.00001 * (peer.isBma() ? (peer.isSsl() ? 1 : 0.5) :0)); // If many endpoints: BMAS first, then BMA
peer.score = score;
return -score; return -score;
}); });
...@@ -682,7 +796,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -682,7 +796,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
} }
// Raise event on new main block // Raise event on new main block
if (updateMainBuid && mainBlock.buid && (!data.mainBlock || data.mainBlock.buid !== mainBlock.buid)) { if (updateMainBuid && mainBlock && mainBlock.buid && (!data.mainBlock || data.mainBlock.buid !== mainBlock.buid)) {
data.mainBlock = mainBlock; data.mainBlock = mainBlock;
api.data.raise.mainBlockChanged(mainBlock); api.data.raise.mainBlockChanged(mainBlock);
} }
...@@ -748,79 +862,269 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -748,79 +862,269 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
}, },
start = function(bma, options) { start = function(bma, options) {
if (startPromise) {
console.warn('[network-service] Waiting previous start to be closed...');
return startPromise.then(function() {
return start(bma, options);
});
}
options = options || {}; options = options || {};
return BMA.ready() bma = bma || BMA;
var pid = 0;
startPromise = bma.ready()
.then(function() { .then(function() {
close(); // Stop previous network scan (if running)
close(data.pid);
// Compute next PID
pid = ++data.pid;
data.bma = bma ? bma : BMA; // Prepare data
data.bma = bma;
data.filter = options.filter ? angular.merge(data.filter, options.filter) : data.filter; data.filter = options.filter ? angular.merge(data.filter, options.filter) : data.filter;
data.sort = options.sort ? angular.merge(data.sort, options.sort) : data.sort; data.sort = options.sort ? angular.merge(data.sort, options.sort) : data.sort;
data.expertMode = angular.isDefined(options.expertMode) ? options.expertMode : data.expertMode; data.expertMode = angular.isDefined(options.expertMode) ? options.expertMode : data.expertMode;
data.timeout = angular.isDefined(options.timeout) ? options.timeout : csConfig.timeout; data.timeout = angular.isDefined(options.timeout) ? options.timeout : getDeviceTimeout(); // Fore recompute
data.startTime = Date.now();
data.autoRefresh = angular.isDefined(options.autoRefresh) ? data.autoRefresh : true;
data.flushIntervalMs = angular.isDefined(options.flushIntervalMs) ? options.flushIntervalMs : 1000;
data.withSandboxes = angular.isDefined(options.withSandboxes) ? options.withSandboxes : true;
// Init a min block number // Init a min block number
data.minOnlineBlockNumber = data.mainBlock && data.mainBlock.buid && (parseInt(data.mainBlock.buid.split('-')[0]) - constants.MAX_BLOCK_OFFSET) || undefined; var mainBlockNumber = data.mainBlock && buidBlockNumber(data.mainBlock.buid);
data.minOnlineBlockNumber = mainBlockNumber && Math.max(0, (mainBlockNumber - constants.MAX_BLOCK_OFFSET)) || undefined;
if (data.minOnlineBlockNumber === undefined) { if (data.minOnlineBlockNumber === undefined) {
return csCurrency.blockchain.current(true/*use cache*/) return csCurrency.blockchain.current(true/*use cache*/)
.then(function(current) { .then(function(current) {
data.minOnlineBlockNumber = current.number - constants.MAX_BLOCK_OFFSET; data.minOnlineBlockNumber = Math.max(0, current.number - constants.MAX_BLOCK_OFFSET);
if (Date.now() - data.startDate > 2000) {
console.warn('[network-service] Resetting network start date, because blockchain.current() take more than 2s to respond');
data.startTime = Date.now(); // Reset the startTime (use to compute remainingTime)
}
}); });
} }
}) })
.then(function() { .then(function() {
console.info('[network] Starting network from [{0}]'.format(bma.server));
var now = Date.now(); var now = Date.now();
console.info('[network] [#{0}] Starting from [{1}{2}] {ssl: {3}, expertMode: {4}, sandboxes: {5}}'.format(pid, bma.server, bma.path, bma.useSsl, data.expertMode, data.withSandboxes));
addListeners(); // Start listener to auto refresh peers
if (data.autoRefresh) addListeners();
return loadPeers() return loadPeers()
.then(function(peers){ .then(function(peers){
console.debug('[network] Started in '+(Date.now() - now)+'ms'); if (peers) console.debug('[network] [#{0}] Started - {1} peer(s) found, in {2}ms'.format(
return peers; pid,
peers.length,
Date.now() - now));
return data;
}); });
}); });
return startPromise;
}, },
close = function() { close = function(pid) {
if (data.bma) { if (data.bma) {
console.info('[network-service] Stopping...'); console.info(pid > 0 ? '[network] [#{0}] Stopping'.format(pid) : '[network] Stopping');
removeListeners(); removeListeners();
resetData(); resetData();
} }
if (interval && pid === data.pid && pid > 0) {
$interval.cancel(interval);
}
startPromise = null;
}, },
isStarted = function() { isStarted = function() {
return !data.bma; return !!data.bma;
}, },
$q_started = function(callback) { startIfNeed = function(bma, options) {
if (!isStarted()) { // start first if (startPromise) return startPromise;
return start() // Start if need
.then(function() { if (!isStarted()) {
return $q(callback); return start(bma, options);
});
} }
else { else {
return $q(callback); return $q.resolve(data);
} }
}, },
getMainBlockUid = function() { getMainBlockUid = function(bma, options) {
return $q_started(function(resolve, reject){ var wasStarted = isStarted();
resolve (data.mainBuid); var pid = wasStarted ? data.pid : data.pid + 1;
return startIfNeed(bma, options)
.then(function(data) {
var buid = data.mainBlock && data.mainBlock.buid;
if (!wasStarted) close(pid);
return buid;
})
.catch(function(err) {
console.error('[network] [#{0}] Failed to get main block'.format(pid));
if (!wasStarted) close(pid);
throw err;
}); });
}, },
// Get peers on the main consensus blocks // Get peers on the main consensus blocks
getTrustedPeers = function() { getSynchronizedBmaPeers = function(bma, options) {
return $q_started(function(resolve, reject){ options = options || {};
resolve(data.peers.reduce(function(res, peer){ options.filter = options.filter || {};
return (peer.hasMainConsensusBlock && peer.uid) ? res.concat(peer) : res; options.filter.bma = angular.isDefined(options.filter.bma) ? options.filter.bma : true;
}, [])); options.filter.ssl = isHttpsMode ? true : undefined /*= all */;
options.filter.online = true;
options.filter.expertMode = false; // Difficulties not need
options.timeout = angular.isDefined(options.timeout) ? options.timeout : getDeviceTimeout();
options.minConsensusPeerCount = options.minConsensusPeerCount > 0 ? options.minConsensusPeerCount : -1;
options.autoRefresh = options.autoRefresh === true; // false by default (dont need to detecte changes, using websockets)
options.flushIntervalMs = angular.isDefined(options.flushIntervalMs) ? options.flushIntervalMs : 1000;
options.withSandboxes = angular.isDefined(options.withSandboxes) ? options.withSandboxes : false;
// Compute minConsensusPeerCount, from statistics
if (options.minConsensusPeerCount <= 0) {
// Init using default config value
options.minConsensusPeerCount = csSettings.data.minConsensusPeerCount > 0 ? csSettings.data.minConsensusPeerCount : -1;
// Override with stats value (if greater)
var avgPeerCount = csSettings.stats.computeAvgPeerCount(options);
var avgPeerCount80pct = avgPeerCount > 0 && Math.floor(avgPeerCount * 0.8) || -1;
if (avgPeerCount80pct > options.minConsensusPeerCount) {
console.debug('[network] Using computed minConsensusPeerCount={0} (=80% of average peer count {1})'.format(avgPeerCount80pct, avgPeerCount));
options.minConsensusPeerCount = avgPeerCount80pct;
}
}
var wasStarted = isStarted();
var pid = wasStarted ? data.pid : data.pid + 1;
console.info('[network] [#{0}] Getting synchronized BMA peers... {timeout: {1}, minConsensusPeerCount: {2}, flushIntervalMs: {3}}'.format(pid,
options.timeout, options.minConsensusPeerCount, options.flushIntervalMs));
var filterPeers = function(peers) {
var peerUrls = [];
return peers.reduce(function(res, peer) {
// Exclude if not BMA or not on the main consensus block
if (!peer || !peer.isBma() || !peer.hasMainConsensusBlock) return res;
// Fill some properties compatible
peer.compatible = isCompatible(peer);
peer.url = peer.getUrl();
// Clean unused properties (e.g. the API, created by BMA.lightInstance())
delete peer.api;
// Remove duplicate
if (peerUrls.includes(peer.url)) return res;
peerUrls.push(peer.url);
return res.concat(peer);
}, []);
};
var deferred = $q.defer();
var checkEnoughListener = null;
// Stop when enough peers, on the main consensus block
if (options.minConsensusPeerCount > 0) {
checkEnoughListener = api.data.on.changed($rootScope, function(data) {
console.debug('[network] [#{0}] {1} peers'.format(pid, data.peers.length));
var peers = filterPeers(data.peers);
var consensusPeerCount = peers.length;
// Stop here, if reach minConsensusPeerCount
if (consensusPeerCount >= options.minConsensusPeerCount) {
if (checkEnoughListener) {
checkEnoughListener();
checkEnoughListener = null;
console.debug('[network] [#{0}] Found enough peers on main consensus ({1} peers)- in {2}ms (timeout {3}ms) '.format(pid,
consensusPeerCount,
Date.now() - data.startTime,
options.timeout));
deferred.resolve(peers);
}
}
}); });
} }
;
// Start network scan
startIfNeed(bma, options)
.then(function(data) {
var peers = filterPeers(data.peers);
deferred.resolve(peers);
})
.catch(deferred.reject);
return deferred.promise
.then(function(peers){
// Log
if (peers && peers.length > 0) {
var mainConsensusBlock = peers[0] && peers[0].buid;
console.info('[network] [#{0}] Found {0}/{1} BMA peers on main consensus block #{2} - in {3}ms'.format(
peers.length,
data.peers.length,
mainConsensusBlock,
Date.now() - data.startTime));
}
else {
console.warn('[network] [#{0}] No synchronized BMA peers found - in {1}ms'.format(pid, Date.now() - data.startTime));
}
// Stop network
if (!wasStarted) close(pid);
if (checkEnoughListener) checkEnoughListener();
return peers;
})
.catch(function(err) {
console.error('[network] [#{0}] Error while getting synchronized BMA peers'.format(pid), err);
if (!wasStarted) close(pid);
if (checkEnoughListener) checkEnoughListener();
throw err;
});
},
/**
* Is the peer compatible with Cesium (should be a BMA endpoint )
* @param peer
* @returns {*}
*/
isCompatible = function(peer) {
if (!peer && !peer.isBma() || !peer.version) return false;
// CHeck version compatible, from min version
if (!peer.version || !csHttp.version.isCompatible(csSettings.data.minVersionAtStartup || csSettings.data.minVersion, peer.version) ||
// Exclude beta versions (1.9.0* and 1.8.7-rc*)
peer.version.startsWith('1.9.0') || peer.version.startsWith('1.8.7-rc')
) {
console.debug('[network] [#{0}] BMA endpoint [{1}] is EXCLUDED (incompatible version {2})'.format(data.pid, peer.getServer(), peer.version));
return false;
}
// Exclude if transactions not stored
if (!peer.storage && peer.storage.transactions !== true) {
console.debug('[network] [#{0}] BMA endpoint [{1}] is EXCLUDED (no transactions storage)'.format(data.pid, peer.getServer()));
return false;
}
// Exclude g1.duniter.org, because of fail-over config, that can switch node
if (peer.host === 'g1.duniter.org') {
console.debug('[network] [#{0}] BMA endpoint [{1}] is EXCLUDED (load-balancing nightmare)'.format(data.pid, peer.getServer()));
return false;
}
// Exclude if one sandbox is full
if (peer.sandboxes && peer.sandboxes.full) {
console.debug('[network] [#{0}] BMA endpoint [{1}] is EXCLUDED (one sandbox is full)'.format(data.pid, peer.getServer()));
return false;
}
return true;
};
// Register extension points // Register extension points
api.registerEvent('data', 'changed'); api.registerEvent('data', 'changed');
...@@ -828,14 +1132,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -828,14 +1132,13 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
api.registerEvent('data', 'rollback'); api.registerEvent('data', 'rollback');
return { return {
id: id,
data: data, data: data,
start: start, start: start,
close: close, close: close,
hasPeers: hasPeers, hasPeers: hasPeers,
getPeers: getPeers, getPeers: getPeers,
sort: sort, sort: sort,
getTrustedPeers: getTrustedPeers, getSynchronizedBmaPeers: getSynchronizedBmaPeers,
getKnownBlocks: getKnownBlocks, getKnownBlocks: getKnownBlocks,
getMainBlockUid: getMainBlockUid, getMainBlockUid: getMainBlockUid,
loadPeers: loadPeers, loadPeers: loadPeers,
...@@ -843,13 +1146,4 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', ...@@ -843,13 +1146,4 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
// api extension // api extension
api: api api: api
}; };
}
var service = new CsNetwork('default');
service.instance = function(id) {
return new CsNetwork(id);
};
return service;
}); });
angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
.factory('csSettings', function($rootScope, $q, Api, localStorage, $translate, csConfig) { .factory('csSettings', function($rootScope, $q, $window, $timeout, Api, localStorage, $translate, csConfig) {
'ngInject'; 'ngInject';
// Define app locales // Define app locales
var locales = [ var
{id:'en', label:'English'}, locales = [
{id:'en-GB', label:'English (UK)'}, {id:'en', label:'English', flag: 'us'},
{id:'fr-FR', label:'Français'}, {id:'en-GB', label:'English (UK)', flag: 'gb'},
{id:'nl-NL', label:'Nederlands'}, {id:'eo-EO', label:'Esperanto', flag: 'eo'},
{id:'es-ES', label:'Spanish'}, {id:'fr-FR', label:'Français', flag: 'fr'},
{id:'it-IT', label:'Italiano'} {id:'nl-NL', label:'Nederlands', flag: 'nl'},
]; {id:'es-ES', label:'Español', flag: 'es'},
var fallbackLocale = csConfig.fallbackLanguage ? fixLocale(csConfig.fallbackLanguage) : 'en'; {id:'ca', label:'Català', flag: 'ca'},
{id:'it-IT', label:'Italiano', flag: 'it'},
{id:'pt-PT', label:'Português', flag: 'pt'},
{id:'de-DE', label:'Deutsch', flag: 'de'}
],
timeouts = [
-1,
500,
1000,
2000,
3000,
5000,
10000,
30000,
60000,
300000
],
fallbackLocale = csConfig.fallbackLanguage ? fixLocale(csConfig.fallbackLanguage) : 'en'
;
// Convert browser locale to app locale (fix #140) // Convert browser locale to app locale (fix #140)
function fixLocale (locale) { function fixLocale (locale) {
...@@ -40,7 +58,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -40,7 +58,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
// Convert browser locale to app locale (fix #140) // Convert browser locale to app locale (fix #140)
function fixLocaleWithLog (locale) { function fixLocaleWithLog (locale) {
var fixedLocale = fixLocale(locale); var fixedLocale = fixLocale(locale);
if (locale != fixedLocale) { if (locale !== fixedLocale) {
console.debug('[settings] Fix locale [{0}] -> [{1}]'.format(locale, fixedLocale)); console.debug('[settings] Fix locale [{0}] -> [{1}]'.format(locale, fixedLocale));
} }
return fixedLocale; return fixedLocale;
...@@ -53,24 +71,36 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -53,24 +71,36 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
}, },
// Settings that user cannot change himself (only config can override this values) // Settings that user cannot change himself (only config can override this values)
fixedSettings = { fixedSettings = {
timeout : 4000,
cacheTimeMs: 60000, /*1 min*/ cacheTimeMs: 60000, /*1 min*/
timeWarningExpireMembership: 2592000 * 2 /*=2 mois*/, timeWarningExpireMembership: 2592000 * 2 /*=2 mois*/,
timeWarningExpire: 2592000 * 3 /*=3 mois*/, timeWarningExpire: 2592000 * 3 /*=3 mois*/,
minVersion: '1.1.0', minVersion: '1.8.0',
minVersionAtStartup: '1.8.7', // use for node auto-selection
minConsensusPeerCount: 10, // use for node auto-selection (avoid to start if no few peers found)
sourceUrl: 'https://git.duniter.org/clients/cesium-grp/cesium',
sourceLicenseUrl: 'https://git.duniter.org/clients/cesium-grp/cesium/-/raw/master/LICENSE',
newIssueUrl: "https://git.duniter.org/clients/cesium-grp/cesium/issues/new", newIssueUrl: "https://git.duniter.org/clients/cesium-grp/cesium/issues/new",
userForumUrl: "https://forum.monnaie-libre.fr", userForumUrl: "https://forum.monnaie-libre.fr",
userTelegramUrl: "https://t.me/monnaielibrejune",
techForumUrl: "https://forum.duniter.org",
latestReleaseUrl: "https://api.github.com/repos/duniter/cesium/releases/latest", latestReleaseUrl: "https://api.github.com/repos/duniter/cesium/releases/latest",
duniterLatestReleaseUrl: "https://api.github.com/repos/duniter/duniter/releases/latest", // FIXME: get release from gitlab
duniterLatestReleaseUrl: undefined, // disable for now
// "https://api.github.com/repos/duniter/duniter/releases/latest", // Github
// "https://git.duniter.org/nodes/typescript/duniter/-/releases.json" // Gitlab
httpsMode: false httpsMode: false
}, },
defaultSettings = angular.merge({ defaultSettings = angular.merge({
timeout: -1, // -1 = auto
useRelative: false, useRelative: false,
useLocalStorage: true, // override to false if no device useLocalStorage: !!$window.localStorage, // Overwritten to false if not a device
useLocalStorageEncryption: false, useLocalStorageEncryption: false,
walletHistoryTimeSecond: 30 * 24 * 60 * 60 /*30 days*/, useFullscreen: null,
walletHistorySliceSecond: 5 * 24 * 60 * 60 /*download using 5 days slice*/, persistCache: false, // disable by default (waiting resolution of issue #885)
walletHistoryAutoRefresh: true, // override to false if device walletHistoryTimeSecond: 30 * 24 * 60 * 60, // 30 days
walletHistorySliceSecond: 5 * 24 * 60 * 60, // download using 5 days slice - need for cache
walletHistoryScrollMaxTimeSecond: 3 * 30 * 24 * 60 * 60, // Limit TX load infinite scroll to 3 month
walletHistoryAutoRefresh: true, // Reload TX history on new block ? Overwritten to false if device
rememberMe: true, rememberMe: true,
keepAuthIdle: 10 * 60, keepAuthIdle: 10 * 60,
showUDHistory: true, showUDHistory: true,
...@@ -78,6 +108,13 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -78,6 +108,13 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
decimalCount: 4, decimalCount: 4,
uiEffects: true, uiEffects: true,
blockValidityWindow: 6, blockValidityWindow: 6,
network: {
// Synchronized BMA peers found
peers: [],
stats: [],
statsWindowSecond: (csConfig.network && csConfig.network.statsWindowSecond || 10 * 24 * 60 * 60), // 10 days
statsPeriodSecond: 5 * 60 // 5 min
},
helptip: { helptip: {
enable: true, enable: true,
installDocUrl: "https://duniter.org/en/wiki/duniter/install/", installDocUrl: "https://duniter.org/en/wiki/duniter/install/",
...@@ -88,6 +125,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -88,6 +125,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
wotCerts: 0, wotCerts: 0,
wallet: 0, wallet: 0,
walletCerts: 0, walletCerts: 0,
wallets: 0,
header: 0, header: 0,
settings: 0 settings: 0
}, },
...@@ -97,10 +135,21 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -97,10 +135,21 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
}, },
wallet: { wallet: {
showPubkey: true, showPubkey: true,
alertIfUnusedWallet: true alertIfUnusedWallet: true,
notificationReadTime: 0
}, },
locale: { locale: {
id: fixLocaleWithLog(csConfig.defaultLanguage || $translate.use()) // use config locale if set, or browser default id: fixLocaleWithLog(csConfig.defaultLanguage || $translate.use()) // use config locale if set, or browser default
},
license: {
"en": "license/license_g1-en",
"fr-FR": "license/license_g1-fr-FR",
"es-ES": "license/license_g1-es-ES",
"es-CT": "license/license_g1-es-CT",
"eo-EO": "license/license_g1-eo-EO",
"pt-PT": "license/license_g1-pt-PT",
"it-IT": "license/license_g1-it-IT",
"de-DE": "license/license_g1-de-DE"
} }
}, },
fixedSettings, fixedSettings,
...@@ -117,8 +166,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -117,8 +166,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
defaultSettings.walletHistoryAutoRefresh = false; defaultSettings.walletHistoryAutoRefresh = false;
// endRemoveIf(no-device) // endRemoveIf(no-device)
var function reset() {
reset = function() {
_.keys(data).forEach(function(key){ _.keys(data).forEach(function(key){
delete data[key]; delete data[key];
}); });
...@@ -127,9 +175,9 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -127,9 +175,9 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
return api.data.raisePromise.reset(data) return api.data.raisePromise.reset(data)
.then(store); .then(store);
}, }
getByPath = function(path, defaultValue) { function getByPath(path, defaultValue) {
var obj = data; var obj = data;
_.each(path.split('.'), function(key) { _.each(path.split('.'), function(key) {
obj = obj[key]; obj = obj[key];
...@@ -140,26 +188,26 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -140,26 +188,26 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
}); });
return obj; return obj;
}, }
emitChangedEvent = function() { function emitChangedEvent() {
var hasChanged = angular.isUndefined(previousData) || !angular.equals(previousData, data); var hasChanged = angular.isUndefined(previousData) || !angular.equals(previousData, data);
if (hasChanged) { if (hasChanged) {
previousData = angular.copy(data); previousData = angular.copy(data);
return api.data.raise.changed(data); return api.data.raise.changed(data);
} }
}, }
store = function() { function store() {
if (!started) { if (!started) {
console.debug('[setting] Waiting start finished...'); console.debug('[settings] Waiting start finished...');
return (startPromise || start()).then(store); return (startPromise || start()).then(store);
} }
var promise; var promise;
if (data.useLocalStorage) { if (data.useLocalStorage) {
// When node is temporary (fallback node): keep previous node address - issue #476 // When node is temporary (fallback node): keep previous node address - issue #476
if (data.node.temporary === true) { if (data.node && data.node.temporary === true) {
promise = localStorage.getObject(constants.STORAGE_KEY) promise = localStorage.getObject(constants.STORAGE_KEY)
.then(function(previousSettings) { .then(function(previousSettings) {
var savedData = angular.copy(data); var savedData = angular.copy(data);
...@@ -179,7 +227,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -179,7 +227,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
return promise return promise
.then(function() { .then(function() {
if (data.useLocalStorage) { if (data.useLocalStorage) {
console.debug('[setting] Saved locally'); console.debug('[settings] Saved locally');
} }
// Emit event on store // Emit event on store
...@@ -188,19 +236,23 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -188,19 +236,23 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
// Emit event on store // Emit event on store
.then(emitChangedEvent); .then(emitChangedEvent);
}, }
/** /**
* Apply new settings (can be partial) * Apply new settings (can be partial)
* @param newData * @param newData
*/ */
applyData = function(newData) { function applyData(newData) {
if (!newData) return; // skip empty if (!newData) return; // skip empty
// DEBUG
//console.debug('[settings] Applying data', newData);
var localeChanged = false; var localeChanged = false;
if (newData.locale && newData.locale.id) { if (newData.locale && newData.locale.id) {
// Fix previously stored locale (could use bad format) // Fix previously stored locale (could use bad format)
newData.locale.id = fixLocale(newData.locale.id); var localeId = fixLocale(newData.locale.id);
newData.locale = _.findWhere(locales, {id: localeId});
localeChanged = !data.locale || newData.locale.id !== data.locale.id || newData.locale.id !== $translate.use(); localeChanged = !data.locale || newData.locale.id !== data.locale.id || newData.locale.id !== $translate.use();
} }
...@@ -209,19 +261,32 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -209,19 +261,32 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
newData[key] = defaultSettings[key]; // This will apply fixed value (override by config.js file) newData[key] = defaultSettings[key]; // This will apply fixed value (override by config.js file)
}); });
// Force using an existing timeout (e.g. for version < 1.7.4)
newData.timeout = newData.timeout && timeouts.includes(newData.timeout) ? newData.timeout : -1 /* auto */;
// If need select a random peer, from the config
if (!data.node && !newData.node && _.size(csConfig.fallbackNodes) > 0) {
newData.node = _.sample(csConfig.fallbackNodes);
console.info('[settings] Random selected peer [{0}]'.format(newData.node.host));
newData.node.temporary = true;
}
// Apply new settings // Apply new settings
angular.merge(data, newData); angular.merge(data, newData);
// Delete temporary properties, if false // Delete temporary properties, if false
if (newData && newData.node && !newData.node.temporary || !data.node.temporary) delete data.node.temporary; if ((newData && newData.node && !newData.node.temporary) || (data.node && !data.node.temporary)) {
delete data.node.temporary;
}
// Apply the new locale (only if need) // Apply the new locale (only if need)
// will produce an event cached by onLocaleChange(); // will produce an event cached by onLocaleChange();
if (localeChanged) $translate.use(data.locale.id); if (localeChanged) {
$translate.use(data.locale.id);
}, }
}
restore = function() { function restore() {
var now = Date.now(); var now = Date.now();
return localStorage.getObject(constants.STORAGE_KEY) return localStorage.getObject(constants.STORAGE_KEY)
...@@ -234,54 +299,153 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -234,54 +299,153 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
return; return;
} }
console.debug('[settings] Loaded from local storage in {0}ms'.format(Date.now()-now));
// Apply stored data // Apply stored data
applyData(storedData); applyData(storedData);
console.debug('[settings] Loaded from local storage in '+(Date.now()-now)+'ms');
emitChangedEvent(); emitChangedEvent();
}); });
}, }
/**
* Save synchronized BMA peers, after a network scan
* @param newData
*/
function savePeers(peers, options) {
if (!peers) return; // skip empty
console.debug("[settings] Saving {0} BMA peers...".format(peers.length));
data.network = data.network || {};
data.network.peers = peers;
// Update peer stats
var statsWindowSecond = options && options.statsWindowSecond || data.network.statsWindowSecond;
if (statsWindowSecond > 0) {
// Clean stats outside the stats window
var now = Date.now();
var minTime = now - statsWindowSecond * 1000;
data.network.stats = _.filter(data.network.stats || [], function(stat) {
return stat.time > minTime;
});
var currentStat = {
time: now,
peerCount: peers.length + 1 /*current BMA peer*/
};
// If previous stats is recent (< 5min)
// this is used to avoid too many stats, but keep the must fresh value
var previousStats = data.network.stats.length ? data.network.stats[data.network.stats.length-1] : undefined;
var previousAgeSecond = previousStats && (now - previousStats.time) / 1000;
var statsPeriodSecond = data.network.statsPeriodSecond || 5 * 60; // 5 min;
if (previousAgeSecond && previousAgeSecond < statsPeriodSecond) {
if (currentStat.peerCount === previousStats.peerCount) {
console.debug("[settings] Skip network stats (recent stats exists)");
}
// Replace it peer count change
else {
angular.merge(previousStats, currentStat);
}
}
else {
// Insert
data.network.stats.push(currentStat);
}
}
// Storing, with a delay 2s
$timeout(store, 2000);
}
function computeAvgPeerCount(options) {
if (!data.network || !data.network.stats || !data.network.stats.length) return undefined; // Cannot compute
var statsWindowSecond = options && options.statsWindowSecond || data.network.statsWindowSecond || -1;
if (statsWindowSecond > data.network.statsWindowSecond) {
console.warn('[settings] Peer stats windows ({0}s) should not be greater than the collected windows ({1}s)'.format(
statsWindowSecond,
data.statsWindowSecond
));
statsWindowSecond = data.network.statsWindowSecond;
}
getLicenseUrl = function() { var now = Date.now();
var minTime = now - statsWindowSecond * 1000;
// Select stats inside the expected window
var stats = _.filter(data.network.stats || [], function(stat) {
return stat.time > minTime && stat.peerCount > 1;
});
if (!stats.length) return undefined; // Not enough stats to compute something
// Compute the AVG(peerCount)
var sum = _.reduce(stats, function(sum, stat) { return sum + stat.peerCount; }, 0);
return Math.floor(sum / stats.length);
}
function getLicenseUrl() {
var locale = data.locale && data.locale.id || csConfig.defaultLanguage || 'en'; var locale = data.locale && data.locale.id || csConfig.defaultLanguage || 'en';
return (csConfig.license) ? return (csConfig.license) ?
(csConfig.license[locale] ? csConfig.license[locale] : csConfig.license[csConfig.defaultLanguage || 'en'] || csConfig.license) : undefined; (csConfig.license[locale] ? csConfig.license[locale] : defaultSettings.license[csConfig.defaultLanguage || 'en'] || csConfig.license) : undefined;
}, }
function getFeedUrl() {
var locale = data.locale && data.locale.id || csConfig.defaultLanguage || 'en';
return (csConfig.feed && csConfig.feed.jsonFeed) ?
(csConfig.feed.jsonFeed[locale] ? csConfig.feed.jsonFeed[locale] : defaultSettings.feed.jsonFeed[csConfig.defaultLanguage || 'en'] || csConfig.feed.jsonFeed) : undefined;
}
// Detect locale successful changes, then apply to vendor libs // Detect locale successful changes, then apply to vendor libs
onLocaleChange = function() { function onLocaleChange() {
var locale = $translate.use(); var locale = $translate.use();
console.debug('[settings] Locale ['+locale+']'); console.debug('[settings] Locale ['+locale+']');
// config moment lib // config moment lib
try {
moment.locale(locale.toLowerCase());
}
catch(err) {
try { try {
moment.locale(locale.substr(0,2)); moment.locale(locale.substr(0,2));
} }
catch(err) { catch(err) {
moment.locale('en'); moment.locale('en-gb');
console.warn('[settings] Unknown local for moment lib. Using default [en]'); console.warn('[settings] Unknown local for moment lib. Using default [en]');
} }
}
// config numeral lib // config numeral lib
try { try {
numeral.language(locale.substr(0,2)); numeral.language(locale.toLowerCase());
} }
catch(err) { catch(err) {
numeral.language('en'); try {
numeral.language(locale.substring(0, 2));
}
catch(err) {
numeral.language('en-gb');
console.warn('[settings] Unknown local for numeral lib. Using default [en]'); console.warn('[settings] Unknown local for numeral lib. Using default [en]');
} }
}
// Emit event // Emit event
api.locale.raise.changed(locale); api.locale.raise.changed(locale);
}, }
function isStarted() {
return started;
}
ready = function() { function ready() {
if (started) return $q.when(); if (started) return $q.when();
return startPromise || start(); return startPromise || start();
}, }
start = function() { function start() {
console.debug('[settings] Starting...'); console.debug('[settings] Starting...');
startPromise = localStorage.ready() startPromise = localStorage.ready()
...@@ -296,10 +460,12 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -296,10 +460,12 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
startPromise = null; startPromise = null;
// Emit event (used by plugins) // Emit event (used by plugins)
api.data.raise.ready(data); api.data.raise.ready(data);
return data;
}); });
return startPromise; return startPromise;
}; }
$rootScope.$on('$translateChangeSuccess', onLocaleChange); $rootScope.$on('$translateChangeSuccess', onLocaleChange);
...@@ -316,6 +482,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -316,6 +482,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
//start(); //start();
return { return {
isStarted: isStarted,
ready: ready, ready: ready,
start: start, start: start,
data: data, data: data,
...@@ -325,10 +492,16 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config']) ...@@ -325,10 +492,16 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
store: store, store: store,
restore: restore, restore: restore,
getLicenseUrl: getLicenseUrl, getLicenseUrl: getLicenseUrl,
getFeedUrl: getFeedUrl,
savePeers: savePeers,
stats: {
computeAvgPeerCount: computeAvgPeerCount
},
defaultSettings: defaultSettings, defaultSettings: defaultSettings,
// api extension // api extension
api: api, api: api,
locales: locales, locales: locales,
timeouts: timeouts,
constants: constants constants: constants
}; };
}); });
...@@ -64,6 +64,11 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -64,6 +64,11 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
return $q.when(); return $q.when();
}; };
exports.standard.remove = function(key, value) {
exports.standard.storage.removeItem(key);
return $q.when();
};
exports.standard.get = function(key, defaultValue) { exports.standard.get = function(key, defaultValue) {
return $q.when(exports.standard.storage[key] || defaultValue); return $q.when(exports.standard.storage[key] || defaultValue);
}; };
...@@ -82,16 +87,19 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -82,16 +87,19 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
// Set a value to the secure storage (or remove if value is not defined) // Set a value to the secure storage (or remove if value is not defined)
exports.secure.put = function(key, value) { exports.secure.put = function(key, value) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
if (value !== undefined && value !== null) { if (value != null) {
exports.secure.storage.set( exports.secure.storage.set(
function (key) { function (key) {
resolve(); resolve();
}, },
function (err) { function (err) {
$log.error("[storage] Failed to set value for key=" + key);
$log.error(err); $log.error(err);
reject(err); reject(err);
}, },
key, value); key, value,
null // Cipher mode - "CCM" (default) or "GCM" (Galois/Counter Mode)
);
} }
// Remove // Remove
else { else {
...@@ -100,6 +108,7 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -100,6 +108,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
resolve(); resolve();
}, },
function (err) { function (err) {
$log.error("[storage] Failed to remove key=" + key);
$log.error(err); $log.error(err);
resolve(); // Error = not found resolve(); // Error = not found
}, },
...@@ -110,6 +119,7 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -110,6 +119,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
// Get a value from the secure storage // Get a value from the secure storage
exports.secure.get = function(key, defaultValue) { exports.secure.get = function(key, defaultValue) {
$log.debug("[storage] Getting value from secure storage, using key=" + key);
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
exports.secure.storage.get( exports.secure.storage.get(
function (value) { function (value) {
...@@ -121,6 +131,7 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -121,6 +131,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
} }
}, },
function (err) { function (err) {
$log.error("[storage] Failed to get value from secure storage, using key=" + key);
$log.error(err); $log.error(err);
resolve(); // Error = not found resolve(); // Error = not found
}, },
...@@ -132,11 +143,31 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -132,11 +143,31 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
exports.secure.setObject = function(key, value) { exports.secure.setObject = function(key, value) {
$log.debug("[storage] Setting object into secure storage, using key=" + key); $log.debug("[storage] Setting object into secure storage, using key=" + key);
return $q(function(resolve, reject){ return $q(function(resolve, reject){
if (value != null) {
exports.secure.storage.set( exports.secure.storage.set(
resolve, resolve,
reject, function(err) {
$log.error("[storage] Failed to set object into secure storage, using key=" + key, value);
$log.error(err);
reject(err);
},
key, key,
value ? JSON.stringify(value) : undefined); JSON.stringify(value),
null // Cipher mode - "CCM" (default) or "GCM" (Galois/Counter Mode)
);
}
else {
exports.secure.storage.remove(
function () {
resolve();
},
function (err) {
$log.error("[storage] Failed to remove key=" + key);
$log.error(err);
resolve(); // Error = not found
},
key);
}
}); });
}; };
...@@ -145,8 +176,12 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -145,8 +176,12 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
$log.debug("[storage] Getting object from secure storage, using key=" + key); $log.debug("[storage] Getting object from secure storage, using key=" + key);
return $q(function(resolve, reject){ return $q(function(resolve, reject){
exports.secure.storage.get( exports.secure.storage.get(
function(value) {resolve(JSON.parse(value||'null'));}, function(value) {
$log.debug("[storage] Getting object from secure storage, using key=" + key + " - result="+value);
resolve(JSON.parse(value||'null'));
},
function(err) { function(err) {
$log.error("[storage] Failed to get object from secure storage, using key=" + key);
$log.error(err); $log.error(err);
resolve(); // Error = not found resolve(); // Error = not found
}, },
...@@ -165,7 +200,7 @@ angular.module('cesium.storage.services', [ 'cesium.config']) ...@@ -165,7 +200,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
}); });
} }
// Fallback to session storage (locaStorage could have been disabled on some browser) // Fallback to session storage (localStorage could have been disabled on some browser)
else { else {
console.debug('[storage] Starting {session} storage...'); console.debug('[storage] Starting {session} storage...');
// Set standard storage as default // Set standard storage as default
......
...@@ -2,38 +2,31 @@ ...@@ -2,38 +2,31 @@
angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
'cesium.settings.services', 'cesium.wot.services' ]) 'cesium.settings.services', 'cesium.wot.services' ])
.factory('csTx', function($q, $timeout, $filter, $translate, FileSaver, UIUtils, BMA, Api, .factory('csTx', function($q, $timeout, $filter, $translate, FileSaver, UIUtils, Device, BMA, Api,
csConfig, csSettings, csWot, csCurrency) { csConfig, csSettings, csWot, csCurrency) {
'ngInject'; 'ngInject';
var defaultBMA = BMA;
function CsTx(id, BMA) {
BMA = BMA || defaultBMA;
var var
api = new Api(this, "csTx-" + id), api = new Api(this, "csTx");
_reduceTxAndPush = function(pubkey, txArray, result, processedTxMap, allowPendings) { function reduceTxAndPush(pubkey, txArray, result, processedTxMap, allowPendings) {
if (!txArray || !txArray.length) return; // Skip if empty if (!txArray || !txArray.length) return; // Skip if empty
_.forEach(txArray, function(tx) { _.forEach(txArray, function(tx) {
if (tx.block_number || allowPendings) { if (tx.block_number !== null || allowPendings) {
var walletIsIssuer = false; var walletIsIssuer = false;
var otherIssuer = tx.issuers.reduce(function(issuer, res) { var otherIssuers = tx.issuers.reduce(function(res, issuer) {
walletIsIssuer = (res === pubkey) ? true : walletIsIssuer; walletIsIssuer = walletIsIssuer || (issuer === pubkey);
return issuer + ((res !== pubkey) ? ', ' + res : ''); return (issuer !== pubkey) ? res.concat(issuer) : res;
}, ''); }, []);
if (otherIssuer.length > 0) { var otherRecipients = [],
otherIssuer = otherIssuer.substring(2); outputBase,
} sources = [],
var otherReceiver; lockedOutputs;
var outputBase;
var sources = [];
var lockedOutputs;
var amount = tx.outputs.reduce(function(sum, output, noffset) { var amount = tx.outputs.reduce(function(sum, output, noffset) {
// FIXME duniter v1.4.13 // FIXME duniter v1.4.13
var outputArray = (typeof output == 'string') ? output.split(':',3) : [output.amount,output.base,output.conditions]; var outputArray = (typeof output === 'string') ? output.split(':',3) : [output.amount,output.base,output.conditions];
outputBase = parseInt(outputArray[1]); outputBase = parseInt(outputArray[1]);
var outputAmount = powBase(parseInt(outputArray[0]), outputBase); var outputAmount = powBase(parseInt(outputArray[0]), outputBase);
var outputCondition = outputArray[2]; var outputCondition = outputArray[2];
...@@ -42,7 +35,7 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -42,7 +35,7 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
// Simple unlock condition // Simple unlock condition
if (sigMatches) { if (sigMatches) {
var outputPubkey = sigMatches[1]; var outputPubkey = sigMatches[1];
if (outputPubkey == pubkey) { // output is for the wallet if (outputPubkey === pubkey) { // output is for the wallet
if (!walletIsIssuer) { if (!walletIsIssuer) {
return sum + outputAmount; return sum + outputAmount;
} }
...@@ -54,35 +47,40 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -54,35 +47,40 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
type: 'T', type: 'T',
identifier: tx.hash, identifier: tx.hash,
noffset: noffset, noffset: noffset,
consumed: false consumed: false,
conditions: outputCondition
}); });
} }
} }
else { // output is for someone else
if (outputPubkey !== '' && outputPubkey != otherIssuer) { // The output is for someone else
otherReceiver = outputPubkey; else {
// Add into recipients list(if not a issuer)
if (outputPubkey !== '' && !_.contains(otherIssuers, outputPubkey)) {
otherRecipients.push(outputPubkey);
} }
if (walletIsIssuer) { if (walletIsIssuer) {
// TODO: should be fix, when TX has multiple issuers (need a repartition)
return sum - outputAmount; return sum - outputAmount;
} }
} }
} }
// Complex unlock condition, on the issuer pubkey // Complex unlock condition, on the issuer pubkey
else if (outputCondition.indexOf('SIG('+pubkey+')') != -1) { else if (outputCondition.indexOf('SIG('+pubkey+')') !== -1) {
var lockedOutput = BMA.tx.parseUnlockCondition(outputCondition); var lockedOutput = BMA.tx.parseUnlockCondition(outputCondition);
if (lockedOutput) { if (lockedOutput) {
// Add a source // Add a source
// FIXME: should be uncomment when filtering source on transfer() sources.push(angular.merge({
/*sources.push(angular.merge({
amount: parseInt(outputArray[0]), amount: parseInt(outputArray[0]),
base: outputBase, base: outputBase,
type: 'T', type: 'T',
identifier: tx.hash, identifier: tx.hash,
noffset: noffset, noffset: noffset,
conditions: outputCondition,
consumed: false consumed: false
}, lockedOutput)); }, lockedOutput));
*/
lockedOutput.amount = outputAmount; lockedOutput.amount = outputAmount;
lockedOutputs = lockedOutputs || []; lockedOutputs = lockedOutputs || [];
lockedOutputs.push(lockedOutput); lockedOutputs.push(lockedOutput);
...@@ -94,23 +92,26 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -94,23 +92,26 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
return sum; return sum;
}, 0); }, 0);
var txPubkey = amount > 0 ? otherIssuer : otherReceiver; var txPubkeys = amount > 0 ? otherIssuers : otherRecipients;
var time = tx.time || tx.blockstampTime; var time = tx.time || tx.blockstampTime;
// Avoid duplicated tx, or tx to him self // Avoid duplicated tx, or tx to him self (if amount = 0)
var txKey = amount + ':' + tx.hash + ':' + time; var txKey = (amount !== 0) ? [amount, tx.hash, time].join(':') : undefined;
if (!processedTxMap[txKey] && amount !== 0) { if (txKey && !processedTxMap[txKey]) {
processedTxMap[txKey] = true; processedTxMap[txKey] = true; // Mark as processed
var newTx = { var newTx = {
id: txKey,
time: time, time: time,
amount: amount, amount: amount,
pubkey: txPubkey, pubkey: txPubkeys.length === 1 ? txPubkeys[0] : undefined,
pubkeys: txPubkeys.length > 1 ? txPubkeys : undefined,
comment: tx.comment, comment: tx.comment,
isUD: false, isUD: false,
hash: tx.hash, hash: tx.hash,
locktime: tx.locktime, locktime: tx.locktime,
block_number: tx.block_number block_number: tx.block_number
}; };
// If pending: store sources and inputs for a later use - see method processTransactionsAndSources() // If pending: store sources and inputs for a later use - see method processTransactionsAndSources()
if (walletIsIssuer && tx.block_number === null) { if (walletIsIssuer && tx.block_number === null) {
newTx.inputs = tx.inputs; newTx.inputs = tx.inputs;
...@@ -123,9 +124,10 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -123,9 +124,10 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
} }
} }
}); });
}, }
loadTx = function(pubkey, fromTime) {
function loadTx(pubkey, fromTime) {
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
var nowInSec = moment().utc().unix(); var nowInSec = moment().utc().unix();
...@@ -138,12 +140,6 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -138,12 +140,6 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
}; };
var processedTxMap = {}; var processedTxMap = {};
var _reduceTx = function (res) {
_reduceTxAndPush(pubkey, res.history.sent, tx.history, processedTxMap);
_reduceTxAndPush(pubkey, res.history.received, tx.history, processedTxMap);
_reduceTxAndPush(pubkey, res.history.sending, tx.pendings, processedTxMap, true /*allow pendings*/);
_reduceTxAndPush(pubkey, res.history.pending, tx.pendings, processedTxMap, true /*allow pendings*/);
};
var jobs = [ var jobs = [
// get current block // get current block
...@@ -151,51 +147,75 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -151,51 +147,75 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
// get pending tx // get pending tx
BMA.tx.history.pending({pubkey: pubkey}) BMA.tx.history.pending({pubkey: pubkey})
.then(_reduceTx) .then(function (res) {
reduceTxAndPush(pubkey, res.history.sending, tx.pendings, processedTxMap, true /*allow pendings*/);
reduceTxAndPush(pubkey, res.history.pending, tx.pendings, processedTxMap, true /*allow pendings*/);
})
]; ];
// get TX history since // get TX history since
if (fromTime !== 'pending') { if (fromTime !== 'pending') {
var slices = [];
// get TX from a given time // Fill slices: {params, cache}[]
if (fromTime > 0) { {
// Use slice, to be able to cache requests result
var sliceTime = csSettings.data.walletHistorySliceSecond; var sliceTime = csSettings.data.walletHistorySliceSecond;
fromTime = fromTime - (fromTime % sliceTime); fromTime = fromTime - (fromTime % sliceTime);
for(var i = fromTime; i - sliceTime < nowInSec; i += sliceTime) { var i;
jobs.push(BMA.tx.history.times({pubkey: pubkey, from: i, to: i+sliceTime-1}) for (i = fromTime; i - sliceTime < nowInSec; i += sliceTime) {
.then(_reduceTx) slices.push({params: {pubkey: pubkey, from: i, to: i+sliceTime-1}, cache: true /*with cache*/});
);
} }
slices.push({params: {pubkey: pubkey, from: i, to: nowInSec+999999999}, cache: false/*no cache for the last slice*/});
}
// DEBUG
// console.debug('[tx] Loading TX using slices: ', slices);
var reduceTxFn = function (res) {
reduceTxAndPush(pubkey, res.history.sent, tx.history, processedTxMap, false);
reduceTxAndPush(pubkey, res.history.received, tx.history, processedTxMap, false);
};
jobs.push(BMA.tx.history.timesNoCache({pubkey: pubkey, from: nowInSec - (nowInSec % sliceTime), to: nowInSec+999999999}) // get TX from a given time
.then(_reduceTx)); if (fromTime > 0) {
jobs.push($q.all(slices.map(function(slice) {
return BMA.tx.history.times(slice.params, slice.cache).then(reduceTxFn);
})));
} }
// get all TX // get all TX
else { else {
jobs.push(BMA.tx.history.all({pubkey: pubkey}) jobs.push(BMA.tx.history.all({pubkey: pubkey}).then(reduceTxFn));
.then(_reduceTx)
);
} }
// get UD history // get UD history
if (csSettings.data.showUDHistory && fromTime > 0) { if (csSettings.data.showUDHistory) {
/*jobs.push( // FIXME: cannot use BMA here, because it return only NOT consumed UD !
BMA.ud.history({pubkey: pubkey}) /*var reduceUdFn = function(res) {
.then(function(res){ if (!res || !res.history || !res.history.history) return;
udHistory = !res.history || !res.history.history ? [] :
_.forEach(res.history.history, function(ud){ _.forEach(res.history.history, function(ud){
if (ud.time < fromTime) return res; // skip to old UD if (ud.time < fromTime) return res; // skip to old UD
var amount = powBase(ud.amount, ud.base); var amount = powBase(ud.amount, ud.base);
udHistory.push({ tx.history.push({
id: [amount, 'ud', ud.time].join(':'),
time: ud.time, time: ud.time,
amount: amount, amount: amount,
isUD: true, isUD: true,
block_number: ud.block_number block_number: ud.block_number
}); });
}); });
}));*/ };
// get UD from a given time
if (fromTime > 0) {
jobs.push($q.all(slices.map(function(slice) {
return BMA.ud.history.times(slice.params, slice.cache).then(reduceUdFn);
})));
}
// get all UD
else {
jobs.push(BMA.ud.history.all({pubkey: pubkey}).then(reduceUdFn));
}*/
// API extension // API extension
jobs.push( jobs.push(
api.data.raisePromise.loadUDs({ api.data.raisePromise.loadUDs({
...@@ -226,13 +246,11 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -226,13 +246,11 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
tx.history.sort(function(tx1, tx2) { tx.history.sort(function(tx1, tx2) {
return (tx2.time - tx1.time); return (tx2.time - tx1.time);
}); });
tx.validating = tx.history.filter(function(tx) { var firstValidatedTxIndex = _.findIndex(tx.history, function(tx){
return (tx.block_number > current.number - csSettings.data.blockValidityWindow); return (tx.block_number <= current.number - csSettings.data.blockValidityWindow);
}); });
// remove validating from history // remove validating from history
if (tx.validating.length) { tx.validating = firstValidatedTxIndex > 0 ? tx.history.splice(0, firstValidatedTxIndex) : [];
tx.history.splice(0, tx.validating.length);
}
tx.fromTime = fromTime !== 'pending' && fromTime || undefined; tx.fromTime = fromTime !== 'pending' && fromTime || undefined;
tx.toTime = tx.history.length ? tx.history[0].time /*=max(tx.time)*/: tx.fromTime; tx.toTime = tx.history.length ? tx.history[0].time /*=max(tx.time)*/: tx.fromTime;
...@@ -241,27 +259,27 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -241,27 +259,27 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
}) })
.catch(reject); .catch(reject);
}); });
}, }
powBase = function(amount, base) { function powBase(amount, base) {
return base <= 0 ? amount : amount * Math.pow(10, base); return base <= 0 ? amount : amount * Math.pow(10, base);
}, }
addSource = function(src, sources, sourcesIndexByKey) { function addSource(src, sources, sourcesIndexByKey) {
var srcKey = src.type+':'+src.identifier+':'+src.noffset; var srcKey = src.type+':'+src.identifier+':'+src.noffset;
if (angular.isUndefined(sourcesIndexByKey[srcKey])) { if (angular.isUndefined(sourcesIndexByKey[srcKey])) {
sources.push(src); sources.push(src);
sourcesIndexByKey[srcKey] = sources.length - 1; sourcesIndexByKey[srcKey] = sources.length - 1;
} }
}, }
addSources = function(result, sources) { function addSources(result, sources) {
_(sources).forEach(function(src) { _.forEach(sources, function(src) {
addSource(src, result.sources, result.sourcesIndexByKey); addSource(src, result.sources, result.sourcesIndexByKey);
}); });
}, }
loadSourcesAndBalance = function(pubkey) { function loadSourcesAndBalance(pubkey) {
return BMA.tx.sources({pubkey: pubkey}) return BMA.tx.sources({pubkey: pubkey})
.then(function(res){ .then(function(res){
var data = { var data = {
...@@ -282,10 +300,16 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -282,10 +300,16 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
console.warn("[tx] Error while getting sources...", err); console.warn("[tx] Error while getting sources...", err);
throw err; throw err;
}); });
}, }
loadData = function(pubkey, fromTime) { function loadData(pubkey, fromTime) {
var now = Date.now(); var now = Date.now();
var data;
// Alert user, when request is too long (> 2s)
$timeout(function() {
if (!data) UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
}, 2000);
return $q.all([ return $q.all([
...@@ -298,7 +322,7 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -298,7 +322,7 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
.then(function(res) { .then(function(res) {
// Copy sources and balance // Copy sources and balance
var data = res[0]; data = res[0];
data.tx = res[1]; data.tx = res[1];
var txPendings = []; var txPendings = [];
...@@ -308,38 +332,49 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -308,38 +332,49 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
function _processPendingTx(tx) { function _processPendingTx(tx) {
var consumedSources = []; var consumedSources = [];
var valid = true; // do not check sources from received TX
if (tx.amount > 0) { // do not check sources from received TX // => move this tx in errors (even if not really)
valid = false; if (tx.amount > 0) {
// TODO get sources from the issuer ? txErrors.push(tx);
} }
else { else {
_.forEach(tx.inputs, function(input) { var validInputs = true;
_.find(tx.inputs, function(input) {
var inputKey = input.split(':').slice(2).join(':'); var inputKey = input.split(':').slice(2).join(':');
var srcIndex = data.sourcesIndexByKey[inputKey]; var srcIndex = data.sourcesIndexByKey[inputKey];
if (angular.isDefined(srcIndex)) {
// The input source not exists: mark as invalid
if (!angular.isDefined(srcIndex)) {
validInputs = false;
return true; // break
}
// Mark input source as consumed
consumedSources.push(data.sources[srcIndex]); consumedSources.push(data.sources[srcIndex]);
});
// Some input source not exist: mark as error
if (!validInputs) {
console.error("[tx] Pending TX '{}' use an unknown source as input: mark as error".format(tx.hash));
txErrors.push(tx);
} }
// All tx inputs are valid
else { else {
valid = false; // Add tx outputs has new sources
return false; // break if (tx.sources) {
}
});
if (tx.sources) { // add source output
addSources(data, tx.sources); addSources(data, tx.sources);
} }
delete tx.sources; // DO NOT modify a cached data
delete tx.inputs; //delete tx.sources;
} //delete tx.inputs;
if (valid) {
balanceWithPending += tx.amount; // update balance balanceWithPending += tx.amount; // update balance
txPendings.push(tx); txPendings.push(tx);
_.forEach(consumedSources, function(src) { _.forEach(consumedSources, function(src) {
src.consumed = true; src.consumed = true;
}); });
} }
else {
txErrors.push(tx);
} }
} }
...@@ -374,19 +409,25 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -374,19 +409,25 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
var allTx = (data.tx.history || []).concat(data.tx.validating||[], data.tx.pendings||[], data.tx.errors||[]); var allTx = (data.tx.history || []).concat(data.tx.validating||[], data.tx.pendings||[], data.tx.errors||[]);
return csWot.extendAll(allTx, 'pubkey') return csWot.extendAll(allTx, 'pubkey')
.then(function() { .then(function() {
console.debug('[tx] TX and sources loaded in '+ (Date.now()-now) +'ms'); console.debug('[tx] Sources and {0}TX loaded in {1}ms'.format(
fromTime === 'pending' ? 'pending ' : '',
Date.now() - now));
return data; return data;
}); });
})
.catch(function(err) {
console.warn('[tx] Error while getting sources and tx: ' + (err && err.message || err), err);
throw err;
}); });
}, }
loadSources = function(pubkey) { function loadSources(pubkey) {
console.debug("[tx] Loading sources for " + pubkey.substring(0,8)); console.debug("[tx] Loading sources for " + pubkey.substring(0,8));
return loadData(pubkey, 'pending'); return loadData(pubkey, 'pending');
}; }
// Download TX history file // Download TX history file
downloadHistoryFile = function(pubkey, options) { function downloadHistoryFile(pubkey, options) {
options = options || {}; options = options || {};
options.fromTime = options.fromTime || -1; options.fromTime = options.fromTime || -1;
...@@ -437,32 +478,21 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services', ...@@ -437,32 +478,21 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
tx.pubkey, tx.pubkey,
formatDecimal(tx.amount/100), formatDecimal(tx.amount/100),
'"' + (tx.isUD ? translations['COMMON.UNIVERSAL_DIVIDEND'] : tx.comment) + '"' '"' + (tx.isUD ? translations['COMMON.UNIVERSAL_DIVIDEND'] : tx.comment) + '"'
].join(';') + '\n'); ].join(';'));
}, [headers.join(';') + '\n']); }, [headers.join(';')]).join('\n');
return Device.file.save(content, {filename: filename});
var file = new Blob(content, {type: 'text/plain; charset=utf-8'});
FileSaver.saveAs(file, filename);
}); });
}); });
}; }
// Register extension points // Register extension points
api.registerEvent('data', 'loadUDs'); api.registerEvent('data', 'loadUDs');
return { return {
id: id,
load: loadData, load: loadData,
loadSources: loadSources, loadSources: loadSources,
downloadHistoryFile: downloadHistoryFile, downloadHistoryFile: downloadHistoryFile,
// api extension // api extension
api: api api: api
}; };
}
var service = new CsTx('default');
service.instance = function(id, bma) {
return new CsTx(id, bma);
};
return service;
}); });
angular.module('cesium.utils.services', []) // var qrcode;
angular.module('cesium.utils.services', ['angular-fullscreen-toggle'])
// Replace the '$ionicPlatform.ready()', to enable multiple calls // Replace the '$ionicPlatform.ready()', to enable multiple calls
// See http://stealthcode.co/multiple-calls-to-ionicplatform-ready/ // See http://stealthcode.co/multiple-calls-to-ionicplatform-ready/
...@@ -41,17 +43,21 @@ angular.module('cesium.utils.services', []) ...@@ -41,17 +43,21 @@ angular.module('cesium.utils.services', [])
function alertError(err, subtitle) { function alertError(err, subtitle) {
if (!err) { if (!err) {
return $q.when(); return $q.when(); // Silent
} }
return $q(function(resolve) { return $q(function(resolve) {
$translate([err, subtitle, 'ERROR.POPUP_TITLE', 'ERROR.UNKNOWN_ERROR', 'COMMON.BTN_OK']) $translate(['ERROR.POPUP_TITLE', 'ERROR.UNKNOWN_ERROR', 'COMMON.BTN_OK']
// avoid error "translationId must be a not empty string"
.concat(typeof err === 'string' ? [err] : [])
.concat(subtitle ? [subtitle] : [])
)
.then(function (translations) { .then(function (translations) {
var message = err.message || translations[err]; var message = err.message || translations[err];
return $ionicPopup.show({ return $ionicPopup.show({
template: '<p>' + (message || translations['ERROR.UNKNOWN_ERROR']) + '</p>', template: '<p>' + (message || translations['ERROR.UNKNOWN_ERROR']) + '</p>',
title: translations['ERROR.POPUP_TITLE'], title: translations['ERROR.POPUP_TITLE'],
subTitle: translations[subtitle], subTitle: subtitle && translations[subtitle] || undefined,
buttons: [ buttons: [
{ {
text: '<b>'+translations['COMMON.BTN_OK']+'</b>', text: '<b>'+translations['COMMON.BTN_OK']+'</b>',
...@@ -66,17 +72,23 @@ angular.module('cesium.utils.services', []) ...@@ -66,17 +72,23 @@ angular.module('cesium.utils.services', [])
}); });
} }
function alertInfo(message, subtitle) { function alertInfo(message, subtitle, options) {
if (!message) return $q.reject("Missing 'message' argument");
options = options || {};
options.cssClass = options.cssClass || 'info';
options.okText = options.okText || 'COMMON.BTN_OK';
return $q(function(resolve) { return $q(function(resolve) {
$translate([message, subtitle, 'INFO.POPUP_TITLE', 'COMMON.BTN_OK']) $translate([message, 'INFO.POPUP_TITLE', options.okText].concat(subtitle ? [subtitle] : []))
.then(function (translations) { .then(function (translations) {
$ionicPopup.show({ $ionicPopup.show({
template: '<p>' + translations[message] + '</p>', template: '<p>' + translations[message] + '</p>',
title: translations['INFO.POPUP_TITLE'], title: translations['INFO.POPUP_TITLE'],
subTitle: translations[subtitle], subTitle: subtitle && translations[subtitle] || undefined,
cssClass: options.cssClass,
buttons: [ buttons: [
{ {
text: translations['COMMON.BTN_OK'], text: translations[options.okText],
type: 'button-positive', type: 'button-positive',
onTap: function(e) { onTap: function(e) {
resolve(e); resolve(e);
...@@ -92,7 +104,16 @@ angular.module('cesium.utils.services', []) ...@@ -92,7 +104,16 @@ angular.module('cesium.utils.services', [])
return alertInfo('INFO.FEATURES_NOT_IMPLEMENTED'); return alertInfo('INFO.FEATURES_NOT_IMPLEMENTED');
} }
function alertDemo() {
return $translate(["MODE.DEMO.FEATURE_NOT_AVAILABLE", "MODE.DEMO.INSTALL_HELP"])
.then(function(translations) {
var message = translations["MODE.DEMO.FEATURE_NOT_AVAILABLE"] + "<br/><br/>" + translations["MODE.DEMO.INSTALL_HELP"];
return alertInfo(message, undefined, {cssClass: 'large'});
});
}
function askConfirm(message, title, options) { function askConfirm(message, title, options) {
message = message || 'CONFIRM.CAN_CONTINUE';
title = title || 'CONFIRM.POPUP_TITLE'; title = title || 'CONFIRM.POPUP_TITLE';
options = options || {}; options = options || {};
...@@ -157,6 +178,7 @@ angular.module('cesium.utils.services', []) ...@@ -157,6 +178,7 @@ angular.module('cesium.utils.services', [])
} }
function showToast(message, duration, position) { function showToast(message, duration, position) {
if (!message) return $q.reject("Missing 'message' argument");
duration = duration || 'short'; duration = duration || 'short';
position = position || 'bottom'; position = position || 'bottom';
...@@ -175,7 +197,7 @@ angular.module('cesium.utils.services', []) ...@@ -175,7 +197,7 @@ angular.module('cesium.utils.services', [])
// Use the $ionicLoading toast. // Use the $ionicLoading toast.
// First, make sure to convert duration in number // First, make sure to convert duration in number
if (typeof duration == 'string') { if (typeof duration == 'string') {
if (duration == 'short') { if (duration === 'short') {
duration = 2000; duration = 2000;
} }
else { else {
...@@ -203,7 +225,7 @@ angular.module('cesium.utils.services', []) ...@@ -203,7 +225,7 @@ angular.module('cesium.utils.services', [])
reject(fullMsg); reject(fullMsg);
} }
// If just a user cancellation: silent // If just a user cancellation: silent
else if (fullMsg == 'CANCELLED') { else if (fullMsg === 'CANCELLED') {
return hideLoading(10); // timeout, to avoid bug on transfer (when error on reference) return hideLoading(10); // timeout, to avoid bug on transfer (when error on reference)
} }
...@@ -220,7 +242,7 @@ angular.module('cesium.utils.services', []) ...@@ -220,7 +242,7 @@ angular.module('cesium.utils.services', [])
} }
function selectElementText(el) { function selectElementText(el) {
if (el.value || el.type == "text" || el.type == "textarea") { if (el.value || el.type === "text" || el.type === "textarea") {
// Source: http://stackoverflow.com/questions/14995884/select-text-on-input-focus // Source: http://stackoverflow.com/questions/14995884/select-text-on-input-focus
if ($window.getSelection && !$window.getSelection().toString()) { if ($window.getSelection && !$window.getSelection().toString()) {
el.setSelectionRange(0, el.value.length); el.setSelectionRange(0, el.value.length);
...@@ -412,10 +434,9 @@ angular.module('cesium.utils.services', []) ...@@ -412,10 +434,9 @@ angular.module('cesium.utils.services', [])
options = options || {}; options = options || {};
options.templateUrl = options.templateUrl ? options.templateUrl : 'templates/common/popover_copy.html'; options.templateUrl = options.templateUrl ? options.templateUrl : 'templates/common/popover_copy.html';
options.scope = options.scope && options.scope || $rootScope; options.scope = options.scope || $rootScope;
options.scope.popovers = options.scope.popovers || {}; options.scope.popovers = options.scope.popovers || {};
options.autoselect = options.autoselect || false; options.autoselect = options.autoselect || false;
options.bindings = options.bindings || {};
options.autoremove = angular.isDefined(options.autoremove) ? options.autoremove : true; options.autoremove = angular.isDefined(options.autoremove) ? options.autoremove : true;
options.backdropClickToClose = angular.isDefined(options.backdropClickToClose) ? options.backdropClickToClose : true; options.backdropClickToClose = angular.isDefined(options.backdropClickToClose) ? options.backdropClickToClose : true;
options.focusFirstInput = angular.isDefined(options.focusFirstInput) ? options.focusFirstInput : false; options.focusFirstInput = angular.isDefined(options.focusFirstInput) ? options.focusFirstInput : false;
...@@ -426,7 +447,9 @@ angular.module('cesium.utils.services', []) ...@@ -426,7 +447,9 @@ angular.module('cesium.utils.services', [])
popover.deferred=deferred; popover.deferred=deferred;
popover.options=options; popover.options=options;
// Fill the popover scope // Fill the popover scope
if (options.bindings) {
angular.merge(popover.scope, options.bindings); angular.merge(popover.scope, options.bindings);
}
$timeout(function() { // This is need for Firefox $timeout(function() { // This is need for Firefox
popover.show(event) popover.show(event)
.then(function() { .then(function() {
...@@ -660,7 +683,7 @@ angular.module('cesium.utils.services', []) ...@@ -660,7 +683,7 @@ angular.module('cesium.utils.services', [])
// but many fabs can have the same id (many view could be loaded at the same time) // but many fabs can have the same id (many view could be loaded at the same time)
var fabs = document.getElementsByClassName('button-fab'); var fabs = document.getElementsByClassName('button-fab');
_.forEach(fabs, function(fab){ _.forEach(fabs, function(fab){
if (fab.id == id) { if (fab.id === id) {
fab.classList.toggle('on', false); fab.classList.toggle('on', false);
} }
}); });
...@@ -690,7 +713,7 @@ angular.module('cesium.utils.services', []) ...@@ -690,7 +713,7 @@ angular.module('cesium.utils.services', [])
} }
function setEffects(enable) { function setEffects(enable) {
if (exports.motion.enable === enable) return; // same if (exports.motion.enable === enable) return; // unchanged
console.debug('[UI] [effects] ' + (enable ? 'Enable' : 'Disable')); console.debug('[UI] [effects] ' + (enable ? 'Enable' : 'Disable'));
exports.motion.enable = enable; exports.motion.enable = enable;
...@@ -701,7 +724,7 @@ angular.module('cesium.utils.services', []) ...@@ -701,7 +724,7 @@ angular.module('cesium.utils.services', [])
else { else {
$ionicConfig.views.transition('none'); $ionicConfig.views.transition('none');
var nothing = { var nothing = {
class: undefined, ionListClass: '',
show: function(){} show: function(){}
}; };
angular.merge(exports.motion, { angular.merge(exports.motion, {
...@@ -739,6 +762,50 @@ angular.module('cesium.utils.services', []) ...@@ -739,6 +762,50 @@ angular.module('cesium.utils.services', [])
toggleOff: toggleOff toggleOff: toggleOff
}; };
function createQRCodeObj(text, typeNumber,
errorCorrectionLevel, mode, mb) {
mb = mb || 'default'; // default | SJIS | UTF-8
qrcode.stringToBytes = qrcode.stringToBytesFuncs[mb];
var qr = qrcode(typeNumber || 4, errorCorrectionLevel || 'M');
qr.addData(text, mode);
qr.make();
return qr;
}
/**
* Create a QRCode as an <svg> tag
* @param text
* @param typeNumber
* @param errorCorrectionLevel
* @param mode
* @param mb multibyte ? value: 'default' | 'SJIS' | 'UTF-8'
* @returns {string}
*/
function getSvgQRCode(text, typeNumber,
errorCorrectionLevel, mode, mb) {
var qr = createQRCodeObj(text, typeNumber, errorCorrectionLevel, mode, mb);
return qr.createSvgTag();
}
/**
* Create a QRCode as an <img> tag
* @param text
* @param typeNumber
* @param errorCorrectionLevel
* @param mode
* @param mb multibyte ? value: 'default' | 'SJIS' | 'UTF-8'
* @returns {string}
*/
function getImgQRCode(text, typeNumber,
errorCorrectionLevel, mode, mb) {
var qr = createQRCodeObj(text, typeNumber, errorCorrectionLevel, mode, mb);
return qr.createImgTag();
}
function toggleOn(options, timeout) { function toggleOn(options, timeout) {
// We have a single option, so it may be passed as a string or property // We have a single option, so it may be passed as a string or property
...@@ -793,7 +860,8 @@ angular.module('cesium.utils.services', []) ...@@ -793,7 +860,8 @@ angular.module('cesium.utils.services', [])
error: alertError, error: alertError,
info: alertInfo, info: alertInfo,
confirm: askConfirm, confirm: askConfirm,
notImplemented: alertNotImplemented notImplemented: alertNotImplemented,
demo: alertDemo
}, },
loading: { loading: {
show: showLoading, show: showLoading,
...@@ -811,6 +879,10 @@ angular.module('cesium.utils.services', []) ...@@ -811,6 +879,10 @@ angular.module('cesium.utils.services', [])
ink: ionicMaterialInk.displayEffect, ink: ionicMaterialInk.displayEffect,
motion: raw.motion, motion: raw.motion,
setEffects: setEffects, setEffects: setEffects,
qrcode: {
svg: getSvgQRCode,
img: getImgQRCode
},
fab: { fab: {
show: showFab, show: showFab,
hide: hideFab hide: hideFab
......
...@@ -4,14 +4,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -4,14 +4,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
.factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, $ionicHistory, UIUtils, .factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, $ionicHistory, UIUtils,
Api, Idle, localStorage, sessionStorage, Modals, Api, Idle, localStorage, sessionStorage, Modals, Device,
CryptoUtils, csCrypto, BMA, csConfig, csSettings, FileSaver, Blob, csWot, csTx, csCurrency) { CryptoUtils, csCrypto, BMA, csConfig, csSettings, FileSaver, csWot, csTx, csCurrency) {
'ngInject'; 'ngInject';
var defaultBMA = BMA; var defaultBMA = BMA;
var service; var service;
function factory(id, BMA) { function CsWallet(id, BMA) {
BMA = BMA || defaultBMA; BMA = BMA || defaultBMA;
var var
...@@ -22,11 +22,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -22,11 +22,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
STORAGE_DATA_PREFIX: 'data-', STORAGE_DATA_PREFIX: 'data-',
STORAGE_SECKEY: 'seckey', STORAGE_SECKEY: 'seckey',
/* Need for compat with old currencies (test_net and sou) */ /* Need for compat with old currencies (test_net and sou) */
TX_VERSION: csConfig.compatProtocol_0_80 ? 3 : BMA.constants.PROTOCOL_VERSION, TX_VERSION: BMA.constants.PROTOCOL_VERSION,
IDTY_VERSION: csConfig.compatProtocol_0_80 ? 2 : BMA.constants.PROTOCOL_VERSION, IDTY_VERSION: BMA.constants.PROTOCOL_VERSION,
MS_VERSION: csConfig.compatProtocol_0_80 ? 2 : BMA.constants.PROTOCOL_VERSION, MS_VERSION: BMA.constants.PROTOCOL_VERSION,
CERT_VERSION: csConfig.compatProtocol_0_80 ? 2 : BMA.constants.PROTOCOL_VERSION, CERT_VERSION: BMA.constants.PROTOCOL_VERSION,
REVOKE_VERSION: csConfig.compatProtocol_0_80 ? 2 : BMA.constants.PROTOCOL_VERSION, REVOKE_VERSION: BMA.constants.PROTOCOL_VERSION,
TX_MAX_INPUTS_COUNT: 40 // Allow to get a TX with less than 100 rows (=max row count in Duniter protocol) TX_MAX_INPUTS_COUNT: 40 // Allow to get a TX with less than 100 rows (=max row count in Duniter protocol)
}, },
data = {}, data = {},
...@@ -41,6 +41,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -41,6 +41,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
resetData = function(init) { resetData = function(init) {
data.loaded = false; data.loaded = false;
data.pubkey = null; data.pubkey = null;
data.checksum = null;
data.qrcode=null;
data.uid = null; data.uid = null;
data.localName = null; data.localName = null;
...@@ -120,6 +122,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -120,6 +122,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
addSource = function(src, sources, sourcesIndexByKey) { addSource = function(src, sources, sourcesIndexByKey) {
var srcKey = src.type+':'+src.identifier+':'+src.noffset; var srcKey = src.type+':'+src.identifier+':'+src.noffset;
if (angular.isUndefined(sourcesIndexByKey[srcKey])) { if (angular.isUndefined(sourcesIndexByKey[srcKey])) {
if (!src.conditions) {
console.warn("Trying to add a source without output condition !", src);
}
sources.push(src); sources.push(src);
sourcesIndexByKey[srcKey] = sources.length - 1; sourcesIndexByKey[srcKey] = sources.length - 1;
} }
...@@ -128,7 +133,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -128,7 +133,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
addSources = function(sources) { addSources = function(sources) {
data.sources = data.sources || []; data.sources = data.sources || [];
data.sourcesIndexByKey = data.sourcesIndexByKey || {}; data.sourcesIndexByKey = data.sourcesIndexByKey || {};
_(sources).forEach(function(src) { _.forEach(sources, function(src) {
addSource(src, data.sources, data.sourcesIndexByKey); addSource(src, data.sources, data.sourcesIndexByKey);
}); });
}, },
...@@ -155,8 +160,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -155,8 +160,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
var keepAuth = csSettings.data.keepAuthIdle > 0; var keepAuth = csSettings.data.keepAuthIdle > 0;
var authData; var authData;
return (options && options.authData && $q.when(options.authData) || return (options && options.authData ? $q.when(options.authData) : Modals.showLogin(options))
Modals.showLogin(options))
.then(function(res){ .then(function(res){
if (!res || !res.pubkey || if (!res || !res.pubkey ||
(!needLogin && res.pubkey !== data.pubkey) || (!needLogin && res.pubkey !== data.pubkey) ||
...@@ -232,13 +236,16 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -232,13 +236,16 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}) })
.then(function() { .then(function() {
if (options && options.silent) { if (options && options.silent) {
UIUtils.loading.hide(); UIUtils.loading.hide(10);
}
else {
UIUtils.loading.hide(1000);
} }
return keepAuth ? data : angular.merge({}, data, authData); return keepAuth ? data : angular.merge({}, data, authData);
}) })
.catch(function(err) { .catch(function(err) {
if (err == 'RETRY' && (!options || !options.authData)) { if (err === 'RETRY' && (!options || !options.authData)) {
return $timeout(function(){ return $timeout(function(){
return login(options); return login(options);
}, 300); }, 300);
...@@ -283,6 +290,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -283,6 +290,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}); });
} }
// Disable auth, if readonly or demo
if (csConfig.readonly || csConfig.demo) {
return UIUtils.alert.demo()
.then(function() {
throw 'CANCELLED';
});
}
if (isAuth() && (!options || !options.forceAuth)) { if (isAuth() && (!options || !options.forceAuth)) {
return $q.when(data); return $q.when(data);
} }
...@@ -341,7 +356,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -341,7 +356,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
if (options) { if (options) {
if (options.minData && !options.sources) return data.loaded && true; if (options.minData && !options.sources) return data.loaded && true;
if (options.requirements && !data.requirements.loaded) return false; if (options.requirements && !data.requirements.loaded) return false;
if (options.tx && options.tx.enable && (!data.tx.fromTime || data.tx.fromTime == 'pending')) return false; if (options.tx && options.tx.enable && (!data.tx.fromTime || data.tx.fromTime === 'pending')) return false;
if (options.sigStock && !data.sigStock) return false; if (options.sigStock && !data.sigStock) return false;
} }
return data.loaded && data.sources && true; return data.loaded && data.sources && true;
...@@ -357,14 +372,18 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -357,14 +372,18 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
!data.requirements.needSelf || !data.requirements.needSelf ||
data.requirements.wasMember || data.requirements.wasMember ||
// Check sources
(data.sources && data.sources.length > 0) ||
// Check TX history // Check TX history
data.tx.history.length || data.tx.history.length > 0 ||
data.tx.pendings.length || data.tx.validating.length > 0 ||
data.tx.pendings.length > 0 ||
// Check extended data (name+avatar) // Check extended data (name+avatar)
data.localName || !!data.localName ||
data.name || !!data.name ||
data.avatar !!data.avatar
); );
}, },
...@@ -378,7 +397,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -378,7 +397,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
// store pubkey and uid // store pubkey and uid
store = function(pubkey) { store = function(pubkey) {
pubkey = pubkey && typeof pubkey == 'string' ? pubkey : data.pubkey; pubkey = pubkey && typeof pubkey == 'string' ? pubkey : data.pubkey;
if (settings.useLocalStorage) { if (settings && settings.useLocalStorage) {
if (isLogin() && settings.rememberMe) { if (isLogin() && settings.rememberMe) {
...@@ -443,7 +462,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -443,7 +462,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
localStorage.put(constants.STORAGE_PUBKEY, null); localStorage.put(constants.STORAGE_PUBKEY, null);
localStorage.put(constants.STORAGE_UID, null); localStorage.put(constants.STORAGE_UID, null);
if (settings.useLocalStorage) { if (settings && settings.useLocalStorage) {
// Clean data (only in the session storage - keep local) // Clean data (only in the session storage - keep local)
return pubkey ? sessionStorage.put(constants.STORAGE_DATA_PREFIX + pubkey, null) : $q.when(); return pubkey ? sessionStorage.put(constants.STORAGE_DATA_PREFIX + pubkey, null) : $q.when();
} }
...@@ -460,7 +479,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -460,7 +479,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
storeData = function() { storeData = function() {
if (!isLogin()) throw {message:'ERROR.NEED_LOGIN_FIRST'}; if (!isLogin()) throw {message:'ERROR.NEED_LOGIN_FIRST'};
var useEncryption = settings.useLocalStorageEncryption; var useEncryption = settings && settings.useLocalStorageEncryption;
var storageKey = constants.STORAGE_DATA_PREFIX + data.pubkey; var storageKey = constants.STORAGE_DATA_PREFIX + data.pubkey;
var content; // Init only if used var content; // Init only if used
...@@ -548,12 +567,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -548,12 +567,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
var seckey = res[0]; var seckey = res[0];
var pubkey = res[1]; var pubkey = res[1];
var uid = res[2]; var uid = res[2];
if (!pubkey || pubkey == 'null') return; if (!pubkey || pubkey === 'null') return;
console.debug('[wallet] Restore {' + pubkey.substring(0, 8) + '} from local storage'); console.debug('[wallet] Restore {' + pubkey.substring(0, 8) + '} from local storage');
var keypair; var keypair;
if (seckey && seckey.length && seckey != 'null') { if (seckey && seckey.length && seckey !== 'null') {
try { try {
keypair = { keypair = {
signPk: CryptoUtils.util.decode_base58(pubkey), signPk: CryptoUtils.util.decode_base58(pubkey),
...@@ -641,7 +660,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -641,7 +660,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
_.forEach(content.children, function(child) { _.forEach(content.children, function(child) {
if (!pubkeys[child.pubkey]) { // make sure wallet is unique by pubkey if (!pubkeys[child.pubkey]) { // make sure wallet is unique by pubkey
pubkeys[child.pubkey] = true; pubkeys[child.pubkey] = true;
var wallet = getNewChildrenInstance(); var wallet = newChildInstance();
wallet.data.pubkey = child.pubkey; wallet.data.pubkey = child.pubkey;
wallet.data.localName = child.localName; wallet.data.localName = child.localName;
wallet.data.uid = child.uid; wallet.data.uid = child.uid;
...@@ -688,15 +707,31 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -688,15 +707,31 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return data; return data;
}, },
loadRequirements = function() { loadRequirements = function(withCache, secondTry) {
// Clean existing events // Clean existing events
cleanEventsByContext('requirements'); cleanEventsByContext('requirements');
// Get requirements // Get requirements
return csWot.loadRequirements(data); return csWot.loadRequirements(data, withCache)
.catch(function(err) {
// Retry once (can be a timeout, because Duniter node are long to response)
if (!secondTry) {
console.error("[wallet] Unable to load requirements (first try): {0}. Retrying once...".format(err && err.message || err), err);
UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
return loadRequirements(withCache, true);
}
console.error("[wallet] Unable to load requirements (after a second try): {0}".format(err && err.message || err), err);
throw err;
});
}, },
loadTxAndSources = function(fromTime) { loadTxAndSources = function(fromTime) {
// DEBUG
//console.debug('[wallet-service] Calling loadTxAndSources() from time ' + fromTime);
if (fromTime === 'pending') {
UIUtils.loading.update({template: "INFO.LOADING_PENDING_TX"});
}
return csTx.load(data.pubkey, fromTime) return csTx.load(data.pubkey, fromTime)
.then(function(res){ .then(function(res){
resetTxAndSources(); resetTxAndSources();
...@@ -824,6 +859,15 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -824,6 +859,15 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}); });
}, },
loadQrCode = function(){
if (!data.pubkey || data.qrcode) return $q.when(data.qrcode);
console.debug("[wallet] Creating SVG QRCode...");
return $timeout(function() {
data.qrcode = UIUtils.qrcode.svg(data.pubkey);
return data.qrcode;
});
},
loadData = function(options) { loadData = function(options) {
var alertIfUnusedWallet = !csCurrency.data.initPhase && (!csSettings.data.wallet || csSettings.data.wallet.alertIfUnusedWallet) && var alertIfUnusedWallet = !csCurrency.data.initPhase && (!csSettings.data.wallet || csSettings.data.wallet.alertIfUnusedWallet) &&
...@@ -846,12 +890,22 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -846,12 +890,22 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
loadPromise = loadFullData(); loadPromise = loadFullData();
} }
return loadPromise return $q.all([
loadPromise,
// Warn if wallet has been never used - see #167 // Create the QR code
loadQrCode(),
// Compute the checksum (if need)
csCrypto.ready().then(function() {
data.checksum = data.checksum || csCrypto.util.pkChecksum(data.pubkey);
})
])
.then(function() { .then(function() {
var unused = isNeverUsed();
var showAlert = alertIfUnusedWallet && !isNew() && angular.isDefined(unused) && unused; // Warn if wallet has been never used - see #167
var unused = alertIfUnusedWallet && isNeverUsed();
var showAlert = alertIfUnusedWallet && !isNew() && unused === true;
if (!showAlert) return true; if (!showAlert) return true;
return UIUtils.loading.hide() return UIUtils.loading.hide()
.then(function () { .then(function () {
...@@ -865,8 +919,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -865,8 +919,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return logout().then(function () { return logout().then(function () {
throw 'RETRY'; throw 'RETRY';
}); });
} } else {
else {
// Remembering to not ask for confirmation // Remembering to not ask for confirmation
if (csSettings.data.wallet.alertIfUnusedWallet) { if (csSettings.data.wallet.alertIfUnusedWallet) {
csSettings.data.wallet.alertIfUnusedWallet = false; csSettings.data.wallet.alertIfUnusedWallet = false;
...@@ -883,20 +936,27 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -883,20 +936,27 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
if (confirm) { if (confirm) {
return data; return data;
} }
else { // cancel else { // user cancelled
throw 'CANCELLED'; throw 'CANCELLED';
} }
})
.catch(function(err) {
loadPromise = null;
console.error('[wallet] Failed to load wallet data', err);
throw err;
}); });
}, },
loadFullData = function() { loadFullData = function(fromTime) {
data.loaded = false; data.loaded = false;
var now = Date.now();
console.debug("[wallet] Loading {{0}} full data...".format(data.pubkey && data.pubkey.substr(0,8)));
return $q.all([ return $q.all([
// Get requirements // Get requirements
loadRequirements() loadRequirements(true)
.then(function(data) { .then(function(data) {
if (data.requirements && (data.requirements.isMember || data.requirements.wasMember)) { if (data.requirements && (data.requirements.isMember || data.requirements.wasMember)) {
// Load sigStock // Load sigStock
...@@ -907,8 +967,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -907,8 +967,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
} }
}), }),
// Get TX and sources // Get TX and sources (only pending by default)
loadTxAndSources() loadTxAndSources(fromTime || 'pending')
]) ])
.then(function() { .then(function() {
...@@ -923,6 +983,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -923,6 +983,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}) })
.then(function() { .then(function() {
data.loaded = true; data.loaded = true;
console.debug("[wallet] Loaded {{0}} full data in {1}ms".format(data.pubkey && data.pubkey.substr(0,8), Date.now() - now));
// Make sure to hide loading, because sometimes it stay - should fix freeze screen
UIUtils.loading.hide(1000);
return data; return data;
}) })
.catch(function(err) { .catch(function(err) {
...@@ -938,6 +1002,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -938,6 +1002,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
if (!options.requirements) { if (!options.requirements) {
return $q.when(data); return $q.when(data);
} }
return refreshData(options) return refreshData(options)
.then(function(data) { .then(function(data) {
data.loaded = true; data.loaded = true;
...@@ -951,7 +1016,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -951,7 +1016,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
sources: true, sources: true,
tx: { tx: {
enable: true, enable: true,
fromTime: data.tx ? data.tx.fromTime : undefined // keep previous time fromTime: data.tx && data.tx.fromTime !== 'pending' ? data.tx.fromTime : undefined // keep previous time
}, },
sigStock: true, sigStock: true,
api: true api: true
...@@ -967,13 +1032,16 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -967,13 +1032,16 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
var jobs = []; var jobs = [];
var now = Date.now();
console.debug("[wallet] {0} {{1}} data, with options: ".format(!data.loaded ? 'Loading' : 'Refreshing', data.pubkey.substr(0,8)), options);
// Get requirements // Get requirements
if (options.requirements) { if (options.requirements) {
// Reset events // Reset events
cleanEventsByContext('requirements'); cleanEventsByContext('requirements');
jobs.push( jobs.push(
loadRequirements() loadRequirements(true)
// Add wallet events // Add wallet events
.then(addEvents) .then(addEvents)
...@@ -986,8 +1054,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -986,8 +1054,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
} }
else if (options.sources && (options.tx && !options.tx.enable)) { else if (options.sources && (options.tx && !options.tx.enable)) {
// Get sources (no TX) // Get sources and only pending TX (and NOT the TX history)
jobs.push(loadSources()); jobs.push(loadTxAndSources('pending'));
} }
// Load sigStock // Load sigStock
...@@ -1002,13 +1070,20 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1002,13 +1070,20 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return api.data.raisePromise.load(data) return api.data.raisePromise.load(data)
.then(function(){ .then(function(){
console.debug("[wallet] {0} {{1}} data in {2}ms".format(!data.loaded ? 'Loaded' : 'Refreshed', data.pubkey.substr(0,8), Date.now() - now));
// Compute if full loaded // Compute if full loaded
if (!data.loaded) { if (!data.loaded) {
data.loaded = data.requirements.loaded && data.sources; data.loaded = data.requirements.loaded && data.sources && true;
} }
return data; return data;
}); });
})
.catch(function(err) {
console.error("[wallet] Error while {0} data: {1}".format(!data.loaded ? 'Loading' : 'Refreshing', (err && err.message || err)), err);
data.loaded = data.requirements.loaded && data.sources && true;
throw err;
}); });
}, },
...@@ -1071,7 +1146,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1071,7 +1146,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
var minBase = filterBase; var minBase = filterBase;
var maxBase = filterBase; var maxBase = filterBase;
_.find(data.sources || [], function(source) { _.find(data.sources || [], function(source) {
if (!source.consumed && source.base == filterBase){ if (!source.consumed && source.base === filterBase &&
// Filter on simple SIG output condition - fix #845
BMA.regexp.TX_OUTPUT_SIG.exec(source.conditions)
) {
sourcesAmount += powBase(source.amount, source.base); sourcesAmount += powBase(source.amount, source.base);
sources.push(source); sources.push(source);
} }
...@@ -1108,7 +1186,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1108,7 +1186,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return $q.all([ return $q.all([
getKeypair(), getKeypair(),
csCurrency.get(), csCurrency.get(),
block && $q.when(block) || csCurrency.blockchain.current() block && $q.when(block) || csCurrency.blockchain.current(true)
]) ])
.then(function(res) { .then(function(res) {
var keypair = res[0]; var keypair = res[0];
...@@ -1123,6 +1201,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1123,6 +1201,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
if (!isLogin()){ if (!isLogin()){
throw {message:'ERROR.NEED_LOGIN_FIRST'}; throw {message:'ERROR.NEED_LOGIN_FIRST'};
} }
if (destPub === data.pubkey){
throw {message:'ERROR.SAME_TX_RECIPIENT'};
}
if (!amount) { if (!amount) {
throw {message:'ERROR.AMOUNT_REQUIRED'}; throw {message:'ERROR.AMOUNT_REQUIRED'};
} }
...@@ -1419,7 +1500,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1419,7 +1500,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
type: 'T', type: 'T',
noffset: outputOffset, noffset: outputOffset,
amount: outputAmount, amount: outputAmount,
base: outputBase base: outputBase,
conditions: 'SIG('+restPub+')',
consumed: false
}); });
} }
outputOffset++; outputOffset++;
...@@ -1431,7 +1514,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1431,7 +1514,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
// Append to logs (need to resolve issue #524) // Append to logs (need to resolve issue #524)
if (logs) { if (logs) {
if (destPub == data.pubkey) { if (destPub === data.pubkey) {
logs.push('[wallet] Creating new TX, using inputs:\n - minBase: '+inputs.minBase+'\n - maxBase: '+inputs.maxBase); logs.push('[wallet] Creating new TX, using inputs:\n - minBase: '+inputs.minBase+'\n - maxBase: '+inputs.maxBase);
} }
else { else {
...@@ -1446,14 +1529,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1446,14 +1529,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return CryptoUtils.sign(tx, keypair) return CryptoUtils.sign(tx, keypair)
.then(function(signature) { .then(function(signature) {
var signedTx = tx + signature + "\n"; var signedTx = tx + signature + "\n";
return BMA.tx.process({transaction: signedTx}) return processTx(signedTx)
.catch(function(err) {
if (err && err.ucode === BMA.errorCodes.TX_ALREADY_PROCESSED) {
// continue
return;
}
throw err;
})
.then(function() { .then(function() {
return CryptoUtils.util.hash(signedTx); return CryptoUtils.util.hash(signedTx);
}) })
...@@ -1473,6 +1549,70 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1473,6 +1549,70 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}); });
}, },
processTx = function(signedTx) {
// Send to default BMA node
return BMA.tx.process({transaction: signedTx})
.catch(function(err) {
if (err.ucode === BMA.errorCodes.TX_ALREADY_PROCESSED) {
return; // continue
}
// TX sandbox is full: retry using random peers
if (err.ucode === BMA.errorCodes.TRANSACTIONS_SANDBOX_FULL) {
console.warn('[wallet] Node sandbox is full! Will send TX to some random peers...');
return processTxRandomPeer(signedTx)
.then(function(success) {
if (success) return; // OK, continue
// If all random peers failed: rethrow the original error
throw {ucode: BMA.errorCodes.TRANSACTIONS_SANDBOX_FULL, message: 'ERROR.TX_SANDBOX_FULL'};
});
}
// Other error
throw err;
})
;
},
processTxRandomPeer = function(signedTx, n, timeout) {
n = n || 3;
timeout = timeout || csConfig.timeout;
// Select some peers
var randomPeers = _.sample(csSettings.data.network && csSettings.data.network.peers || [], n);
if (!randomPeers.length) {
return $q.resolve(false); // Skip, if no peers
}
console.warn('[wallet] Sending TX to {0} random peers...'.format(randomPeers.length));
return $q.all(
_.map(randomPeers, function(peer) {
var bma = BMA.lightInstance(peer.host, peer.port, peer.path, peer.useSsl, timeout);
return bma.tx.process({transaction: signedTx})
.then(function() {
return true;
})
.catch(function(err) {
if (err.ucode === BMA.errorCodes.TX_ALREADY_PROCESSED) {
return true;
}
return false;
});
}))
.then(function(res) {
var succeedPeers = _.filter(randomPeers, function(peer, index) {
return res[index];
});
if (succeedPeers.length) {
console.info('[wallet] TX successfully sent to {0} random peers'.format(succeedPeers.length), succeedPeers);
return true; // succeed
}
return false; // succeed
});
},
getIdentityDocument = function(currency, keypair, uid, blockUid) { getIdentityDocument = function(currency, keypair, uid, blockUid) {
uid = uid || data.uid; uid = uid || data.uid;
blockUid = blockUid || data.blockUid; blockUid = blockUid || data.blockUid;
...@@ -1526,7 +1666,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1526,7 +1666,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
.then(function () { .then(function () {
if (!!needToLoadRequirements) { if (!!needToLoadRequirements) {
// Refresh membership data (if need) // Refresh membership data (if need)
return loadRequirements() return loadRequirements(false/*no cache*/)
// Add wallet events // Add wallet events
.then(addEvents); .then(addEvents);
...@@ -1577,7 +1717,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1577,7 +1717,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}) })
.then(function() { .then(function() {
return $timeout(function() { return $timeout(function() {
return loadRequirements(); return loadRequirements(false /*no cache*/);
}, 1000); // waiting for node to process membership doc }, 1000); // waiting for node to process membership doc
}) })
...@@ -1593,12 +1733,18 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1593,12 +1733,18 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return $q.all([ return $q.all([
getKeypair(), getKeypair(),
csCurrency.get(), csCurrency.get(),
csCurrency.blockchain.current() csCurrency.blockchain.lastValid()
]) ])
.then(function(res) { .then(function(res) {
var keypair = res[0]; var keypair = res[0];
var currency = res[1]; var currency = res[1];
var block = res[2]; var block = res[2];
// Check if member account
if (!data.isMember && !csConfig.initPhase) {
throw {message:'ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION'};
}
// Create the self part to sign // Create the self part to sign
var cert = 'Version: '+ constants.CERT_VERSION +'\n' + var cert = 'Version: '+ constants.CERT_VERSION +'\n' +
'Type: Certification\n' + 'Type: Certification\n' +
...@@ -1685,6 +1831,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1685,6 +1831,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}, },
recoverId = function(recover) { recoverId = function(recover) {
if (!recover || !recover.cypherNonce || !recover.cypherSalt || !recover.cypherPwd) {
throw {message:'ERROR.INVALID_FILE_FORMAT'};
}
var nonce = CryptoUtils.util.decode_base58(recover.cypherNonce); var nonce = CryptoUtils.util.decode_base58(recover.cypherNonce);
return getkeypairSaveId(recover) return getkeypairSaveId(recover)
.then(function (recover) { .then(function (recover) {
...@@ -1699,7 +1848,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1699,7 +1848,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return recover; return recover;
}) })
.catch(function(err){ .catch(function(err){
console.warn('Incorrect answers - Unable to recover passwords'); console.warn('Incorrect answers: unable to recover identifiers', err);
throw new Error('Incorrect answers: unable to recover identifiers');
}); });
}, },
...@@ -1725,16 +1875,24 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1725,16 +1875,24 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}, },
downloadSaveId = function(record){ downloadSaveId = function(record){
return getSaveIDDocument(record)
.then(function(saveId) {
var saveIdFile = new Blob([saveId], {type: 'text/plain; charset=utf-8'});
FileSaver.saveAs(saveIdFile, '{0}-recover_ID.txt'.format(data.pubkey.substring(0,8)));
});
return $q.all([
csCurrency.get(),
getSaveIDDocument(record),
])
.then(function(res) {
var currency = res[0];
var document= res[1];
return $translate('ACCOUNT.SECURITY.SAVE_ID_FILENAME', {
currency: currency.name,
pubkey: data.pubkey
})
.then(function(filename){
return Device.file.save(document, {filename: filename});
});
});
}, },
downloadKeyFile = function(format){ downloadKeyFile = function(format){
if (!isAuth()) return $q.reject('user not authenticated'); if (!isAuth()) return $q.reject('user not authenticated');
...@@ -1767,8 +1925,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1767,8 +1925,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
format: format, format: format,
}) })
.then(function(filename){ .then(function(filename){
var file = new Blob([document], {type: 'text/plain; charset=utf-8'}); return Device.file.save(document, {filename: filename});
FileSaver.saveAs(file, filename);
}); });
}); });
...@@ -1832,7 +1989,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1832,7 +1989,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
.then(function() { .then(function() {
return $timeout(function() { return $timeout(function() {
return loadRequirements(); return loadRequirements(false/*no cache*/);
}, 1000); // waiting for node to process membership doc }, 1000); // waiting for node to process membership doc
}) })
...@@ -1859,7 +2016,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1859,7 +2016,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
.then(function(res) { .then(function(res) {
if (isLogin()) { if (isLogin()) {
return $timeout(function () { return $timeout(function () {
return loadRequirements(); return loadRequirements(false/*no cache*/);
}, 1000) // waiting for node to process membership doc }, 1000) // waiting for node to process membership doc
// Add wallet events // Add wallet events
...@@ -1879,7 +2036,6 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1879,7 +2036,6 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
addEvent({type: 'pending', message: 'INFO.REVOCATION_SENT_WAITING_PROCESS', context: 'requirements'}, true); addEvent({type: 'pending', message: 'INFO.REVOCATION_SENT_WAITING_PROCESS', context: 'requirements'}, true);
} }
}); });
}, },
downloadRevocation = function(){ downloadRevocation = function(){
...@@ -1890,14 +2046,13 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1890,14 +2046,13 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
.then(function(res) { .then(function(res) {
var currency = res[0]; var currency = res[0];
var revocation = res[1]; var revocation = res[1];
var revocationFile = new Blob([revocation], {type: 'text/plain; charset=utf-8'});
return $translate('ACCOUNT.SECURITY.REVOCATION_FILENAME', { return $translate('ACCOUNT.SECURITY.REVOCATION_FILENAME', {
uid: data.uid, uid: data.uid,
currency: currency.name, currency: currency.name,
pubkey: data.pubkey pubkey: data.pubkey
}) })
.then(function (fileName) { .then(function (filename) {
FileSaver.saveAs(revocationFile, fileName); return Device.file.save(revocation, {filename: filename});
}); });
}); });
}, },
...@@ -1933,8 +2088,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1933,8 +2088,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}, },
createNewChildWallet = function(options) { createNewChildWallet = function(options) {
var walletId = getChildrenWalletCount()+1; var wallet = newChildInstance();
var wallet = service.instance(walletId);
addChildWallet(wallet, options); addChildWallet(wallet, options);
return wallet; return wallet;
}, },
...@@ -1973,10 +2127,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1973,10 +2127,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
removeChildWalletById = function(id, options) { removeChildWalletById = function(id, options) {
data.children = data.children || []; data.children = data.children || [];
var childIndex = _.findIndex(data.children, function(child) {return child.id == id;}); var childIndex = _.findIndex(data.children, function(child) {return child.id === id;});
if (childIndex === -1) { if (childIndex === -1) {
console.warn('[wallet] Unable to remove child wallet {'+id+'} (not found)'); console.warn('[wallet] Unable to remove child wallet {{0}} (not found)'.format(id));
return; throw new Error('Wallet with id {{0}} not found'.format(id));
} }
// Remove the wallet, and return it // Remove the wallet, and return it
var wallet = data.children.splice(childIndex, 1)[0]; var wallet = data.children.splice(childIndex, 1)[0];
...@@ -1985,13 +2139,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -1985,13 +2139,14 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
wallet.stop(); wallet.stop();
// Store (store children locally) // Store (store children locally)
if (!options || angular.isUndefined(options.store) || options.store) { if (!options || options.store !== false) {
return storeData(); return storeData();
} }
return $q.when();
}, },
getChildWalletById = function(id) { getChildWalletById = function(id) {
return _.find(data.children|| [], function(child) {return child.id == id;}); return (id !== 'default') && _.find(data.children|| [], function(child) {return child.id === +id;}) || undefined;
}, },
getChildWalletByPubkey = function(pubkey) { getChildWalletByPubkey = function(pubkey) {
...@@ -2006,11 +2161,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2006,11 +2161,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return angular.isDefined(data.childrenCount) ? data.childrenCount : (data.children && data.children.length || 0); return angular.isDefined(data.childrenCount) ? data.childrenCount : (data.children && data.children.length || 0);
}, },
getNewChildrenInstance = function() { newChildInstance = function() {
// Return max(id) + 1 // Return max(id) + 1
var walletId = (data.children && data.children.reduce(function(res, wallet) { var walletId = (data.children || []).reduce(function(res, wallet) {
return Math.max(res, wallet.id); return Math.max(res, wallet.id);
}, 0) || data.childrenCount || 0 )+ 1; }, 0) + 1;
return service.instance(walletId, BMA); return service.instance(walletId, BMA);
}, },
...@@ -2021,6 +2176,20 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2021,6 +2176,20 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}); });
}, },
getAllPubkeys = function() {
if (!data.pubkey) throw new Error('User not login!');
return (data.children || []).reduce(function(res, wallet) {
return wallet.data.pubkey ? res.concat(wallet.data.pubkey) : res;
}, [data.pubkey]);
},
getByPubkey = function(pubkey) {
if (!pubkey) throw new Error("Missing 'pubkey' argument !");
if (!data.pubkey) throw new Error('User not login!');
if (data.pubkey === pubkey) return exports; // main wallet
return getChildWalletByPubkey(pubkey);
},
downloadChildrenWalletFile = function() { downloadChildrenWalletFile = function() {
return $q.all([ return $q.all([
getAllChildrenWallet(), getAllChildrenWallet(),
...@@ -2032,13 +2201,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2032,13 +2201,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
var content = (children||[]).reduce(function(res, wallet) { var content = (children||[]).reduce(function(res, wallet) {
return res + [wallet.data.pubkey, wallet.data.uid, wallet.data.localName||wallet.data.name].join('\t') + '\n'; return res + [wallet.data.pubkey, wallet.data.uid, wallet.data.localName||wallet.data.name].join('\t') + '\n';
}, ''); }, '');
var file = new Blob([content], {type: 'text/plain; charset=utf-8'});
return $translate('ACCOUNT.WALLET_LIST.EXPORT_FILENAME', { return $translate('ACCOUNT.WALLET_LIST.EXPORT_FILENAME', {
pubkey: data.pubkey, pubkey: data.pubkey,
currency: currency.name, currency: currency.name,
}) })
.then(function (fileName) { .then(function(filename) {
FileSaver.saveAs(file, fileName); return Device.file.save(content, {filename: filename});
}); });
}); });
}, },
...@@ -2078,7 +2246,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2078,7 +2246,12 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
fromJson = function(json, failIfInvalid) { fromJson = function(json, failIfInvalid) {
failIfInvalid = angular.isUndefined(failIfInvalid) ? true : failIfInvalid; failIfInvalid = angular.isUndefined(failIfInvalid) ? true : failIfInvalid;
return $q(function(resolve, reject) { return $q(function(resolve, reject) {
var obj = JSON.parse(json || '{}'); var obj;
try {
obj = JSON.parse(json || '{}');
}
catch(err) { /* invalid JSON : continue*/}
// FIXME #379 // FIXME #379
/*if (obj && obj.pubkey) { /*if (obj && obj.pubkey) {
resolve({ resolve({
...@@ -2129,7 +2302,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2129,7 +2302,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
checkAuthIdle = function(isAuthResult) { checkAuthIdle = function(isAuthResult) {
isAuthResult = angular.isDefined(isAuthResult) ? isAuthResult : isAuth(); isAuthResult = angular.isDefined(isAuthResult) ? isAuthResult : isAuth();
var newEnableAuthIdle = isAuthResult && settings.keepAuthIdle > 0 && settings.keepAuthIdle != csSettings.constants.KEEP_AUTH_IDLE_SESSION; var newEnableAuthIdle = isAuthResult && settings && settings.keepAuthIdle > 0 && settings.keepAuthIdle != csSettings.constants.KEEP_AUTH_IDLE_SESSION;
var changed = (enableAuthIdle != newEnableAuthIdle); var changed = (enableAuthIdle != newEnableAuthIdle);
// need start/top watching // need start/top watching
...@@ -2155,7 +2328,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2155,7 +2328,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
} }
// Make sure to store seckey, in the session storage for secret key -fix #372 // Make sure to store seckey, in the session storage for secret key -fix #372
var storeSecKey = isAuthResult && settings.keepAuthIdle == csSettings.constants.KEEP_AUTH_IDLE_SESSION && true; var storeSecKey = isAuthResult && settings && settings.keepAuthIdle == csSettings.constants.KEEP_AUTH_IDLE_SESSION && true;
if (storeSecKey) { if (storeSecKey) {
sessionStorage.put(constants.STORAGE_SECKEY, CryptoUtils.util.encode_base58(data.keypair.signSk)); sessionStorage.put(constants.STORAGE_SECKEY, CryptoUtils.util.encode_base58(data.keypair.signSk));
} }
...@@ -2166,7 +2339,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2166,7 +2339,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
}; };
function getWalletSettings(settings) { function getWalletSettings(settings) {
return { return settings && {
useLocalStorage: settings.useLocalStorage, useLocalStorage: settings.useLocalStorage,
useLocalStorageEncryption: settings.useLocalStorageEncryption, useLocalStorageEncryption: settings.useLocalStorageEncryption,
rememberMe: settings.rememberMe, rememberMe: settings.rememberMe,
...@@ -2177,7 +2350,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2177,7 +2350,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
function onSettingsChanged(allSettings) { function onSettingsChanged(allSettings) {
var newSettings = getWalletSettings(allSettings); var newSettings = getWalletSettings(allSettings);
var hasChanged = !angular.equals(settings, newSettings); var hasChanged = !angular.equals(settings, newSettings);
if (!hasChanged) return; // skip if (!hasChanged || !settings) return; // skip
var useEncryptionChanged = !angular.equals(settings.useLocalStorageEncryption, newSettings.useLocalStorageEncryption); var useEncryptionChanged = !angular.equals(settings.useLocalStorageEncryption, newSettings.useLocalStorageEncryption);
var useStorageChanged = !angular.equals(settings.useLocalStorage, newSettings.useLocalStorage) || useEncryptionChanged; var useStorageChanged = !angular.equals(settings.useLocalStorage, newSettings.useLocalStorage) || useEncryptionChanged;
...@@ -2252,10 +2425,29 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2252,10 +2425,29 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return startPromise || start(); return startPromise || start();
} }
function stop() { function stop(options) {
if (!started && !startPromise) return $q.when();
var wasLogin = isLogin();
var wasAuth = isAuth();
console.debug('[wallet] Stopping...'); console.debug('[wallet] Stopping...');
removeListeners(); removeListeners();
if (!options || options.emitEvent !== false) {
// Reset all data
resetData(); resetData();
// Send logout/unauth events
if (wasLogin) api.data.raise.logout();
if (wasAuth) api.data.raise.unauth();
}
else {
// Just mark as need to reload
data.loaded = false;
}
return $q.when();
} }
function restart() { function restart() {
...@@ -2357,6 +2549,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2357,6 +2549,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
isNew: isNew, isNew: isNew,
isUserPubkey: isUserPubkey, isUserPubkey: isUserPubkey,
getData: getData, getData: getData,
loadQrCode: loadQrCode,
loadData: loadData, loadData: loadData,
refreshData: refreshData, refreshData: refreshData,
// internal // internal
...@@ -2379,6 +2572,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2379,6 +2572,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
recoverId: recoverId, recoverId: recoverId,
downloadRevocation: downloadRevocation, downloadRevocation: downloadRevocation,
downloadKeyFile: downloadKeyFile, downloadKeyFile: downloadKeyFile,
pubkeys: getAllPubkeys,
getByPubkey: getByPubkey,
membership: { membership: {
inside: membership(true), inside: membership(true),
out: membership(false) out: membership(false)
...@@ -2397,7 +2592,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2397,7 +2592,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
setParent: setParentWallet, setParent: setParentWallet,
count: getChildrenWalletCount, count: getChildrenWalletCount,
hasPubkey: hasChildrenWithPubkey, hasPubkey: hasChildrenWithPubkey,
instance: getNewChildrenInstance, instance: newChildInstance,
downloadFile: downloadChildrenWalletFile downloadFile: downloadChildrenWalletFile
}, },
api: api api: api
...@@ -2405,8 +2600,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se ...@@ -2405,8 +2600,8 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
return exports; return exports;
} }
service = factory('default', BMA); service = CsWallet('default', BMA);
service.instance = factory; service.instance = CsWallet;
return service; return service;
}); });
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services', angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services',
'cesium.settings.services']) 'cesium.settings.services'])
.factory('csWot', function($q, $timeout, BMA, Api, CacheFactory, csConfig, csCurrency, csSettings, csCache) { .factory('csWot', function($rootScope, $q, $timeout, BMA, Api, CacheFactory, UIUtils, csConfig, csCurrency, csSettings, csCache) {
'ngInject'; 'ngInject';
function factory(id) {
var var
api = new Api(this, "csWot-" + id), api = new Api(this, "csWot"),
identityCache = csCache.get('csWot-idty-', csCache.constants.SHORT), cachePrefix = 'csWot-',
identityCache = csCache.get(cachePrefix + 'idty-', csCache.constants.MEDIUM),
requirementsCache = csCache.get(cachePrefix + 'requirements-', csCache.constants.MEDIUM),
// Add id, and remove duplicated id // Add id, and remove duplicated id
_addUniqueIds = function(idties) { _addUniqueIds = function(idties) {
...@@ -174,17 +175,31 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -174,17 +175,31 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
}); });
}, },
loadRequirements = function(data) { loadRequirements = function(inputData, withCache) {
if (!data || (!data.pubkey && !data.uid)) return $q.when(data); if (!inputData || (!inputData.pubkey && !inputData.uid)) return $q.when(inputData);
var now = Date.now(); var cacheKey = inputData.pubkey||inputData.uid;
var data = (withCache !== false) ? requirementsCache.get(cacheKey) : null;
if (data) {
console.debug("[wot] Requirements " + cacheKey + " found in cache");
// Update data with cache
angular.merge(inputData, data);
return $q.when(data);
}
data = {pubkey: inputData.pubkey, uid: inputData.uid};
// Alert user, when request is too long (> 2s)
$timeout(function() {
if (!data.requirements || !data.requirements.loaded) UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
}, 2000);
var now = Date.now();
return $q.all([ return $q.all([
// Get currency // Get currency
csCurrency.get(), csCurrency.get(),
// Get requirements // Get requirements
BMA.wot.requirements({pubkey: data.pubkey||data.uid}) BMA.wot.requirements({pubkey: data.pubkey||data.uid}, false/*no cache*/)
.then(function(res) { .then(function(res) {
return _fillIdentitiesMeta(res && res.identities); return _fillIdentitiesMeta(res && res.identities);
}) })
...@@ -244,25 +259,29 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -244,25 +259,29 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
}); });
} }
/// Save to cache
requirementsCache.put(cacheKey, data);
angular.merge(inputData, data); // Update the input data
console.debug("[wot] Requirements for '{0}' loaded in {1}ms".format((data.pubkey && data.pubkey.substring(0,8))||data.uid, Date.now() - now)); console.debug("[wot] Requirements for '{0}' loaded in {1}ms".format((data.pubkey && data.pubkey.substring(0,8))||data.uid, Date.now() - now));
return data; return inputData;
}) })
.catch(function(err) { .catch(function(err) {
_resetRequirements(data); data.requirements = {loaded: true}; // Mark has loaded - need by the previous $timeout
_resetRequirements(inputData);
// If not a member: continue // If not a member: continue
if (!!err && if (!!err &&
(err.ucode == BMA.errorCodes.NO_MATCHING_MEMBER || (err.ucode == BMA.errorCodes.NO_MATCHING_MEMBER ||
err.ucode == BMA.errorCodes.NO_IDTY_MATCHING_PUB_OR_UID)) { err.ucode == BMA.errorCodes.NO_IDTY_MATCHING_PUB_OR_UID)) {
data.requirements.loaded = true; inputData.requirements.loaded = true;
return data; return inputData;
} }
throw err; throw err;
}); });
}, },
loadIdentityByLookup = function(pubkey, uid) { loadIdentityByLookup = function(pubkey, uid) {
var data = { var data = {
pubkey: pubkey, pubkey: pubkey,
...@@ -650,27 +669,29 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -650,27 +669,29 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
if (!pubkey && uid && !options.force) { if (!pubkey && uid && !options.force) {
return BMA.wot.member.getByUid(uid) return BMA.wot.member.getByUid(uid)
.then(function(member) { .then(function(member) {
if (member) return loadData(member.pubkey, member.uid, options); // recursive call if (member) return loadData(member.pubkey, member.uid, options); // recursive call, with a pubkey
//throw {message: 'NOT_A_MEMBER'}; //throw {message: 'NOT_A_MEMBER'};
return loadData(pubkey, uid, angular.copy(options, {force: true})); var options = angular.copy(options || {});
options.force = true;
return loadData(pubkey, uid, options); // Loop with force=true
}); });
} }
// Check cached data // Check cached data
if (pubkey) { if (pubkey) {
data = (!angular.isDefined(options.cache) || options.cache) ? identityCache.get(pubkey) : null; data = (options.cache !== false) ? identityCache.get(pubkey) : null;
if (data && (!uid || data.uid === uid) && (!options.blockUid || data.blockUid === options.blockUid)) { if (data && (!uid || data.uid === uid) && (!options.blockUid || data.blockUid === options.blockUid)) {
console.debug("[wot] Identity " + pubkey.substring(0, 8) + " found in cache"); console.debug("[wot] Identity {{0}} found in cache".format(pubkey.substring(0, 8)));
return $q.when(data); return $q.when(data);
} }
console.debug("[wot] Loading identity " + pubkey.substring(0, 8) + "..."); console.debug("[wot] Loading identity {{0}}...".format(pubkey.substring(0, 8)));
data = { data = {
pubkey: pubkey, pubkey: pubkey,
uid: uid uid: uid
}; };
} }
else { else {
console.debug("[wot] Loading identity from uid " + uid); console.debug("[wot] Loading identity from uid {{0}}...".format(uid));
data = { data = {
uid: uid uid: uid
}; };
...@@ -684,13 +705,15 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -684,13 +705,15 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
var medianTime; var medianTime;
return $q.all([ return $q.all([
// Get parameters // Get parameters
csCurrency.parameters() csCurrency.parameters()
.then(function(res) { .then(function(res) {
parameters = res; parameters = res;
}), }),
// Get current time // Get current time
csCurrency.blockchain.current() csCurrency.blockchain.current(true)
.then(function(current) { .then(function(current) {
medianTime = current.medianTime; medianTime = current.medianTime;
}) })
...@@ -705,7 +728,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -705,7 +728,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
}), }),
// Get requirements // Get requirements
loadRequirements(data), loadRequirements(data, options.cache !== false),
// Get identity using lookup // Get identity using lookup
loadIdentityByLookup(pubkey, uid) loadIdentityByLookup(pubkey, uid)
...@@ -833,6 +856,15 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -833,6 +856,15 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
return api.data.raisePromise.search(text, idties, 'pubkey') return api.data.raisePromise.search(text, idties, 'pubkey')
.then(function() { .then(function() {
// remove CS+ ids that match pubkey regex (considered attacks) - fix #959
idties = idties.filter(function(idty) {
if (BMA.regexp.PUBKEY.test(text) || BMA.regexp.PUBKEY_WITH_CHECKSUM.test(text)) {
text_pk = text.split(':')[0];
return idty.pubkey === text_pk;
}
return true;
});
// Make sure to add uid to new results - fix #488 // Make sure to add uid to new results - fix #488
if (idties.length > lookupResultCount) { if (idties.length > lookupResultCount) {
var idtiesWithoutUid = _.filter(idties, function(idty) { var idtiesWithoutUid = _.filter(idties, function(idty) {
...@@ -965,7 +997,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -965,7 +997,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
var idtiesByBlock = {}; var idtiesByBlock = {};
var idtiesByPubkey = {}; var idtiesByPubkey = {};
_.forEach(memberships, function(ms){ _.forEach(memberships, function(ms){
if (ms.membership == 'IN' && !uids[ms.pubkey]) { if (ms.membership === 'IN' && !uids[ms.pubkey]) {
var idty = { var idty = {
uid: ms.uid, uid: ms.uid,
pubkey: ms.pubkey, pubkey: ms.pubkey,
...@@ -987,7 +1019,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -987,7 +1019,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
// Remove previous idty from map // Remove previous idty from map
if (otherIdtySamePubkey) { if (otherIdtySamePubkey) {
idtiesByBlock[otherIdtySamePubkey.block] = idtiesByBlock[otherIdtySamePubkey.block].reduce(function(res, aidty){ idtiesByBlock[otherIdtySamePubkey.block] = idtiesByBlock[otherIdtySamePubkey.block].reduce(function(res, aidty){
if (aidty.pubkey == otherIdtySamePubkey.pubkey) return res; // if match idty to remove, to NOT add if (aidty.pubkey === otherIdtySamePubkey.pubkey) return res; // if match idty to remove, to NOT add
return (res||[]).concat(aidty); return (res||[]).concat(aidty);
}, null); }, null);
if (idtiesByBlock[otherIdtySamePubkey.block] === null) { if (idtiesByBlock[otherIdtySamePubkey.block] === null) {
...@@ -1158,6 +1190,11 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -1158,6 +1190,11 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
event.messageParams = event.messageParams || {}; event.messageParams = event.messageParams || {};
data.events = data.events || []; data.events = data.events || [];
data.events.push(event); data.events.push(event);
},
cleanCache = function() {
console.debug("[wot] Cleaning cache...");
csCache.clear(cachePrefix);
} }
; ;
...@@ -1165,8 +1202,10 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -1165,8 +1202,10 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
api.registerEvent('data', 'load'); api.registerEvent('data', 'load');
api.registerEvent('data', 'search'); api.registerEvent('data', 'search');
// Listen if node changed
BMA.api.node.on.stop($rootScope, cleanCache, this);
return { return {
id: id,
load: loadData, load: loadData,
loadRequirements: loadRequirements, loadRequirements: loadRequirements,
search: search, search: search,
...@@ -1178,10 +1217,4 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c ...@@ -1178,10 +1217,4 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
// api extension // api extension
api: api api: api
}; };
}
var service = factory('default', BMA);
service.instance = factory;
return service;
}); });
Source diff could not be displayed: it is too large. Options to address this: view the blob.
// source: https://github.com/ricmoo/aes-js - version 3.3.1
(function(root) {
"use strict";
function checkInt(value) {
return (parseInt(value) === value);
}
function checkInts(arrayish) {
if (!checkInt(arrayish.length)) { return false; }
for (var i = 0; i < arrayish.length; i++) {
if (!checkInt(arrayish[i]) || arrayish[i] < 0 || arrayish[i] > 255) {
return false;
}
}
return true;
}
function coerceArray(arg, copy) {
// ArrayBuffer view
if (arg.buffer && ArrayBuffer.isView(arg) && arg.name === 'Uint8Array') {
if (copy) {
if (arg.slice) {
arg = arg.slice();
} else {
arg = Array.prototype.slice.call(arg);
}
}
return arg;
}
// It's an array; check it is a valid representation of a byte
if (Array.isArray(arg)) {
if (!checkInts(arg)) {
throw new Error('Array contains invalid value: ' + arg);
}
return new Uint8Array(arg);
}
// Something else, but behaves like an array (maybe a Buffer? Arguments?)
if (checkInt(arg.length) && checkInts(arg)) {
return new Uint8Array(arg);
}
throw new Error('unsupported array-like object');
}
function createArray(length) {
return new Uint8Array(length);
}
function copyArray(sourceArray, targetArray, targetStart, sourceStart, sourceEnd) {
if (sourceStart != null || sourceEnd != null) {
if (sourceArray.slice) {
sourceArray = sourceArray.slice(sourceStart, sourceEnd);
} else {
sourceArray = Array.prototype.slice.call(sourceArray, sourceStart, sourceEnd);
}
}
targetArray.set(sourceArray, targetStart);
}
var convertUtf8 = (function() {
function toBytes(text) {
var result = [], i = 0;
text = encodeURI(text);
while (i < text.length) {
var c = text.charCodeAt(i++);
// if it is a % sign, encode the following 2 bytes as a hex value
if (c === 37) {
result.push(parseInt(text.substr(i, 2), 16))
i += 2;
// otherwise, just the actual byte
} else {
result.push(c)
}
}
return coerceArray(result);
}
function fromBytes(bytes) {
var result = [], i = 0;
while (i < bytes.length) {
var c = bytes[i];
if (c < 128) {
result.push(String.fromCharCode(c));
i++;
} else if (c > 191 && c < 224) {
result.push(String.fromCharCode(((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f)));
i += 2;
} else {
result.push(String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f)));
i += 3;
}
}
return result.join('');
}
return {
toBytes: toBytes,
fromBytes: fromBytes,
}
})();
var convertHex = (function() {
function toBytes(text) {
var result = [];
for (var i = 0; i < text.length; i += 2) {
result.push(parseInt(text.substr(i, 2), 16));
}
return result;
}
// http://ixti.net/development/javascript/2011/11/11/base64-encodedecode-of-utf8-in-browser-with-js.html
var Hex = '0123456789abcdef';
function fromBytes(bytes) {
var result = [];
for (var i = 0; i < bytes.length; i++) {
var v = bytes[i];
result.push(Hex[(v & 0xf0) >> 4] + Hex[v & 0x0f]);
}
return result.join('');
}
return {
toBytes: toBytes,
fromBytes: fromBytes,
}
})();
// Number of rounds by keysize
var numberOfRounds = {16: 10, 24: 12, 32: 14}
// Round constant words
var rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91];
// S-box and Inverse S-box (S is for Substitution)
var S = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16];
var Si =[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d];
// Transformations for encryption
var T1 = [0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a];
var T2 = [0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616];
var T3 = [0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16];
var T4 = [0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c];
// Transformations for decryption
var T5 = [0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742];
var T6 = [0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857];
var T7 = [0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8];
var T8 = [0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0];
// Transformations for decryption key expansion
var U1 = [0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3];
var U2 = [0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697];
var U3 = [0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46];
var U4 = [0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d];
function convertToInt32(bytes) {
var result = [];
for (var i = 0; i < bytes.length; i += 4) {
result.push(
(bytes[i ] << 24) |
(bytes[i + 1] << 16) |
(bytes[i + 2] << 8) |
bytes[i + 3]
);
}
return result;
}
var AES = function(key) {
if (!(this instanceof AES)) {
throw Error('AES must be instanitated with `new`');
}
Object.defineProperty(this, 'key', {
value: coerceArray(key, true)
});
this._prepare();
}
AES.prototype._prepare = function() {
var rounds = numberOfRounds[this.key.length];
if (rounds == null) {
throw new Error('invalid key size (must be 16, 24 or 32 bytes)');
}
// encryption round keys
this._Ke = [];
// decryption round keys
this._Kd = [];
for (var i = 0; i <= rounds; i++) {
this._Ke.push([0, 0, 0, 0]);
this._Kd.push([0, 0, 0, 0]);
}
var roundKeyCount = (rounds + 1) * 4;
var KC = this.key.length / 4;
// convert the key into ints
var tk = convertToInt32(this.key);
// copy values into round key arrays
var index;
for (var i = 0; i < KC; i++) {
index = i >> 2;
this._Ke[index][i % 4] = tk[i];
this._Kd[rounds - index][i % 4] = tk[i];
}
// key expansion (fips-197 section 5.2)
var rconpointer = 0;
var t = KC, tt;
while (t < roundKeyCount) {
tt = tk[KC - 1];
tk[0] ^= ((S[(tt >> 16) & 0xFF] << 24) ^
(S[(tt >> 8) & 0xFF] << 16) ^
(S[ tt & 0xFF] << 8) ^
S[(tt >> 24) & 0xFF] ^
(rcon[rconpointer] << 24));
rconpointer += 1;
// key expansion (for non-256 bit)
if (KC != 8) {
for (var i = 1; i < KC; i++) {
tk[i] ^= tk[i - 1];
}
// key expansion for 256-bit keys is "slightly different" (fips-197)
} else {
for (var i = 1; i < (KC / 2); i++) {
tk[i] ^= tk[i - 1];
}
tt = tk[(KC / 2) - 1];
tk[KC / 2] ^= (S[ tt & 0xFF] ^
(S[(tt >> 8) & 0xFF] << 8) ^
(S[(tt >> 16) & 0xFF] << 16) ^
(S[(tt >> 24) & 0xFF] << 24));
for (var i = (KC / 2) + 1; i < KC; i++) {
tk[i] ^= tk[i - 1];
}
}
// copy values into round key arrays
var i = 0, r, c;
while (i < KC && t < roundKeyCount) {
r = t >> 2;
c = t % 4;
this._Ke[r][c] = tk[i];
this._Kd[rounds - r][c] = tk[i++];
t++;
}
}
// inverse-cipher-ify the decryption round key (fips-197 section 5.3)
for (var r = 1; r < rounds; r++) {
for (var c = 0; c < 4; c++) {
tt = this._Kd[r][c];
this._Kd[r][c] = (U1[(tt >> 24) & 0xFF] ^
U2[(tt >> 16) & 0xFF] ^
U3[(tt >> 8) & 0xFF] ^
U4[ tt & 0xFF]);
}
}
}
AES.prototype.encrypt = function(plaintext) {
if (plaintext.length != 16) {
throw new Error('invalid plaintext size (must be 16 bytes)');
}
var rounds = this._Ke.length - 1;
var a = [0, 0, 0, 0];
// convert plaintext to (ints ^ key)
var t = convertToInt32(plaintext);
for (var i = 0; i < 4; i++) {
t[i] ^= this._Ke[0][i];
}
// apply round transforms
for (var r = 1; r < rounds; r++) {
for (var i = 0; i < 4; i++) {
a[i] = (T1[(t[ i ] >> 24) & 0xff] ^
T2[(t[(i + 1) % 4] >> 16) & 0xff] ^
T3[(t[(i + 2) % 4] >> 8) & 0xff] ^
T4[ t[(i + 3) % 4] & 0xff] ^
this._Ke[r][i]);
}
t = a.slice();
}
// the last round is special
var result = createArray(16), tt;
for (var i = 0; i < 4; i++) {
tt = this._Ke[rounds][i];
result[4 * i ] = (S[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff;
result[4 * i + 1] = (S[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff;
result[4 * i + 2] = (S[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff;
result[4 * i + 3] = (S[ t[(i + 3) % 4] & 0xff] ^ tt ) & 0xff;
}
return result;
}
AES.prototype.decrypt = function(ciphertext) {
if (ciphertext.length != 16) {
throw new Error('invalid ciphertext size (must be 16 bytes)');
}
var rounds = this._Kd.length - 1;
var a = [0, 0, 0, 0];
// convert plaintext to (ints ^ key)
var t = convertToInt32(ciphertext);
for (var i = 0; i < 4; i++) {
t[i] ^= this._Kd[0][i];
}
// apply round transforms
for (var r = 1; r < rounds; r++) {
for (var i = 0; i < 4; i++) {
a[i] = (T5[(t[ i ] >> 24) & 0xff] ^
T6[(t[(i + 3) % 4] >> 16) & 0xff] ^
T7[(t[(i + 2) % 4] >> 8) & 0xff] ^
T8[ t[(i + 1) % 4] & 0xff] ^
this._Kd[r][i]);
}
t = a.slice();
}
// the last round is special
var result = createArray(16), tt;
for (var i = 0; i < 4; i++) {
tt = this._Kd[rounds][i];
result[4 * i ] = (Si[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff;
result[4 * i + 1] = (Si[(t[(i + 3) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff;
result[4 * i + 2] = (Si[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff;
result[4 * i + 3] = (Si[ t[(i + 1) % 4] & 0xff] ^ tt ) & 0xff;
}
return result;
}
/**
* Mode Of Operation - Electonic Codebook (ECB)
*/
var ModeOfOperationECB = function(key) {
if (!(this instanceof ModeOfOperationECB)) {
throw Error('AES must be instanitated with `new`');
}
this.description = "Electronic Code Block";
this.name = "ecb";
this._aes = new AES(key);
}
ModeOfOperationECB.prototype.encrypt = function(plaintext) {
plaintext = coerceArray(plaintext);
if ((plaintext.length % 16) !== 0) {
throw new Error('invalid plaintext size (must be multiple of 16 bytes)');
}
var ciphertext = createArray(plaintext.length);
var block = createArray(16);
for (var i = 0; i < plaintext.length; i += 16) {
copyArray(plaintext, block, 0, i, i + 16);
block = this._aes.encrypt(block);
copyArray(block, ciphertext, i);
}
return ciphertext;
}
ModeOfOperationECB.prototype.decrypt = function(ciphertext) {
ciphertext = coerceArray(ciphertext);
if ((ciphertext.length % 16) !== 0) {
throw new Error('invalid ciphertext size (must be multiple of 16 bytes)');
}
var plaintext = createArray(ciphertext.length);
var block = createArray(16);
for (var i = 0; i < ciphertext.length; i += 16) {
copyArray(ciphertext, block, 0, i, i + 16);
block = this._aes.decrypt(block);
copyArray(block, plaintext, i);
}
return plaintext;
}
/**
* Mode Of Operation - Cipher Block Chaining (CBC)
*/
var ModeOfOperationCBC = function(key, iv) {
if (!(this instanceof ModeOfOperationCBC)) {
throw Error('AES must be instanitated with `new`');
}
this.description = "Cipher Block Chaining";
this.name = "cbc";
if (!iv) {
iv = createArray(16);
} else if (iv.length != 16) {
throw new Error('invalid initialation vector size (must be 16 bytes)');
}
this._lastCipherblock = coerceArray(iv, true);
this._aes = new AES(key);
}
ModeOfOperationCBC.prototype.encrypt = function(plaintext) {
plaintext = coerceArray(plaintext);
if ((plaintext.length % 16) !== 0) {
throw new Error('invalid plaintext size (must be multiple of 16 bytes)');
}
var ciphertext = createArray(plaintext.length);
var block = createArray(16);
for (var i = 0; i < plaintext.length; i += 16) {
copyArray(plaintext, block, 0, i, i + 16);
for (var j = 0; j < 16; j++) {
block[j] ^= this._lastCipherblock[j];
}
this._lastCipherblock = this._aes.encrypt(block);
copyArray(this._lastCipherblock, ciphertext, i);
}
return ciphertext;
}
ModeOfOperationCBC.prototype.decrypt = function(ciphertext) {
ciphertext = coerceArray(ciphertext);
if ((ciphertext.length % 16) !== 0) {
throw new Error('invalid ciphertext size (must be multiple of 16 bytes)');
}
var plaintext = createArray(ciphertext.length);
var block = createArray(16);
for (var i = 0; i < ciphertext.length; i += 16) {
copyArray(ciphertext, block, 0, i, i + 16);
block = this._aes.decrypt(block);
for (var j = 0; j < 16; j++) {
plaintext[i + j] = block[j] ^ this._lastCipherblock[j];
}
copyArray(ciphertext, this._lastCipherblock, 0, i, i + 16);
}
return plaintext;
}
/**
* Mode Of Operation - Cipher Feedback (CFB)
*/
var ModeOfOperationCFB = function(key, iv, segmentSize) {
if (!(this instanceof ModeOfOperationCFB)) {
throw Error('AES must be instanitated with `new`');
}
this.description = "Cipher Feedback";
this.name = "cfb";
if (!iv) {
iv = createArray(16);
} else if (iv.length != 16) {
throw new Error('invalid initialation vector size (must be 16 size)');
}
if (!segmentSize) { segmentSize = 1; }
this.segmentSize = segmentSize;
this._shiftRegister = coerceArray(iv, true);
this._aes = new AES(key);
}
ModeOfOperationCFB.prototype.encrypt = function(plaintext) {
if ((plaintext.length % this.segmentSize) != 0) {
throw new Error('invalid plaintext size (must be segmentSize bytes)');
}
var encrypted = coerceArray(plaintext, true);
var xorSegment;
for (var i = 0; i < encrypted.length; i += this.segmentSize) {
xorSegment = this._aes.encrypt(this._shiftRegister);
for (var j = 0; j < this.segmentSize; j++) {
encrypted[i + j] ^= xorSegment[j];
}
// Shift the register
copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize);
copyArray(encrypted, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize);
}
return encrypted;
}
ModeOfOperationCFB.prototype.decrypt = function(ciphertext) {
if ((ciphertext.length % this.segmentSize) != 0) {
throw new Error('invalid ciphertext size (must be segmentSize bytes)');
}
var plaintext = coerceArray(ciphertext, true);
var xorSegment;
for (var i = 0; i < plaintext.length; i += this.segmentSize) {
xorSegment = this._aes.encrypt(this._shiftRegister);
for (var j = 0; j < this.segmentSize; j++) {
plaintext[i + j] ^= xorSegment[j];
}
// Shift the register
copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize);
copyArray(ciphertext, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize);
}
return plaintext;
}
/**
* Mode Of Operation - Output Feedback (OFB)
*/
var ModeOfOperationOFB = function(key, iv) {
if (!(this instanceof ModeOfOperationOFB)) {
throw Error('AES must be instanitated with `new`');
}
this.description = "Output Feedback";
this.name = "ofb";
if (!iv) {
iv = createArray(16);
} else if (iv.length != 16) {
throw new Error('invalid initialation vector size (must be 16 bytes)');
}
this._lastPrecipher = coerceArray(iv, true);
this._lastPrecipherIndex = 16;
this._aes = new AES(key);
}
ModeOfOperationOFB.prototype.encrypt = function(plaintext) {
var encrypted = coerceArray(plaintext, true);
for (var i = 0; i < encrypted.length; i++) {
if (this._lastPrecipherIndex === 16) {
this._lastPrecipher = this._aes.encrypt(this._lastPrecipher);
this._lastPrecipherIndex = 0;
}
encrypted[i] ^= this._lastPrecipher[this._lastPrecipherIndex++];
}
return encrypted;
}
// Decryption is symetric
ModeOfOperationOFB.prototype.decrypt = ModeOfOperationOFB.prototype.encrypt;
/**
* Counter object for CTR common mode of operation
*/
var Counter = function(initialValue) {
if (!(this instanceof Counter)) {
throw Error('Counter must be instanitated with `new`');
}
// We allow 0, but anything false-ish uses the default 1
if (initialValue !== 0 && !initialValue) { initialValue = 1; }
if (typeof(initialValue) === 'number') {
this._counter = createArray(16);
this.setValue(initialValue);
} else {
this.setBytes(initialValue);
}
}
Counter.prototype.setValue = function(value) {
if (typeof(value) !== 'number' || parseInt(value) != value) {
throw new Error('invalid counter value (must be an integer)');
}
// We cannot safely handle numbers beyond the safe range for integers
if (value > Number.MAX_SAFE_INTEGER) {
throw new Error('integer value out of safe range');
}
for (var index = 15; index >= 0; --index) {
this._counter[index] = value % 256;
value = parseInt(value / 256);
}
}
Counter.prototype.setBytes = function(bytes) {
bytes = coerceArray(bytes, true);
if (bytes.length != 16) {
throw new Error('invalid counter bytes size (must be 16 bytes)');
}
this._counter = bytes;
};
Counter.prototype.increment = function() {
for (var i = 15; i >= 0; i--) {
if (this._counter[i] === 255) {
this._counter[i] = 0;
} else {
this._counter[i]++;
break;
}
}
}
/**
* Mode Of Operation - Counter (CTR)
*/
var ModeOfOperationCTR = function(key, counter) {
if (!(this instanceof ModeOfOperationCTR)) {
throw Error('AES must be instanitated with `new`');
}
this.description = "Counter";
this.name = "ctr";
if (!(counter instanceof Counter)) {
counter = new Counter(counter)
}
this._counter = counter;
this._remainingCounter = null;
this._remainingCounterIndex = 16;
this._aes = new AES(key);
}
ModeOfOperationCTR.prototype.encrypt = function(plaintext) {
var encrypted = coerceArray(plaintext, true);
for (var i = 0; i < encrypted.length; i++) {
if (this._remainingCounterIndex === 16) {
this._remainingCounter = this._aes.encrypt(this._counter._counter);
this._remainingCounterIndex = 0;
this._counter.increment();
}
encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++];
}
return encrypted;
}
// Decryption is symetric
ModeOfOperationCTR.prototype.decrypt = ModeOfOperationCTR.prototype.encrypt;
///////////////////////
// Padding
// See:https://tools.ietf.org/html/rfc2315
function pkcs7pad(data) {
data = coerceArray(data, true);
var padder = 16 - (data.length % 16);
var result = createArray(data.length + padder);
copyArray(data, result);
for (var i = data.length; i < result.length; i++) {
result[i] = padder;
}
return result;
}
function pkcs7strip(data) {
data = coerceArray(data, true);
if (data.length < 16) { throw new Error('PKCS#7 invalid length'); }
var padder = data[data.length - 1];
if (padder > 16) { throw new Error('PKCS#7 padding byte out of range'); }
var length = data.length - padder;
for (var i = 0; i < padder; i++) {
if (data[length + i] !== padder) {
throw new Error('PKCS#7 invalid padding byte');
}
}
var result = createArray(length);
copyArray(data, result, 0, 0, length);
return result;
}
///////////////////////
// Exporting
// The block cipher
var aesjs = {
AES: AES,
Counter: Counter,
ModeOfOperation: {
ecb: ModeOfOperationECB,
cbc: ModeOfOperationCBC,
cfb: ModeOfOperationCFB,
ofb: ModeOfOperationOFB,
ctr: ModeOfOperationCTR
},
utils: {
hex: convertHex,
utf8: convertUtf8
},
padding: {
pkcs7: {
pad: pkcs7pad,
strip: pkcs7strip
}
},
_arrayTest: {
coerceArray: coerceArray,
createArray: createArray,
copyArray: copyArray,
}
};
// node.js
if (typeof exports !== 'undefined') {
module.exports = aesjs
// RequireJS/AMD
// http://www.requirejs.org/docs/api.html
// https://github.com/amdjs/amdjs-api/wiki/AMD
} else if (typeof(define) === 'function' && define.amd) {
define(aesjs);
// Web Browsers
} else {
// If there was an existing library at "aesjs" make sure it's still available
if (root.aesjs) {
aesjs._aesjs = root.aesjs;
}
root.aesjs = aesjs;
}
})(this);
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
while (i < string.length) { while (i < string.length) {
c = string[i]; c = string[i];
if (!(c in Base58.alphabetMap)) { if (!(c in Base58.alphabetMap)) {
console.error("Base58.decode received unacceptable input. Character '" + c + "' is not in the Base58 alphabet.");
throw "Base58.decode received unacceptable input. Character '" + c + "' is not in the Base58 alphabet."; throw "Base58.decode received unacceptable input. Character '" + c + "' is not in the Base58 alphabet.";
} }
j = 0; j = 0;
......
!function(e,t){var i=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(i),i.styleSheet)i.styleSheet.disabled||(i.styleSheet.cssText=t);else try{i.innerHTML=t}catch(d){i.innerText=t}}(document,".digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number .digit-keyboard-key-letters.hidden,.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper.hidden{visibility:hidden}.digit-keyboard{position:absolute;left:0;right:0;bottom:0;background:0 0;font-size:24px;z-index:5000}.digit-keyboard *{box-sizing:border-box}.digit-keyboard .digit-keyboard-row{display:flex;flex-direction:row;border-bottom:1px solid #333}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper{flex:1;text-align:center;position:relative;height:60px}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key{display:block;margin:0 auto;position:absolute;left:0;top:0;right:0;bottom:0;border-right:1px solid #333;border-left:1px solid #333;line-height:60px;-webkit-transition:background-color .1s linear;-moz-transition:background-color .1s linear;-o-transition:background-color .1s linear;-ms-transition:background-color .1s linear;transition:background-color .1s linear}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number{font-size:1em;vertical-align:middle;display:inline-block;line-height:normal;text-align:center;margin-top:-.4em}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number .digit-keyboard-key-letters{font-size:9.6px;line-height:9.6px;height:9.6px;margin-top:-.25em}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-action{position:absolute;left:0;top:0;right:0;bottom:0;line-height:60px;font-size:24px}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper:first-child .digit-keyboard-key{border-right:none}.digit-keyboard .digit-keyboard-row .digit-keyboard-key-wrapper:last-child .digit-keyboard-key{border-left:none}.digit-keyboard .digit-keyboard-row:first-child{border-top:1px solid #333}.digit-keyboard .digit-keyboard-row:last-child{border:none}.digit-keyboard .digit-keyboard-row:last-child .digit-keyboard-key-wrapper .digit-keyboard-key{border-bottom:1px solid #333}.digit-keyboard.align-center{margin:0 auto}.digit-keyboard.align-left{margin-right:auto}.digit-keyboard.align-right{margin-left:auto}.digit-keyboard.no-letters .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number{margin-top:-.3em}.digit-keyboard.no-letters .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number .digit-keyboard-key-letters{display:none}.digit-keyboard.round-buttons .digit-keyboard-row{border:none}.digit-keyboard.round-buttons .digit-keyboard-row .digit-keyboard-key-wrapper{margin:10px 0 0}.digit-keyboard.round-buttons .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key{width:60px;-webkit-border-radius:100%;-moz-border-radius:100%;border-radius:100%;border:1px solid #333}.digit-keyboard.round-buttons .digit-keyboard-row:last-child .digit-keyboard-key-wrapper{margin-bottom:10px}.digit-keyboard.round-buttons.no-letters .digit-keyboard-row .digit-keyboard-key-wrapper .digit-keyboard-key .digit-keyboard-key-number{height:57.6px;line-height:57.6px}.digit-keyboard.keyboard-light *{border-color:#e6e6e6!important}.digit-keyboard.keyboard-light .digit-keyboard-key{background:#fff;color:#444}.digit-keyboard.keyboard-light .digit-keyboard-key.activated{background:#e6e6e6}.digit-keyboard.keyboard-stable *{border-color:#dfdfdf!important}.digit-keyboard.keyboard-stable .digit-keyboard-key{background:#f8f8f8;color:#444}.digit-keyboard.keyboard-stable .digit-keyboard-key.activated{background:#dfdfdf}.digit-keyboard.keyboard-positive *{border-color:#0c60ee!important}.digit-keyboard.keyboard-positive .digit-keyboard-key{background:#387ef5;color:#fff}.digit-keyboard.keyboard-positive .digit-keyboard-key.activated{background:#0c60ee}.digit-keyboard.keyboard-calm *{border-color:#0a9dc7!important}.digit-keyboard.keyboard-calm .digit-keyboard-key{background:#11c1f3;color:#fff}.digit-keyboard.keyboard-calm .digit-keyboard-key.activated{background:#0a9dc7}.digit-keyboard.keyboard-balanced *{border-color:#28a54c!important}.digit-keyboard.keyboard-balanced .digit-keyboard-key{background:#33cd5f;color:#fff}.digit-keyboard.keyboard-balanced .digit-keyboard-key.activated{background:#28a54c}.digit-keyboard.keyboard-energized *{border-color:#e6b500!important}.digit-keyboard.keyboard-energized .digit-keyboard-key{background:#ffc900;color:#fff}.digit-keyboard.keyboard-energized .digit-keyboard-key.activated{background:#e6b500}.digit-keyboard.keyboard-assertive *{border-color:#e42112!important}.digit-keyboard.keyboard-assertive .digit-keyboard-key{background:#ef473a;color:#fff}.digit-keyboard.keyboard-assertive .digit-keyboard-key.activated{background:#e42112}.digit-keyboard.keyboard-royal *{border-color:#6b46e5!important}.digit-keyboard.keyboard-royal .digit-keyboard-key{background:#886aea;color:#fff}.digit-keyboard.keyboard-royal .digit-keyboard-key.activated{background:#6b46e5}.digit-keyboard.keyboard-dark *{border-color:#2b2b2b!important}.digit-keyboard.keyboard-dark .digit-keyboard-key{background:#444;color:#fff}.digit-keyboard.keyboard-dark .digit-keyboard-key.activated{background:#2b2b2b}.digit-keyboard.keyboard-opaque-black *{border-color:rgba(0,0,0,.75)!important}.digit-keyboard.keyboard-opaque-black .digit-keyboard-key{background:rgba(0,0,0,.15);color:rgba(0,0,0,.75)}.digit-keyboard.keyboard-opaque-black .digit-keyboard-key.activated{background:rgba(0,0,0,.25)}.digit-keyboard.keyboard-opaque-white *{border-color:rgba(255,255,255,.75)!important}.digit-keyboard.keyboard-opaque-white .digit-keyboard-key{background:rgba(255,255,255,.15);color:rgba(255,255,255,.75)}.digit-keyboard.keyboard-opaque-white .digit-keyboard-key.activated{background:rgba(255,255,255,.25)}.digit-keyboard.animation-slide-up{-webkit-transition:transform linear 150ms;transition:transform linear 150ms;transform:translate3d(0,0,0)}.digit-keyboard.animation-slide-up.ng-hide{transform:translate3d(0,100%,0)}.digit-keyboard.animation-pop{-webkit-transition:transform linear 150ms;transition:transform linear 150ms;transform:scale3d(1,1,1)}.digit-keyboard.animation-pop.ng-hide{transform:scale3d(0,0,1)}"),!function(e){try{e=angular.module("ion-digit-keyboard.templates")}catch(t){e=angular.module("ion-digit-keyboard.templates",[])}e.run(["$templateCache",function(e){e.put("keyboard.tpl.html","<div class=\"digit-keyboard align-{{align}} keyboard-{{theme}} animation-{{animation}} {{roundButtons ? 'round-buttons' : ''}} {{showLetters == false ? 'no-letters' : ''}} {{(ngShow == false || ngHide == true) ? 'ng-hide' : ''}}\" style=\"width: {{width}};\"><div class=digit-keyboard-row><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(1)><div class=digit-keyboard-key-number>1<div class=digit-keyboard-key-letters></div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(2)><div class=digit-keyboard-key-number>2<div class=digit-keyboard-key-letters>ABC</div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(3)><div class=digit-keyboard-key-number>3<div class=digit-keyboard-key-letters>DEF</div></div></div></div></div><div class=digit-keyboard-row><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(4)><div class=digit-keyboard-key-number>4<div class=digit-keyboard-key-letters>GHI</div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(5)><div class=digit-keyboard-key-number>5<div class=digit-keyboard-key-letters>JKL</div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(6)><div class=digit-keyboard-key-number>6<div class=digit-keyboard-key-letters>MNO</div></div></div></div></div><div class=digit-keyboard-row><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(7)><div class=digit-keyboard-key-number>7<div class=digit-keyboard-key-letters>PQRS</div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(8)><div class=digit-keyboard-key-number>8<div class=digit-keyboard-key-letters>TUV</div></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(9)><div class=digit-keyboard-key-number>9<div class=digit-keyboard-key-letters>WXYZ</div></div></div></div></div><div class=digit-keyboard-row><div class=\"digit-keyboard-key-wrapper {{showLeftAction == false ? 'hidden' : ''}}\"><div class=digit-keyboard-key ng-click=leftAction($event) style={{leftStyle}}><div class=digit-keyboard-key-action ng-bind-html=leftHtml style={{leftFontSize}}></div></div></div><div class=digit-keyboard-key-wrapper><div class=digit-keyboard-key ng-click=numberAction(0)><div class=digit-keyboard-key-number style=\"margin-top: -0.30em;\">0</div></div></div><div class=\"digit-keyboard-key-wrapper {{showRightAction == false ? 'hidden' : ''}}\"><div class=digit-keyboard-key ng-click=rightAction($event) style={{rightStyle}}><div class=digit-keyboard-key-action ng-bind-html=rightHtml style={{rightFontSize}}></div></div></div></div></div>")}])}(),angular.module("ion-digit-keyboard.directive",[]).directive("ionDigitKeyboard",["$timeout","$ionicScrollDelegate","$templateCache",function(e,t,i){var d=150;return{restrict:"EA",template:i.get("keyboard.tpl.html"),replace:!0,scope:{settings:"=settings",ngShow:"=",ngHide:"="},link:function(i,o,r){function a(){1==b&&(k.style.bottom="0px",k.style.height=s)}function n(){e(function(){if(1==b){var e=o[0].offsetHeight;s=k.style.height,k.style.height="auto",k.style.bottom=e+1+"px"}},d+10)}function y(){i.ngShow===!0?n():i.ngShow===!1&&a(),t.resize()}function g(){i.ngHide===!0?a():i.ngHide===!1&&n(),t.resize()}"undefined"==typeof i.settings&&(i.settings={});var k,s,b=!0,l="ion-content",c={},u={};if(i.leftStyle="",i.rightStyle="",i.leftFontSize="",i.rightFontSize="",i.showLetters="undefined"==typeof i.settings.showLetters?!1:i.settings.showLetters,i.roundButtons="undefined"==typeof i.settings.roundButtons?!1:i.settings.roundButtons,i.numberAction=i.settings.action||function(){},i.width=i.settings.width||"100%",i.align=i.settings.align||"center",i.animation=i.settings.animation||"slide-up",i.theme=i.settings.theme||"stable","undefined"!=typeof i.settings.leftButton&&"object"==typeof i.settings.leftButton.style&&("undefined"!=typeof i.settings.leftButton.style.color&&(i.leftStyle+="color: "+i.settings.leftButton.style.color+";"),"undefined"!=typeof i.settings.leftButton.style.bgColor&&(c["default"]=i.settings.leftButton.style.bgColor,i.leftStyle+="background-color: "+c["default"]+";"),"undefined"!=typeof i.settings.leftButton.style.activeBgColor&&(c.active=i.settings.leftButton.style.activeBgColor),"undefined"!=typeof i.settings.leftButton.style.borderColor&&(i.leftStyle+="border-color: "+i.settings.leftButton.style.borderColor+" !important;"),"undefined"!=typeof i.settings.leftButton.style.fontSize&&(i.leftFontSize="font-size: "+i.settings.leftButton.style.fontSize+" !important;")),"undefined"!=typeof i.settings.rightButton&&"object"==typeof i.settings.rightButton.style&&("undefined"!=typeof i.settings.rightButton.style.color&&(i.rightStyle+="color: "+i.settings.rightButton.style.color+";"),"undefined"!=typeof i.settings.rightButton.style.bgColor&&(u["default"]=i.settings.rightButton.style.bgColor,i.rightStyle+="background-color: "+u["default"]+";"),"undefined"!=typeof i.settings.rightButton.style.activeBgColor&&(u.active=i.settings.rightButton.style.activeBgColor),"undefined"!=typeof i.settings.rightButton.style.borderColor&&(i.rightStyle+="border-color: "+i.settings.rightButton.style.borderColor+" !important;"),"undefined"!=typeof i.settings.rightButton.style.fontSize&&(i.rightFontSize="font-size: "+i.settings.rightButton.style.fontSize+" !important;")),i.showLeftAction=!1,"undefined"!=typeof i.settings.leftButton&&(i.leftHtml=i.settings.leftButton.html,i.showLeftAction=!0,i.leftAction=function(e){if(c.active){var t=e.target;"I"==t.tagName?t=t.parentNode.parentNode:t.className.indexOf("digit-keyboard-key-action")>-1&&(t=t.parentNode),"undefined"==typeof c["default"]&&(c["default"]=t.style.backgroundColor),t.style.backgroundColor=c.active,setTimeout(function(){t.style.backgroundColor=c["default"]},100)}i.settings.leftButton.action()}),i.showRightAction=!1,"undefined"!=typeof i.settings.rightButton&&(i.rightHtml=i.settings.rightButton.html,i.showRightAction=!0,i.rightAction=function(e){if(u.active){var t=e.target;"I"==t.tagName?t=t.parentNode.parentNode:t.className.indexOf("digit-keyboard-key-action")>-1&&(t=t.parentNode),"undefined"==typeof u["default"]&&(u["default"]=t.style.backgroundColor),t.style.backgroundColor=u.active,setTimeout(function(){t.style.backgroundColor=u["default"]},100)}i.settings.rightButton.action()}),"object"==typeof i.settings.resizeContent||"boolean"==typeof i.settings.resizeContent)if("object"==typeof i.settings.resizeContent)var b="undefined"==typeof i.settings.resizeContent.enable?!0:i.settings.resizeContent.enable,l="undefined"==typeof i.settings.resizeContent.element?"ion-content":i.settings.resizeContent.element;else"boolean"==typeof i.settings.resizeContent&&(b=i.settings.resizeContent);k=o[0].parentElement.querySelectorAll(l)[0],i.$watch("ngShow",y),i.$watch("ngHide",g)}}}]),angular.module("ion-digit-keyboard",["ionic","ion-digit-keyboard.templates","ion-digit-keyboard.directive"]);
\ No newline at end of file
Source diff could not be displayed: it is too large. Options to address this: view the blob.
/*
Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
(c) 2012-2013, Lennard Voogdt
http://leafletjs.com
https://github.com/lvoogdt
*//*global L*/(function(e,t,n){"use strict";L.AwesomeMarkers={};L.AwesomeMarkers.version="2.0.1";L.AwesomeMarkers.Icon=L.Icon.extend({options:{iconSize:[35,45],iconAnchor:[17,42],popupAnchor:[1,-32],shadowAnchor:[10,12],shadowSize:[36,16],className:"awesome-marker",prefix:"glyphicon",spinClass:"fa-spin",icon:"home",markerColor:"blue",iconColor:"white"},initialize:function(e){e=L.Util.setOptions(this,e)},createIcon:function(){var e=t.createElement("div"),n=this.options;n.icon&&(e.innerHTML=this._createInner());n.bgPos&&(e.style.backgroundPosition=-n.bgPos.x+"px "+ -n.bgPos.y+"px");this._setIconStyles(e,"icon-"+n.markerColor);return e},_createInner:function(){var e,t="",n="",r="",i=this.options;i.icon.slice(0,i.prefix.length+1)===i.prefix+"-"?e=i.icon:e=i.prefix+"-"+i.icon;i.spin&&typeof i.spinClass=="string"&&(t=i.spinClass);i.iconColor&&(i.iconColor==="white"||i.iconColor==="black"?n="icon-"+i.iconColor:r="style='color: "+i.iconColor+"' ");return"<i "+r+"class='"+i.prefix+" "+e+" "+t+" "+n+"'></i>"},_setIconStyles:function(e,t){var n=this.options,r=L.point(n[t==="shadow"?"shadowSize":"iconSize"]),i;t==="shadow"?i=L.point(n.shadowAnchor||n.iconAnchor):i=L.point(n.iconAnchor);!i&&r&&(i=r.divideBy(2,!0));e.className="awesome-marker-"+t+" "+n.className;if(i){e.style.marginLeft=-i.x+"px";e.style.marginTop=-i.y+"px"}if(r){e.style.width=r.x+"px";e.style.height=r.y+"px"}},createShadow:function(){var e=t.createElement("div");this._setIconStyles(e,"shadow");return e}});L.AwesomeMarkers.icon=function(e){return new L.AwesomeMarkers.Icon(e)}})(this,document);