history aggregation
This commit is contained in:
parent
cc34929da6
commit
693e7c92e0
@ -45,10 +45,54 @@ namespace TalerNotify {
|
||||
|
||||
interface Handler {
|
||||
type: string;
|
||||
listener: (e: CustomEvent) => void;
|
||||
listener: (e: CustomEvent) => void|Promise<void>;
|
||||
}
|
||||
const handlers: Handler[] = [];
|
||||
|
||||
function hashContract(contract: string): Promise<string> {
|
||||
let walletHashContractMsg = {
|
||||
type: "hash-contract",
|
||||
detail: {contract}
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
|
||||
if (!resp.hash) {
|
||||
console.log("error", resp);
|
||||
reject(Error("hashing failed"));
|
||||
}
|
||||
resolve(resp.hash);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkRepurchase(contract: string): Promise<any> {
|
||||
const walletMsg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: contract
|
||||
},
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||
resolve(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function putHistory(historyEntry: any): Promise<void> {
|
||||
const walletMsg = {
|
||||
type: "put-history-entry",
|
||||
detail: {
|
||||
historyEntry,
|
||||
},
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
chrome.runtime.sendMessage({type: "ping"}, (resp) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
@ -150,7 +194,7 @@ namespace TalerNotify {
|
||||
});
|
||||
|
||||
|
||||
addHandler("taler-confirm-contract", (msg: any) => {
|
||||
addHandler("taler-confirm-contract", async(msg: any) => {
|
||||
if (!msg.contract_wrapper) {
|
||||
console.error("contract wrapper missing");
|
||||
return;
|
||||
@ -173,30 +217,20 @@ namespace TalerNotify {
|
||||
detail: {contract: offer.contract}
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
|
||||
let contractHash = await hashContract(offer.contract);
|
||||
|
||||
if (!resp.hash) {
|
||||
console.log("error", resp);
|
||||
throw Error("hashing failed");
|
||||
}
|
||||
|
||||
if (resp.hash != offer.H_contract) {
|
||||
if (contractHash != offer.H_contract) {
|
||||
console.error("merchant-supplied contract hash is wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
const walletMsg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: offer.contract
|
||||
},
|
||||
};
|
||||
let resp = await checkRepurchase(offer.contract);
|
||||
|
||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||
if (resp.error) {
|
||||
console.error("wallet backend error", resp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.isRepurchase) {
|
||||
console.log("doing repurchase");
|
||||
console.assert(resp.existingFulfillmentUrl);
|
||||
@ -205,6 +239,25 @@ namespace TalerNotify {
|
||||
resp.existingContractHash);
|
||||
|
||||
} else {
|
||||
|
||||
let merchantName = "(unknown)";
|
||||
try {
|
||||
merchantName = offer.contract.merchant.name;
|
||||
} catch (e) {
|
||||
// bad contract / name not included
|
||||
}
|
||||
|
||||
let historyEntry = {
|
||||
timestamp: (new Date).getTime(),
|
||||
subjectId: `contract-${contractHash}`,
|
||||
type: "offer-contract",
|
||||
detail: {
|
||||
contractHash,
|
||||
merchantName,
|
||||
}
|
||||
};
|
||||
await putHistory(historyEntry);
|
||||
|
||||
const uri = URI(chrome.extension.getURL(
|
||||
"pages/confirm-contract.html"));
|
||||
const params = {
|
||||
@ -219,8 +272,6 @@ namespace TalerNotify {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
|
||||
const walletMsg = {
|
||||
|
@ -56,8 +56,20 @@ interface ReserveRecord {
|
||||
exchange_base_url: string,
|
||||
created: number,
|
||||
last_query: number|null,
|
||||
current_amount: null,
|
||||
/**
|
||||
* Current amount left in the reserve
|
||||
*/
|
||||
current_amount: AmountJson|null,
|
||||
/**
|
||||
* Amount requested when the reserve was created.
|
||||
* When a reserve is re-used (rare!) the current_amount can
|
||||
* be higher than the requested_amount
|
||||
*/
|
||||
requested_amount: AmountJson,
|
||||
/**
|
||||
* Amount we've already withdrawn from the reserve.
|
||||
*/
|
||||
withdrawn_amount: AmountJson;
|
||||
confirmed: boolean,
|
||||
}
|
||||
|
||||
@ -139,6 +151,7 @@ export interface HistoryRecord {
|
||||
timestamp: number;
|
||||
subjectId?: string;
|
||||
detail: any;
|
||||
level: HistoryLevel;
|
||||
}
|
||||
|
||||
|
||||
@ -154,6 +167,13 @@ interface Transaction {
|
||||
merchantSig: string;
|
||||
}
|
||||
|
||||
export enum HistoryLevel {
|
||||
Trace = 1,
|
||||
Developer = 2,
|
||||
Expert = 3,
|
||||
User = 4,
|
||||
}
|
||||
|
||||
|
||||
export interface Badge {
|
||||
setText(s: string): void;
|
||||
@ -531,6 +551,7 @@ export class Wallet {
|
||||
|
||||
async putHistory(historyEntry: HistoryRecord): Promise<void> {
|
||||
await Query(this.db).put("history", historyEntry).finish();
|
||||
this.notifier.notify();
|
||||
}
|
||||
|
||||
|
||||
@ -632,17 +653,21 @@ export class Wallet {
|
||||
let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url);
|
||||
let reserve = await this.updateReserve(reserveRecord.reserve_pub,
|
||||
exchange);
|
||||
await this.depleteReserve(reserve, exchange);
|
||||
let n = await this.depleteReserve(reserve, exchange);
|
||||
|
||||
if (n != 0) {
|
||||
let depleted = {
|
||||
type: "depleted-reserve",
|
||||
subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
|
||||
timestamp: (new Date).getTime(),
|
||||
detail: {
|
||||
reservePub: reserveRecord.reserve_pub,
|
||||
requestedAmount: reserveRecord.requested_amount,
|
||||
currentAmount: reserveRecord.current_amount,
|
||||
}
|
||||
};
|
||||
await Query(this.db).put("history", depleted).finish();
|
||||
}
|
||||
} catch (e) {
|
||||
// random, exponential backoff truncated at 3 minutes
|
||||
let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(),
|
||||
@ -656,7 +681,7 @@ export class Wallet {
|
||||
}
|
||||
|
||||
|
||||
private async processPreCoin(preCoin: any,
|
||||
private async processPreCoin(preCoin: PreCoin,
|
||||
retryDelayMs = 100): Promise<void> {
|
||||
try {
|
||||
const coin = await this.withdrawExecute(preCoin);
|
||||
@ -690,6 +715,7 @@ export class Wallet {
|
||||
current_amount: null,
|
||||
requested_amount: req.amount,
|
||||
confirmed: false,
|
||||
withdrawn_amount: Amounts.getZero(req.amount.currency)
|
||||
};
|
||||
|
||||
const historyEntry = {
|
||||
@ -787,9 +813,11 @@ export class Wallet {
|
||||
|
||||
async storeCoin(coin: Coin): Promise<void> {
|
||||
console.log("storing coin", new Date());
|
||||
let historyEntry = {
|
||||
|
||||
let historyEntry: HistoryRecord = {
|
||||
type: "withdraw",
|
||||
timestamp: (new Date).getTime(),
|
||||
level: HistoryLevel.Expert,
|
||||
detail: {
|
||||
coinPub: coin.coinPub,
|
||||
}
|
||||
@ -821,13 +849,14 @@ export class Wallet {
|
||||
* Withdraw coins from a reserve until it is empty.
|
||||
*/
|
||||
private async depleteReserve(reserve: any,
|
||||
exchange: IExchangeInfo): Promise<void> {
|
||||
exchange: IExchangeInfo): Promise<number> {
|
||||
let denomsAvailable: Denomination[] = copy(exchange.active_denoms);
|
||||
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
||||
denomsAvailable);
|
||||
|
||||
let ps = denomsForWithdraw.map((denom) => this.withdraw(denom, reserve));
|
||||
await Promise.all(ps);
|
||||
return ps.length;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
"description": "Privacy preserving and transparent payments",
|
||||
"manifest_version": 2,
|
||||
"name": "GNU Taler Wallet (git)",
|
||||
"version": "0.6.6",
|
||||
"version_name": "0.0.1-pre2",
|
||||
"version": "0.6.7",
|
||||
"version_name": "0.0.1-pre3",
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
|
@ -69,3 +69,16 @@ body {
|
||||
#reserve-create table .input input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.historyItem {
|
||||
border: 1px solid black;
|
||||
border-radius: 10px;
|
||||
padding-left: 0.5em;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.historyDate {
|
||||
font-size: 90%;
|
||||
margin: 0.3em;
|
||||
color: slategray;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
||||
import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
|
||||
import {HistoryRecord} from "../lib/wallet/wallet";
|
||||
import {HistoryRecord, HistoryLevel} from "../lib/wallet/wallet";
|
||||
import {AmountJson} from "../lib/wallet/types";
|
||||
|
||||
declare var m: any;
|
||||
@ -92,7 +92,6 @@ function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace WalletBalance {
|
||||
export function controller() {
|
||||
return new Controller();
|
||||
@ -138,7 +137,11 @@ namespace WalletBalance {
|
||||
return listing;
|
||||
}
|
||||
let helpLink = m("a",
|
||||
{config: openInExtension, href: chrome.extension.getURL("pages/help/empty-wallet.html")},
|
||||
{
|
||||
config: openInExtension,
|
||||
href: chrome.extension.getURL(
|
||||
"pages/help/empty-wallet.html")
|
||||
},
|
||||
i18n`help`);
|
||||
|
||||
return i18n.parts`You have no balance to show. Need some ${helpLink} getting started?`;
|
||||
@ -158,8 +161,12 @@ function formatAmount(amount: AmountJson) {
|
||||
}
|
||||
|
||||
|
||||
function abbrevKey(s: string) {
|
||||
return m("span.abbrev", {title: s}, (s.slice(0, 5) + ".."))
|
||||
function abbrev(s: string, n: number = 5) {
|
||||
let sAbbrev = s;
|
||||
if (s.length > n) {
|
||||
sAbbrev = s.slice(0, n) + "..";
|
||||
}
|
||||
return m("span.abbrev", {title: s}, sAbbrev);
|
||||
}
|
||||
|
||||
|
||||
@ -180,29 +187,36 @@ function formatHistoryItem(historyItem: HistoryRecord) {
|
||||
switch (historyItem.type) {
|
||||
case "create-reserve":
|
||||
return m("p",
|
||||
i18n.parts`Created reserve (${abbrevKey(d.reservePub)}) of ${formatAmount(
|
||||
d.requestedAmount)} at ${formatTimestamp(
|
||||
t)}`);
|
||||
i18n.parts`Bank requested reserve (${abbrev(d.reservePub)}) for ${formatAmount(
|
||||
d.requestedAmount)}.`);
|
||||
case "confirm-reserve":
|
||||
return m("p",
|
||||
i18n.parts`Bank confirmed reserve (${abbrevKey(d.reservePub)}) at ${formatTimestamp(
|
||||
t)}`);
|
||||
i18n.parts`Started to withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
|
||||
d.requestedAmount)}.`);
|
||||
case "withdraw":
|
||||
return m("p",
|
||||
i18n`Withdraw at ${formatTimestamp(t)}`);
|
||||
case "offer-contract": {
|
||||
let link = chrome.extension.getURL("view-contract.html");
|
||||
let linkElem = m("a", {href: link}, abbrev(d.contractHash));
|
||||
let merchantElem = m("em", abbrev(d.merchantName, 15));
|
||||
return m("p",
|
||||
i18n.parts`Merchant ${merchantElem} offered contract ${linkElem}.`);
|
||||
}
|
||||
case "depleted-reserve":
|
||||
return m("p",
|
||||
i18n.parts`Wallet depleted reserve (${abbrevKey(d.reservePub)}) at ${formatTimestamp(t)}`);
|
||||
case "pay":
|
||||
i18n.parts`Withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
|
||||
d.requestedAmount)} completed.`);
|
||||
case "pay": {
|
||||
let url = substituteFulfillmentUrl(d.fulfillmentUrl,
|
||||
{H_contract: d.contractHash});
|
||||
return m("p",
|
||||
[
|
||||
i18n`Payment for ${formatAmount(d.amount)} to merchant ${d.merchantName}. `,
|
||||
m(`a`,
|
||||
let merchantElem = m("em", abbrev(d.merchantName, 15));
|
||||
let fulfillmentLinkElem = m(`a`,
|
||||
{href: url, onclick: openTab(url)},
|
||||
"Retry")
|
||||
]);
|
||||
"view product");
|
||||
return m("p",
|
||||
i18n.parts`Confirmed payment of ${formatAmount(d.amount)} to merchant ${merchantElem}. (${fulfillmentLinkElem})`);
|
||||
}
|
||||
default:
|
||||
return m("p", i18n`Unknown event (${historyItem.type})`);
|
||||
}
|
||||
@ -252,11 +266,20 @@ namespace WalletHistory {
|
||||
let subjectMemo: {[s: string]: boolean} = {};
|
||||
let listing: any[] = [];
|
||||
for (let record of history.reverse()) {
|
||||
//if (record.subjectId && subjectMemo[record.subjectId]) {
|
||||
// return;
|
||||
//}
|
||||
if (record.subjectId && subjectMemo[record.subjectId]) {
|
||||
continue;
|
||||
}
|
||||
if (record.level != undefined && record.level < HistoryLevel.User) {
|
||||
continue;
|
||||
}
|
||||
subjectMemo[record.subjectId as string] = true;
|
||||
listing.push(formatHistoryItem(record));
|
||||
|
||||
let item = m("div.historyItem", {}, [
|
||||
m("div.historyDate", {}, (new Date(record.timestamp * 1000)).toString()),
|
||||
formatHistoryItem(record)
|
||||
]);
|
||||
|
||||
listing.push(item);
|
||||
}
|
||||
|
||||
if (listing.length > 0) {
|
||||
|
Loading…
Reference in New Issue
Block a user