diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts
index 7f0673748..959c0e557 100644
--- a/content_scripts/notify.ts
+++ b/content_scripts/notify.ts
@@ -282,7 +282,9 @@ namespace TalerNotify {
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
const walletMsg = {
type: "payment-failed",
- detail: {},
+ detail: {
+ contractHash: msg.H_contract
+ },
};
chrome.runtime.sendMessage(walletMsg, (resp) => {
sendResponse();
@@ -290,8 +292,20 @@ namespace TalerNotify {
});
addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => {
+ if (!msg.H_contract) {
+ console.error("H_contract missing in taler-payment-succeeded");
+ return;
+ }
console.log("got taler-payment-succeeded");
- sendResponse();
+ const walletMsg = {
+ type: "payment-succeeded",
+ detail: {
+ contractHash: msg.H_contract,
+ },
+ };
+ chrome.runtime.sendMessage(walletMsg, (resp) => {
+ sendResponse();
+ })
});
addHandler("taler-get-payment", (msg: any, sendResponse: any) => {
diff --git a/gulpfile.js b/gulpfile.js
index 70d8b2254..14287bb71 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -58,6 +58,9 @@ const paths = {
"pages/*.{ts,tsx}",
"!**/*.d.ts",
],
+ decl: [
+ "lib/refs.d.ts",
+ ],
dev: [
"test/tests/*.{ts,tsx}",
],
@@ -73,10 +76,10 @@ const paths = {
"lib/module-trampoline.js",
"popup/**/*.{html,css}",
"pages/**/*.{html,css}",
- "lib/**/*.d.ts",
"background/*.html",
],
extra: [
+ "lib/**/*.d.ts",
"AUTHORS",
"README",
"COPYING",
@@ -220,7 +223,12 @@ gulp.task("compile-prod", ["clean"], function () {
tsArgs.outDir = ".";
// We don't want source maps for production
tsArgs.sourceMap = undefined;
- return gulp.src(paths.ts.release, {base: "."})
+ let opts = {base: "."};
+ const files = concatStreams(
+ gulp.src(paths.ts.release, opts),
+ gulp.src(paths.ts.decl, opts));
+
+ return files
.pipe(ts(tsArgs))
.pipe(gulp.dest("build/ext/"));
});
@@ -274,6 +282,7 @@ gulp.task("srcdist", [], function () {
// We can't just concat patterns due to exclude patterns
const files = concatStreams(
gulp.src(paths.ts.release, opts),
+ gulp.src(paths.ts.decl, opts),
gulp.src(paths.ts.dev, opts),
gulp.src(paths.dist, opts),
gulp.src(paths.extra, opts));
@@ -346,9 +355,13 @@ function tsconfig(confBase) {
// Generate the tsconfig file
// that should be used during development.
gulp.task("tsconfig", function() {
- return gulp.src(Array.prototype.concat(paths.ts.release, paths.ts.dev), {base: "."})
- .pipe(tsconfig(tsBaseArgs))
- .pipe(gulp.dest("."));
+ let opts = {base: "."};
+ const files = concatStreams(
+ gulp.src(paths.ts.release, opts),
+ gulp.src(paths.ts.dev, opts),
+ gulp.src(paths.ts.decl, opts));
+ return files.pipe(tsconfig(tsBaseArgs))
+ .pipe(gulp.dest("."));
});
diff --git a/lib/refs.ts b/lib/refs.ts
deleted file mode 100644
index a9c2c5eb8..000000000
--- a/lib/refs.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-
-// Help the TypeScript compiler find declarations.
-
-///
-///
-///
diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts
index 3b9d6d228..2782327ac 100644
--- a/lib/wallet/cryptoLib.ts
+++ b/lib/wallet/cryptoLib.ts
@@ -203,6 +203,8 @@ namespace RpcFunctions {
let newAmount = new native.Amount(cd.coin.currentAmount);
newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson();
+ cd.coin.dirty = true;
+ cd.coin.transactionPending = true;
let d = new native.DepositRequestPS({
h_contract: native.HashCode.fromCrock(offer.H_contract),
@@ -338,4 +340,8 @@ namespace RpcFunctions {
return refreshSession;
}
+ export function hashString(str: string): string {
+ const b = native.ByteArray.fromStringWithNull(str);
+ return b.hash().toCrock();
+ }
}
\ No newline at end of file
diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts
index 55e943393..9133330a2 100644
--- a/lib/wallet/db.ts
+++ b/lib/wallet/db.ts
@@ -25,7 +25,7 @@
*/
const DB_NAME = "taler";
-const DB_VERSION = 8;
+const DB_VERSION = 10;
/**
* Return a promise that resolves
@@ -59,14 +59,15 @@ export function openTalerDb(): Promise {
"contract.repurchase_correlation_id"
]);
- db.createObjectStore("precoins",
- {keyPath: "coinPub", autoIncrement: true});
+ db.createObjectStore("precoins", {keyPath: "coinPub"});
const history = db.createObjectStore("history",
{
keyPath: "id",
autoIncrement: true
});
history.createIndex("timestamp", "timestamp");
+ db.createObjectStore("refresh",
+ {keyPath: "meltCoinPub"});
break;
default:
if (e.oldVersion != DB_VERSION) {
diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts
index f1b1eedce..9d634618a 100644
--- a/lib/wallet/types.ts
+++ b/lib/wallet/types.ts
@@ -279,6 +279,19 @@ export interface Coin {
* to fix it.
*/
suspended?: boolean;
+
+ /**
+ * Was the coin revealed in a transaction?
+ */
+ dirty: boolean;
+
+ /**
+ * Is the coin currently involved in a transaction?
+ *
+ * This delays refreshing until the transaction is finished or
+ * aborted.
+ */
+ transactionPending: boolean;
}
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index 43f4227dd..2c5210607 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -27,7 +27,7 @@ import {
IExchangeInfo,
Denomination,
Notifier,
- WireInfo, RefreshSession, ReserveRecord
+ WireInfo, RefreshSession, ReserveRecord, CoinPaySig
} from "./types";
import {HttpResponse, RequestException} from "./http";
import {QueryRoot} from "./query";
@@ -135,11 +135,22 @@ interface ExchangeCoins {
[exchangeUrl: string]: CoinWithDenom[];
}
+interface PayReq {
+ amount: AmountJson;
+ coins: CoinPaySig[];
+ H_contract: string;
+ max_fee: AmountJson;
+ merchant_sig: string;
+ exchange: string;
+ refund_deadline: string;
+ timestamp: string;
+ transaction_id: number;
+}
interface Transaction {
contractHash: string;
contract: Contract;
- payReq: any;
+ payReq: PayReq;
merchantSig: string;
}
@@ -492,16 +503,17 @@ export class Wallet {
private async recordConfirmPay(offer: Offer,
payCoinInfo: PayCoinInfo,
chosenExchange: string): Promise {
- let payReq: any = {};
- payReq["amount"] = offer.contract.amount;
- payReq["coins"] = payCoinInfo.map((x) => x.sig);
- payReq["H_contract"] = offer.H_contract;
- payReq["max_fee"] = offer.contract.max_fee;
- payReq["merchant_sig"] = offer.merchant_sig;
- payReq["exchange"] = URI(chosenExchange).href();
- payReq["refund_deadline"] = offer.contract.refund_deadline;
- payReq["timestamp"] = offer.contract.timestamp;
- payReq["transaction_id"] = offer.contract.transaction_id;
+ let payReq: PayReq = {
+ amount: offer.contract.amount,
+ coins: payCoinInfo.map((x) => x.sig),
+ H_contract: offer.H_contract,
+ max_fee: offer.contract.max_fee,
+ merchant_sig: offer.merchant_sig,
+ exchange: URI(chosenExchange).href(),
+ refund_deadline: offer.contract.refund_deadline,
+ timestamp: offer.contract.timestamp,
+ transaction_id: offer.contract.transaction_id,
+ };
let t: Transaction = {
contractHash: offer.H_contract,
contract: offer.contract,
@@ -792,6 +804,8 @@ export class Wallet {
denomSig: denomSig,
currentAmount: pc.coinValue,
exchangeBaseUrl: pc.exchangeBaseUrl,
+ dirty: false,
+ transactionPending: false,
};
return coin;
}
@@ -1127,18 +1141,53 @@ export class Wallet {
let newCoinDenoms = getWithdrawDenomList(availableAmount,
availableDenoms);
- newCoinDenoms = [newCoinDenoms[0]];
console.log("refreshing into", newCoinDenoms);
+ if (newCoinDenoms.length == 0) {
+ console.log("not refreshing, value too small");
+ return;
+ }
+
let refreshSession: RefreshSession = await (
this.cryptoApi.createRefreshSession(exchange.baseUrl,
- 3,
- coin,
- newCoinDenoms,
- oldDenom.fee_refresh));
+ 3,
+ coin,
+ newCoinDenoms,
+ oldDenom.fee_refresh));
- let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl);
+ coin.currentAmount = Amounts.sub(coin.currentAmount,
+ refreshSession.valueWithFee).amount;
+
+ // FIXME: we should check whether the amount still matches!
+ await this.q()
+ .put("refresh", refreshSession)
+ .put("coins", coin)
+ .finish();
+
+ await this.refreshMelt(refreshSession);
+
+ let r = await this.q().get("refresh", oldCoinPub);
+ if (!r) {
+ throw Error("refresh session does not exist anymore");
+ }
+ await this.refreshReveal(r);
+ }
+
+
+ async refreshMelt(refreshSession: RefreshSession): Promise {
+ if (refreshSession.norevealIndex != undefined) {
+ console.error("won't melt again");
+ return;
+ }
+
+ let coin = await this.q().get("coins", refreshSession.meltCoinPub);
+ if (!coin) {
+ console.error("can't melt coin, it does not exist");
+ return;
+ }
+
+ let reqUrl = URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl);
let meltCoin = {
coin_pub: coin.coinPub,
denom_pub: coin.denomPub,
@@ -1148,7 +1197,7 @@ export class Wallet {
};
let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv));
let req = {
- "new_denoms": newCoinDenoms.map((d) => d.denom_pub),
+ "new_denoms": refreshSession.newDenoms,
"melt_coin": meltCoin,
"transfer_pubs": refreshSession.transferPubs,
"coin_evs": coinEvs,
@@ -1178,11 +1227,10 @@ export class Wallet {
refreshSession.norevealIndex = norevealIndex;
- this.refreshReveal(refreshSession);
-
- // FIXME: implement rest
+ await this.q().put("refresh", refreshSession).finish();
}
+
async refreshReveal(refreshSession: RefreshSession): Promise {
let norevealIndex = refreshSession.norevealIndex;
if (norevealIndex == undefined) {
@@ -1196,12 +1244,54 @@ export class Wallet {
"transfer_privs": privs,
};
- let reqUrl = URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl);
+ let reqUrl = URI("refresh/reveal")
+ .absoluteTo(refreshSession.exchangeBaseUrl);
console.log("reveal request:", req);
let resp = await this.http.postJson(reqUrl, req);
console.log("session:", refreshSession);
console.log("reveal response:", resp);
+
+ if (resp.status != 200) {
+ console.log("error: /refresh/reveal returned status " + resp.status);
+ return;
+ }
+
+ let respJson = JSON.parse(resp.responseText);
+
+ if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
+ console.log("/refresh/reveal did not contain ev_sigs");
+ }
+
+ let exchange = await this.q().get("exchanges", refreshSession.exchangeBaseUrl);
+ if (!exchange) {
+ console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
+ return;
+ }
+
+ for (let i = 0; i < respJson.ev_sigs.length; i++) {
+ let denom = exchange.all_denoms.find((d) => d.denom_pub == refreshSession.newDenoms[i]);
+ if (!denom) {
+ console.error("denom not found");
+ continue;
+ }
+ let pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i];
+ let denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig,
+ pc.blindingKey,
+ denom.denom_pub);
+ let coin: Coin = {
+ coinPub: pc.publicKey,
+ coinPriv: pc.privateKey,
+ denomPub: denom.denom_pub,
+ denomSig: denomSig,
+ currentAmount: denom.value,
+ exchangeBaseUrl: refreshSession.exchangeBaseUrl,
+ dirty: false,
+ transactionPending: false,
+ };
+
+ await this.q().put("coins", coin).finish();
+ }
}
@@ -1283,4 +1373,29 @@ export class Wallet {
return {isRepurchase: false};
}
}
+
+
+ async paymentSucceeded(contractHash: string): Promise {
+ const doPaymentSucceeded = async () => {
+ let t = await this.q().get("transactions", contractHash);
+ if (!t) {
+ console.error("contract not found");
+ return;
+ }
+ for (let pc of t.payReq.coins) {
+ let c = await this.q().get("coins", pc.coin_pub);
+ if (!c) {
+ console.error("coin not found");
+ return;
+ }
+ c.transactionPending = false;
+ await this.q().put("coins", c).finish();
+ }
+ for (let c of t.payReq.coins) {
+ this.refresh(c.coin_pub);
+ }
+ };
+ doPaymentSucceeded();
+ return;
+ }
}
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
index 07f5cc1d8..1c3876772 100644
--- a/lib/wallet/wxMessaging.ts
+++ b/lib/wallet/wxMessaging.ts
@@ -218,6 +218,13 @@ function makeHandlers(db: IDBDatabase,
wallet.updateExchanges();
return Promise.resolve();
},
+ ["payment-succeeded"]: function (detail, sender) {
+ let contractHash = detail.contractHash;
+ if (!contractHash) {
+ return Promise.reject(Error("contractHash missing"));
+ }
+ return wallet.paymentSucceeded(contractHash);
+ },
};
}
diff --git a/tsconfig.json b/tsconfig.json
index fa6cde6d3..e9efcb50c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,8 +13,10 @@
"noImplicitAny": true
},
"files": [
+ "lib/components.ts",
+ "test/tests/taler.ts",
+ "lib/refs.d.ts",
"lib/i18n.ts",
- "lib/refs.ts",
"lib/shopApi.ts",
"lib/taler-wallet-lib.ts",
"lib/wallet/checkable.ts",
@@ -38,7 +40,6 @@
"pages/show-db.ts",
"pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx",
- "pages/tree.tsx",
- "test/tests/taler.ts"
+ "pages/tree.tsx"
]
}
\ No newline at end of file
diff --git a/web-common b/web-common
index e9eb34dfb..02271055c 160000
--- a/web-common
+++ b/web-common
@@ -1 +1 @@
-Subproject commit e9eb34dfb56c8e5a18fa7d555aa651a4532e8f3c
+Subproject commit 02271055cbe9e62cffb3713d109a77015df625d1