DB structure, emscripten interface
This commit is contained in:
parent
5243a70726
commit
1089b40b1c
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
|||||||
name = taler-wallet
|
name = taler-wallet
|
||||||
version=0.1
|
version = $(shell grep '"version"' extension/manifest.json | sed 's/.*"\([0-9.]\+\)".*/\1/')
|
||||||
xpi = ${name}-${version}.xpi
|
xpi = ${name}-${version}.xpi
|
||||||
|
|
||||||
xpi:
|
xpi:
|
||||||
|
220
extension/background/db.js
Normal file
220
extension/background/db.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var DB = function () {
|
||||||
|
let DB = {}; // returned object with exported functions
|
||||||
|
|
||||||
|
let DB_NAME = "taler";
|
||||||
|
let DB_VERSION = 1;
|
||||||
|
|
||||||
|
let db = null;
|
||||||
|
let is_ready = null;
|
||||||
|
|
||||||
|
DB.open = function (onsuccess, onerror)
|
||||||
|
{
|
||||||
|
is_ready = false;
|
||||||
|
|
||||||
|
let req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
req.onerror = onerror;
|
||||||
|
req.onsuccess = function (event)
|
||||||
|
{
|
||||||
|
db = event.target.result;
|
||||||
|
is_ready = true;
|
||||||
|
if (onsuccess)
|
||||||
|
onsuccess();
|
||||||
|
};
|
||||||
|
|
||||||
|
req.onupgradeneeded = function (event)
|
||||||
|
{
|
||||||
|
console.log ("DB: upgrade needed: oldVersion = "+ event.oldVersion);
|
||||||
|
|
||||||
|
db = event.target.result;
|
||||||
|
db.onerror = onerror;
|
||||||
|
|
||||||
|
switch (event.oldVersion)
|
||||||
|
{
|
||||||
|
case 0: // DB does not exist yet
|
||||||
|
{
|
||||||
|
let example = {};
|
||||||
|
|
||||||
|
let mints = db.createObjectStore("mints", { keyPath: "mint_pub" });
|
||||||
|
mints.createIndex("name", "name", { unique: true });
|
||||||
|
|
||||||
|
example.mint = {
|
||||||
|
mint_pub: "<mint's master pub key>", // length: 32
|
||||||
|
name: "Mint One",
|
||||||
|
url: "https://mint.one/",
|
||||||
|
};
|
||||||
|
|
||||||
|
let denoms = db.createObjectStore("denoms", { keyPath: "denom_pub" });
|
||||||
|
|
||||||
|
example.denom = {
|
||||||
|
denom_pub: "<denom pub key>", // length: 32
|
||||||
|
mint_pub: "<mint's master pub key>", // length: 32
|
||||||
|
mint_sig: "<mint's sig>", // length: 64
|
||||||
|
withdraw_expiry_time: 1234567890,
|
||||||
|
deposit_expiry_time: 1234567890,
|
||||||
|
start_time: 1234567890,
|
||||||
|
value: {
|
||||||
|
value: 1,
|
||||||
|
fraction: 230000, // 0..999999
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
withdraw: {
|
||||||
|
value: 0,
|
||||||
|
fraction: 100000,
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
deposit: {
|
||||||
|
value: 0,
|
||||||
|
fraction: 100000,
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
refresh: {
|
||||||
|
value: 0,
|
||||||
|
fraction: 100000,
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let reserves = db.createObjectStore("reserves", { keyPath: "reserve_pub"});
|
||||||
|
example.reserve = {
|
||||||
|
reserve_pub: "<pub key>",
|
||||||
|
reserve_priv: "<priv key>",
|
||||||
|
mint_pub: "<mint's master pub key>",
|
||||||
|
initial: {
|
||||||
|
value: 1,
|
||||||
|
fraction: 230000,
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
value: 1,
|
||||||
|
fraction: 230000,
|
||||||
|
currency: "EUR",
|
||||||
|
blind_session_pub: "<pub key>",
|
||||||
|
status_sig: "<sig>",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let withdrawals = db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true });
|
||||||
|
example.withdrawal = {
|
||||||
|
id: 1, // generated
|
||||||
|
reserve_pub: "<pub key>",
|
||||||
|
reserve_sig: "<sig>",
|
||||||
|
denom_pub: "<pub key",
|
||||||
|
blind_session_pub: "<pub key>",
|
||||||
|
blind_priv: "<priv key>",
|
||||||
|
coin_pub: "<pub key>",
|
||||||
|
coin_priv: "<priv key>",
|
||||||
|
coin_ev: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
let coins = db.createObjectStore("coins", { keyPath: "coin_pub" });
|
||||||
|
example.coin = {
|
||||||
|
// coin either has a withdraw_id or refresh_id
|
||||||
|
// or it is imported in which case both are null
|
||||||
|
withdraw_id: 1, // can be null
|
||||||
|
refresh_id: null, // can be null
|
||||||
|
is_refreshed: false,
|
||||||
|
denom_pub: "<pub key>",
|
||||||
|
coin_pub: "<pub key>",
|
||||||
|
coin_priv: "<priv key>",
|
||||||
|
denom_sig: "<sig>",
|
||||||
|
spent: {
|
||||||
|
value: 1,
|
||||||
|
fraction: 230000,
|
||||||
|
},
|
||||||
|
transactions: [ 123, 456 ], // list of transaction IDs where this coin was used
|
||||||
|
};
|
||||||
|
|
||||||
|
let transactions = db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true });
|
||||||
|
example.transaction = {
|
||||||
|
id: 1, // generated
|
||||||
|
wire_hash: "<hash>",
|
||||||
|
value: {
|
||||||
|
value: 1,
|
||||||
|
fraction: 230000,
|
||||||
|
currency: "EUR",
|
||||||
|
},
|
||||||
|
contract: "<JSON>",
|
||||||
|
is_checkout_done: true,
|
||||||
|
is_confirmed: true,
|
||||||
|
fulfillment_url: "https://some.shop/transaction/completed",
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh = db.createObjectStore("refresh");
|
||||||
|
example.refresh = {
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ready = true;
|
||||||
|
if (onsuccess)
|
||||||
|
onsuccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DB.close = function ()
|
||||||
|
{
|
||||||
|
db.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DB.wallet_get = function (onresult, onerror)
|
||||||
|
{
|
||||||
|
let wallet = { };
|
||||||
|
|
||||||
|
let tr = db.transaction([ "coins", "denoms" ], "readonly");
|
||||||
|
let coins = tr.objectStore("coins");
|
||||||
|
let denoms = tr.objectStore("denoms");
|
||||||
|
|
||||||
|
let coins_cur = coins.openCursor();
|
||||||
|
coins_cur.onerror = onerror;
|
||||||
|
coins_cur.onsuccess = function ()
|
||||||
|
{
|
||||||
|
let cur = event.target.result;
|
||||||
|
if (cur) {
|
||||||
|
let denom_get = denoms.get(cur.valcue.denom_pub);
|
||||||
|
denom_get.onerror = onerror;
|
||||||
|
denom_get.onsuccess = function (event)
|
||||||
|
{
|
||||||
|
let denom = event.target.result;
|
||||||
|
if (denom.currency in wallet)
|
||||||
|
{
|
||||||
|
let w = wallet[denom.currency];
|
||||||
|
w.value += denom.value;
|
||||||
|
w.fraction = (w.fraction + denom.fraction) % 1000000;
|
||||||
|
if (1000000 <= w.fraction + denom.fraction)
|
||||||
|
w.value++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wallet[denom.currency] = denom;
|
||||||
|
}
|
||||||
|
cur.continue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // no more entries
|
||||||
|
{
|
||||||
|
onresult(wallet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DB.transaction_list = function (onresult, onerror)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DB.reserve_list = function (onresult, onerror)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
return DB;
|
||||||
|
}();
|
587
extension/background/emscriptif.js
Normal file
587
extension/background/emscriptif.js
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
This file is part of TALER
|
||||||
|
Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors)
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = [
|
||||||
|
'TWRhelloWorld',
|
||||||
|
'TWRgetValue',
|
||||||
|
'TWRgetFraction',
|
||||||
|
'TWRgetCurrency',
|
||||||
|
'TamountCmp',
|
||||||
|
'TWRverifyConfirmation',
|
||||||
|
'TWRverifySignKey',
|
||||||
|
'TWRverifyDenom',
|
||||||
|
'TWRverifyDenoms',
|
||||||
|
'TWRALLrsaPublicKeyHash',
|
||||||
|
'TWRALLgetEncodingFromRsaSignature',
|
||||||
|
'DWRtestStringCmp',
|
||||||
|
'TWRmultiplyAmount',
|
||||||
|
'TWRmultiplyAmounts',
|
||||||
|
'DWRdumpAmount',
|
||||||
|
'TWRALLgetAmount',
|
||||||
|
'DWRtestString',
|
||||||
|
'DWRgetPurpose',
|
||||||
|
'TWReddsaVerify',
|
||||||
|
'TamountAdd',
|
||||||
|
'TamountSubtract',
|
||||||
|
'TWRALLmakeEddsaSignature',
|
||||||
|
'TWRALLamountAdd',
|
||||||
|
'TWRALLeddsaPublicKeyFromPrivate',
|
||||||
|
'TWRALLeddsaPublicKeyFromPrivString',
|
||||||
|
'TWRALLeddsaPrivateKeyFromString',
|
||||||
|
'TWRALLeccEcdh',
|
||||||
|
'TWRALLhash',
|
||||||
|
'TWRALLecdhePublicKeyFromPrivateKey',
|
||||||
|
'TWRALLrsaPublicKeyDecodeFromString',
|
||||||
|
'GCeddsaSign',
|
||||||
|
'TWRALLmakeWithdrawBundle',
|
||||||
|
'GCALLrsaSignatureDecode',
|
||||||
|
'GCrsaSignatureEncode',
|
||||||
|
'TWRALLsignDepositPermission',
|
||||||
|
'GCALLrsaPublicKeyDecode',
|
||||||
|
'GCALLrsaPublicKeyEncode',
|
||||||
|
'WRALLeddsaPublicKey',
|
||||||
|
'GCALLeddsaKeyCreate',
|
||||||
|
'WRALLecdhePublicKey ',
|
||||||
|
'GSALLdataToStringAlloc',
|
||||||
|
'TWRgnunetFree',
|
||||||
|
'GSstringToData',
|
||||||
|
'TWRALLgetCurrentTime',
|
||||||
|
'TWRgetFancyTime',
|
||||||
|
'GChash',
|
||||||
|
'getHashedArray',
|
||||||
|
'TWRALLsignTest',
|
||||||
|
'GCALLecdheKeyCreate',
|
||||||
|
'GCecdheKeyGetPublic',
|
||||||
|
'WRALLecdhePublicKey',
|
||||||
|
'WRverifyTest',
|
||||||
|
'GCeccEcdh',
|
||||||
|
'TWRALLgenSymmetricKey',
|
||||||
|
'TWRALLgenInitVector',
|
||||||
|
'GCsymmetricDecrypt',
|
||||||
|
'GCsymmetricEncrypt',
|
||||||
|
'TWRALLgenKeyFromBlob',
|
||||||
|
'GCALLrsaPrivateKeyGetPublic',
|
||||||
|
'GCALLrsaPrivateKeyCreate',
|
||||||
|
'GCALLrsaBlindingKeyCreate',
|
||||||
|
'GCrsaBlindingKeyFree',
|
||||||
|
'GCrsaPublicKeyFree',
|
||||||
|
'GCrsaPrivateKeyFree',
|
||||||
|
'GCALLrsaBlind',
|
||||||
|
'GCALLrsaUnblind',
|
||||||
|
'GCALLrsaSign',
|
||||||
|
'GCrsaVerify',
|
||||||
|
'GCrsaSignatureFree',
|
||||||
|
'GCeddsaKeyGetPublic',
|
||||||
|
'WRALLmakePurpose',
|
||||||
|
'GChkdf',
|
||||||
|
'emscMalloc',
|
||||||
|
'emscFree'
|
||||||
|
];
|
||||||
|
|
||||||
|
/* The following definition is needed to make emscripted library to remain
|
||||||
|
'alive' after its loading. Otherwise, the normal behaviour would be:
|
||||||
|
loading -> look for a 'main()' -> if one is found execute it then exit,
|
||||||
|
otherwise just exit. See https://kripken.github.io/emscripten-site/docs/getting_started/FAQ.html
|
||||||
|
DO NOTE: this definition MUST precede the importing/loading of the emscripted
|
||||||
|
library */
|
||||||
|
|
||||||
|
/* FIXME
|
||||||
|
getLastWindow().Module = {
|
||||||
|
|
||||||
|
onRuntimeInitialized: function() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* According to emscripten's design, we need our emscripted library to be executed
|
||||||
|
with a 'window' object as its global scope.
|
||||||
|
Note: that holds on emscripten's functions too, that is they need to be *explicitly*
|
||||||
|
run with some 'window' object as their global scope. In practice, given a function
|
||||||
|
'foo' pointing to some emscripted function, that is accomplished by the mean of 'call()'
|
||||||
|
or 'apply()' methods; so, being 'someWin' a 'window' object, the statements
|
||||||
|
|
||||||
|
foo.call('someWin', arg1, .., argN) or foo.apply('someWin', ['arg1', .., 'argN']) will
|
||||||
|
execute foo(arg1, .., argN) with 'someWin' as its global scope.
|
||||||
|
See http://www.bennadel.com/blog/2265-changing-the-execution-context-of-javascript
|
||||||
|
-functions-using-call-and-apply.htm. */
|
||||||
|
|
||||||
|
/* The naming convention is such that:
|
||||||
|
- 'GCfunctionName' takes its code from GNUNET_CRYPTO_function_name
|
||||||
|
- 'GCALLfunctionName' takes its code from GNUNET_CRYPTO_function_name and returns
|
||||||
|
a pointer that must be deallocated using 'WRgnunetFree' (that takes its code from
|
||||||
|
'GNUNET_free' in the wrapper)
|
||||||
|
- 'GSfunctionName' and 'GSALLfunctionName' comply to the same convention respect to
|
||||||
|
GNUNET_STRINGS_* realm.
|
||||||
|
- 'TWRfunctionName' takes its code from 'TALER_function_name' in the wrapper.
|
||||||
|
- 'TWRALLfunctionName' takes its code from 'TALER_ALL_function_name' in the wrapper
|
||||||
|
and returns a pointer that must be deallocated using 'TWRgnunetFree' (or a function
|
||||||
|
provided by some emscripted routine) (the 'wrapper' is an additional layer written in
|
||||||
|
C that does some struct(s) manipulations where that is uncovenient to do from JavaScript.
|
||||||
|
Currently located at '../../emscripten/testcases/wrap.c')
|
||||||
|
- The same applies to 'TfunctionName' and 'TALLfunctionName', to indicate that the
|
||||||
|
respective functions come from (the emscripted version of) TALER_* realm. */
|
||||||
|
|
||||||
|
|
||||||
|
// shortcut to emscr's 'malloc'
|
||||||
|
function emscMalloc(size) {
|
||||||
|
|
||||||
|
var ptr = Module._malloc(size);
|
||||||
|
return ptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shortcut to emscr's 'free'. This function is problematic:
|
||||||
|
it randomly stops working giving 'emscFree is not a function'
|
||||||
|
error */
|
||||||
|
function emscFree(ptr) {
|
||||||
|
|
||||||
|
Module._free(ptr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var getEmsc = Module.cwrap;
|
||||||
|
|
||||||
|
var TWRhelloWorld = getEmsc('TALER_WR_hello_world', 'void', []);
|
||||||
|
|
||||||
|
var TWRverifyConfirmation = getEmsc('TALER_WR_verify_confirmation',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
var TWRgetValue = getEmsc('TALER_WR_get_value',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRgetFraction = getEmsc('TALER_WR_get_fraction',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRgetCurrency = getEmsc('TALER_WR_get_currency',
|
||||||
|
'string',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
|
||||||
|
var TWRmultiplyAmounts = getEmsc('TALER_WR_multiply_amounts',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number'] );
|
||||||
|
|
||||||
|
var TWRmultiplyAmount = getEmsc('TALER_WR_multiply_amount',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number'] );
|
||||||
|
|
||||||
|
var TWRALLrsaPublicKeyHash = getEmsc('TALER_WRALL_rsa_public_key_hash',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRverifyDenom = getEmsc('TALER_WR_verify_denom',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRverifyDenoms = getEmsc('TALER_WR_verify_denoms',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRverifySignKey = getEmsc('TALER_WR_verify_sign_key',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
|
||||||
|
var TWRALLgetEncodingFromRsaSignature = getEmsc('TALER_WRALL_get_encoding_from_rsa_signature',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TamountCmp = getEmsc('TALER_amount_cmp',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var DWRdumpAmount = getEmsc('DEBUG_WR_dump_amount',
|
||||||
|
'void'
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var DWRtestStringCmp = getEmsc('DEBUG_WR_test_string_cmp',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'string']);
|
||||||
|
|
||||||
|
var TWRALLgetAmount = getEmsc('TALER_WRALL_get_amount',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'string']);
|
||||||
|
|
||||||
|
var DWRgetPurpose = getEmsc('DEBUG_WR_get_purpose',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWReddsaVerify = getEmsc('TALER_WR_eddsa_verify',
|
||||||
|
'number',
|
||||||
|
['string',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRALLmakeEddsaSignature = getEmsc('TALER_WRALL_make_eddsa_signature',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRALLamountAdd = getEmsc('TALER_WRALL_amount_add',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'string']);
|
||||||
|
|
||||||
|
var TamountSubtract = getEmsc('TALER_amount_subtract',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TamountAdd = getEmsc('TALER_amount_add',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
var TWRALLeddsaPublicKeyFromPrivate = getEmsc('TALER_WRALL_eddsa_public_key_from_private',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRALLeddsaPublicKeyFromPrivString = getEmsc('TALER_WRALL_eddsa_public_key_from_priv_string',
|
||||||
|
'number',
|
||||||
|
['string']);
|
||||||
|
|
||||||
|
var TWRALLsignDepositPermission = getEmsc('TALER_WRALL_sign_deposit_permission',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRALLeddsaPrivateKeyFromString = getEmsc('TALER_WRALL_eddsa_private_key_from_string',
|
||||||
|
'number',
|
||||||
|
['string']);
|
||||||
|
|
||||||
|
var TWRALLrsaPublicKeyDecodeFromString = getEmsc('TALER_WRALL_rsa_public_key_decode_from_string',
|
||||||
|
'number',
|
||||||
|
['string']);
|
||||||
|
|
||||||
|
var TWRALLecdhePublicKeyFromPrivateKey = getEmsc('TALER_WRALL_ecdhe_public_key_from_private_key',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRALLeccEcdh = getEmsc('TALER_WRALL_ecc_ecdh',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRALLmakeWithdrawBundle = getEmsc('TALER_WRALL_make_withdraw_bundle',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var WRALLmakePurpose = getEmsc('WRALL_make_purpose',
|
||||||
|
'number',
|
||||||
|
['string',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaSignatureDecode = getEmsc('GNUNET_CRYPTO_rsa_signature_decode',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaSignatureEncode = getEmsc('GNUNET_CRYPTO_rsa_signature_encode',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaPublicKeyEncode = getEmsc('GNUNET_CRYPTO_rsa_public_key_encode',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaPublicKeyDecode = getEmsc('GNUNET_CRYPTO_rsa_public_key_decode',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaPrivateKeyGetPublic = getEmsc('GNUNET_CRYPTO_rsa_private_key_get_public',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCALLrsaPrivateKeyCreate = getEmsc('GNUNET_CRYPTO_rsa_private_key_create',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCALLrsaBlindingKeyCreate = getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCrsaBlindingKeyFree = getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free',
|
||||||
|
'void',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCrsaPublicKeyFree = getEmsc('GNUNET_CRYPTO_rsa_public_key_free',
|
||||||
|
'void',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCrsaPrivateKeyFree = getEmsc('GNUNET_CRYPTO_rsa_private_key_free',
|
||||||
|
'void',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GCALLrsaBlind = getEmsc('GNUNET_CRYPTO_rsa_blind',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaUnblind = getEmsc('GNUNET_CRYPTO_rsa_unblind',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCALLrsaSign = getEmsc('GNUNET_CRYPTO_rsa_sign',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCrsaVerify = getEmsc('GNUNET_CRYPTO_rsa_verify',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCrsaSignatureFree = getEmsc('GNUNET_CRYPTO_rsa_signature_free',
|
||||||
|
'void',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var GChkdf = getEmsc('GNUNET_CRYPTO_hkdf',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var TWRALLgenKeyFromBlob = getEmsc('TALER_WRALL_gen_key_from_blob',
|
||||||
|
'number',
|
||||||
|
['string',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var DWRtestString = getEmsc('DEBUG_WR_test_string',
|
||||||
|
'void',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'string']);
|
||||||
|
|
||||||
|
var GCsymmetricDecrypt = getEmsc('GNUNET_CRYPTO_symmetric_decrypt',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
var GCsymmetricEncrypt = getEmsc('GNUNET_CRYPTO_symmetric_encrypt',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* returns a pointer to a symmetric session key strucure and takes a salt, a
|
||||||
|
(pointer to) binary data used to generate the key, and the length of that
|
||||||
|
data */
|
||||||
|
var TWRALLgenSymmetricKey = getEmsc('TALER_WRALL_gen_symmetric_key',
|
||||||
|
'number',
|
||||||
|
['string',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* returns a pointer to a init. vector strucure and takes a salt, a
|
||||||
|
(pointer to) binary data used to generate the key, and the length of that
|
||||||
|
data */
|
||||||
|
var TWRALLgenInitVector = getEmsc('TALER_WRALL_gen_init_vector',
|
||||||
|
'number',
|
||||||
|
['string',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
// return key material from ECC keys
|
||||||
|
var GCeccEcdh = getEmsc('GNUNET_CRYPTO_ecc_ecdh',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
// return a pointer to a freshly allocated EddsaPublicKey structure
|
||||||
|
/* var WRALLeddsaPublicKey = getEmsc('WRALL_eddsa_public_key',
|
||||||
|
'number'); */
|
||||||
|
|
||||||
|
// return a pointer to a freshly allocated EcdhePublicKey structure
|
||||||
|
/* var WRALLecdhePublicKey = getEmsc('WRALL_ecdhe_public_key',
|
||||||
|
'number'); */
|
||||||
|
|
||||||
|
/* generates a new eddsa private key, returning a pointer to EddsaPrivateKey
|
||||||
|
structure */
|
||||||
|
var GCALLeddsaKeyCreate = getEmsc('GNUNET_CRYPTO_eddsa_key_create',
|
||||||
|
'number');
|
||||||
|
|
||||||
|
/* extract eddsa public key from a pointer to a EddsaPrivateKey structure
|
||||||
|
and put it in second argument */
|
||||||
|
var GCeddsaKeyGetPublic = getEmsc('GNUNET_CRYPTO_eddsa_key_get_public',
|
||||||
|
'void',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* generates a new ecdhe private key, returning a pointer to EcdhePrivateKey
|
||||||
|
structure */
|
||||||
|
var GCALLecdheKeyCreate = getEmsc('GNUNET_CRYPTO_ecdhe_key_create',
|
||||||
|
'number');
|
||||||
|
|
||||||
|
/* extract eddsa public key from a pointer to a EddsaPrivateKey structure and
|
||||||
|
put it in second argument */
|
||||||
|
var GCecdheKeyGetPublic = getEmsc('GNUNET_CRYPTO_ecdhe_key_get_public',
|
||||||
|
'void',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
// what to sign, the reason to sign, the location to store the signature
|
||||||
|
var GCeddsaSign = getEmsc('GNUNET_CRYPTO_eddsa_sign',
|
||||||
|
'int',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* get reference to the emscripted primitive: the first parameter is a
|
||||||
|
pointer (note that it points to the emscripten's heap) to the data being
|
||||||
|
encoded, the second is its length */
|
||||||
|
var GSALLdataToStringAlloc = getEmsc('GNUNET_STRINGS_data_to_string_alloc',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
// import GNUnet's memory deallocator
|
||||||
|
var TWRgnunetFree = getEmsc('TALER_WR_GNUNET_free',
|
||||||
|
'void',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
// GNUnet's base32 decoder
|
||||||
|
var GSstringToData = getEmsc('GNUNET_STRINGS_string_to_data',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
// get absolute time. Returned value has to be freed by gnunetFree
|
||||||
|
var TWRALLgetCurrentTime = getEmsc('TALER_WRALL_get_current_time',
|
||||||
|
'number');
|
||||||
|
|
||||||
|
// prettyfy time
|
||||||
|
var TWRgetFancyTime = getEmsc('TALER_WR_get_fancy_time',
|
||||||
|
'string',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
var TWRALLhash = getEmsc('TALER_WRALL_hash',
|
||||||
|
'number',
|
||||||
|
['number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* computes the hashcode of the value pointed to by 'val' and sets the
|
||||||
|
pointer to the location holding the hashcode (which has to be previously
|
||||||
|
allocated and is a reflection of GNUNET_HashCode type). The returned
|
||||||
|
pointer has to be freed by gnunetFree.
|
||||||
|
Its interface is hash('val', 'valSize', 'hashedBuf') */
|
||||||
|
var GChash = getEmsc('GNUNET_CRYPTO_hash',
|
||||||
|
'void',
|
||||||
|
['number',
|
||||||
|
'number',
|
||||||
|
'number']);
|
||||||
|
|
||||||
|
/* this test just takes the private key to sign a dummy hardcoded
|
||||||
|
message. Return a pointer to the signed message (to be freed) */
|
||||||
|
var TWRALLsignTest = getEmsc('TALER_WRALL_sign_test',
|
||||||
|
'number',
|
||||||
|
['number']);
|
||||||
|
|
||||||
|
/* this test just takes the public key and the signed dummy
|
||||||
|
message. Return GNUNET_OK (=1) if it succeeds, otherwise
|
||||||
|
GNUNET_SYSERR (=-1) */
|
||||||
|
var WRverifyTest = getEmsc('WR_verify_test',
|
||||||
|
'number',
|
||||||
|
['number']);
|
1
extension/background/libwrapper.js
Symbolic link
1
extension/background/libwrapper.js
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../wallet_button/firefox_src/content/lib/libWrapper.jsm
|
@ -1,8 +1,40 @@
|
|||||||
// Nothing here yet.
|
|
||||||
// Eventually, the backend for the wallet will be implemented here.
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//chrome.browserAction.setBadgeBackgroundColor({color: "#000"})
|
//chrome.browserAction.setBadgeBackgroundColor({color: "#000"})
|
||||||
chrome.browserAction.setBadgeText({text: "42"})
|
chrome.browserAction.setBadgeText({text: "42"})
|
||||||
chrome.browserAction.setTitle({title: "Taler: 42 EUR"})
|
chrome.browserAction.setTitle({title: "Taler: 42 EUR"})
|
||||||
|
|
||||||
|
function test_emscripten ()
|
||||||
|
{
|
||||||
|
var cur_time = TWRALLgetCurrentTime();
|
||||||
|
var fancy_time = TWRgetFancyTime(cur_time);
|
||||||
|
console.log('current time: '+ fancy_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_emscripten();
|
||||||
|
|
||||||
|
DB.open(function () {
|
||||||
|
console.log ("DB: ready");
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener(
|
||||||
|
function (req, sender, onresponse) {
|
||||||
|
console.log("Message: " + req.type +
|
||||||
|
(sender.tab
|
||||||
|
? " from a content script: "+ sender.tab.url
|
||||||
|
: " from the extension"));
|
||||||
|
switch (req.type)
|
||||||
|
{
|
||||||
|
case "WALLET_GET":
|
||||||
|
DB.wallet_get (onresponse);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "TRANSACTION_LIST":
|
||||||
|
DB.transaction_list (onresponse);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "RESERVE_LIST":
|
||||||
|
DB.reserve_list (onresponse);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
91
extension/lib/util.js
Normal file
91
extension/lib/util.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format amount as String.
|
||||||
|
*
|
||||||
|
* @param amount
|
||||||
|
* Amount to be formatted.
|
||||||
|
*
|
||||||
|
* @return String, e.g. "1.23"
|
||||||
|
*/
|
||||||
|
function amount_format (amount)
|
||||||
|
{
|
||||||
|
let separator = "." // FIXME: depends on locale
|
||||||
|
return amount.value + separator + amount.fraction.toString().replace(/0+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format amount with currency as String.
|
||||||
|
*
|
||||||
|
* @param amount
|
||||||
|
* Amount to be formatted.
|
||||||
|
*
|
||||||
|
* @return String, e.g. "1.23 EUR"
|
||||||
|
*/
|
||||||
|
function amount_format_currency (amount)
|
||||||
|
{
|
||||||
|
return amount_format(amount) + " " + amount.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Date to String.
|
||||||
|
*
|
||||||
|
* Format: YYYY-MM-DD HH:mm
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* Date to be converted.
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
function date_format (date)
|
||||||
|
{
|
||||||
|
function pad (number) {
|
||||||
|
if (number < 10) {
|
||||||
|
return '0' + number;
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.getUTCFullYear() +
|
||||||
|
'-' + pad(date.getUTCMonth() + 1) +
|
||||||
|
'-' + pad(date.getUTCDate()) +
|
||||||
|
' ' + pad(date.getUTCHours()) +
|
||||||
|
':' + pad(date.getUTCMinutes());
|
||||||
|
//':' + pad(date.getUTCSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send HTTP request.
|
||||||
|
*
|
||||||
|
* @param method
|
||||||
|
* HTTP method.
|
||||||
|
* @param url
|
||||||
|
* URL to send to.
|
||||||
|
* @param content
|
||||||
|
* Content of request.
|
||||||
|
* @param content_type
|
||||||
|
* Content-Type HTTP header.
|
||||||
|
* @param onsuccess
|
||||||
|
* Function called by XMLHttpRequest on success.
|
||||||
|
* @param onerror
|
||||||
|
* Function called by XMLHttpRequest on error.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function http_req (method, url, content, content_type, onsuccess, onerror) {
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
|
||||||
|
req.onload = function(mintEvt) {
|
||||||
|
if (req.readyState == 4)
|
||||||
|
onsuccess(req.status, req.responseText);
|
||||||
|
};
|
||||||
|
|
||||||
|
req.onerror = onerror;
|
||||||
|
req.open(method, url, true);
|
||||||
|
req.setRequestHeader('Content-Type', content_type);
|
||||||
|
req.send(content);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
@ -21,11 +21,6 @@
|
|||||||
"default_popup": "popup/wallet.html"
|
"default_popup": "popup/wallet.html"
|
||||||
},
|
},
|
||||||
|
|
||||||
"web_accessible_resources": [
|
|
||||||
"popup/reserves.html",
|
|
||||||
"popup/wallet.html"
|
|
||||||
],
|
|
||||||
|
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["*://*/*"],
|
"matches": ["*://*/*"],
|
||||||
@ -35,7 +30,14 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background/wallet.js"]
|
"scripts": [
|
||||||
}
|
"lib/util.js",
|
||||||
|
"background/libwrapper.js",
|
||||||
|
"background/emscriptif.js",
|
||||||
|
"background/db.js",
|
||||||
|
"background/wallet.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="popup.css" type="text/css">
|
<link rel="stylesheet" href="popup.css" type="text/css">
|
||||||
|
<script src="../lib/util.js" type="text/javascript"></script>
|
||||||
<script src="transactions.js" type="text/javascript"></script>
|
<script src="transactions.js" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -1,22 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function format_date (date)
|
|
||||||
{
|
|
||||||
function pad (number) {
|
|
||||||
if (number < 10) {
|
|
||||||
return '0' + number;
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
return date.getUTCFullYear() +
|
|
||||||
'-' + pad(date.getUTCMonth() + 1) +
|
|
||||||
'-' + pad(date.getUTCDate()) +
|
|
||||||
' ' + pad(date.getUTCHours()) +
|
|
||||||
':' + pad(date.getUTCMinutes());
|
|
||||||
//':' + pad(date.getUTCSeconds());
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_transaction (date, currency, amount, status, contract)
|
function add_transaction (date, currency, amount, status, contract)
|
||||||
{
|
{
|
||||||
let table = document.getElementById('transactions-table');
|
let table = document.getElementById('transactions-table');
|
||||||
@ -26,7 +9,7 @@ function add_transaction (date, currency, amount, status, contract)
|
|||||||
|
|
||||||
let td_date = document.createElement('td');
|
let td_date = document.createElement('td');
|
||||||
td_date.className = 'date';
|
td_date.className = 'date';
|
||||||
let text_date = document.createTextNode(format_date (date));
|
let text_date = document.createTextNode(date_format (date));
|
||||||
tr.appendChild(td_date).appendChild(text_date);
|
tr.appendChild(td_date).appendChild(text_date);
|
||||||
|
|
||||||
let td_amount = document.createElement('td');
|
let td_amount = document.createElement('td');
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="popup.css" type="text/css">
|
<link rel="stylesheet" href="popup.css" type="text/css">
|
||||||
|
<script src="../lib/util.js" type="text/javascript"></script>
|
||||||
<script src="wallet.js" type="text/javascript"></script>
|
<script src="wallet.js" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -28,8 +28,13 @@ function select_currency (checkbox, currency, amount)
|
|||||||
|
|
||||||
function add_currency (currency, amount)
|
function add_currency (currency, amount)
|
||||||
{
|
{
|
||||||
|
let empty = document.getElementById('wallet-empty');
|
||||||
|
if (! /\bhidden\b/.test(empty.className))
|
||||||
|
empty.className += ' hidden';
|
||||||
|
|
||||||
let table = document.getElementById('wallet-table');
|
let table = document.getElementById('wallet-table');
|
||||||
table.className = table.className.replace(/\bhidden\b/, '');
|
table.className = table.className.replace(/\bhidden\b/, '');
|
||||||
|
|
||||||
let tr = document.createElement('tr');
|
let tr = document.createElement('tr');
|
||||||
tr.id = 'wallet-table-'+ currency;
|
tr.id = 'wallet-table-'+ currency;
|
||||||
table.appendChild(tr);
|
table.appendChild(tr);
|
||||||
@ -73,11 +78,18 @@ function update_currency (currency, amount)
|
|||||||
checkbox._amount = amount;
|
checkbox._amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener(
|
||||||
let empty = document.getElementById('wallet-empty');
|
'DOMContentLoaded',
|
||||||
|
function () {
|
||||||
|
chrome.runtime.sendMessage({type: "WALLET_GET"}, function(wallet) {
|
||||||
|
for (let currency in wallet)
|
||||||
|
{
|
||||||
|
let amount = amount_format(wallet[currency]);
|
||||||
|
add_currency(currency, amount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FIXME
|
// FIXME: remove
|
||||||
empty.className += ' hidden';
|
|
||||||
add_currency('EUR', 42);
|
add_currency('EUR', 42);
|
||||||
add_currency('USD', 17);
|
add_currency('USD', 17);
|
||||||
add_currency('KUD', 1337);
|
add_currency('KUD', 1337);
|
||||||
|
Loading…
Reference in New Issue
Block a user