Merge branch 'master' of git+ssh://taler.net/var/git/wallet-webex

This commit is contained in:
Christian Grothoff 2016-09-22 15:09:18 +02:00
commit 4974dd19c0
32 changed files with 14458 additions and 12645 deletions

BIN
articles/ui/ui.pdf Normal file

Binary file not shown.

View File

@ -31,24 +31,149 @@
namespace TalerNotify { namespace TalerNotify {
const PROTOCOL_VERSION = 1; const PROTOCOL_VERSION = 1;
console.log("Taler injected", chrome.runtime.id); /**
* Wallet-internal version of offerContractFrom, used for 402 payments.
*/
function internalOfferContractFrom(url: string) {
function handle_contract(contract_wrapper: any) {
var cEvent = new CustomEvent("taler-confirm-contract", {
detail: {
contract_wrapper: contract_wrapper,
replace_navigation: true
}
});
document.dispatchEvent(cEvent);
}
function subst(url: string, H_contract) { var contract_request = new XMLHttpRequest();
console.log("downloading contract from '" + url + "'");
contract_request.open("GET", url, true);
contract_request.onload = function (e) {
if (contract_request.readyState == 4) {
if (contract_request.status == 200) {
console.log("response text:",
contract_request.responseText);
var contract_wrapper = JSON.parse(contract_request.responseText);
if (!contract_wrapper) {
console.error("response text was invalid json");
alert("Failure to download contract (invalid json)");
return;
}
handle_contract(contract_wrapper);
} else {
alert("Failure to download contract from merchant " +
"(" + contract_request.status + "):\n" +
contract_request.responseText);
}
}
};
contract_request.onerror = function (e) {
alert("Failure requesting the contract:\n"
+ contract_request.statusText);
};
contract_request.send();
}
/**
* Wallet-internal version of executeContract, used for 402 payments.
*
* Even though we're inside a content script, we send events to the dom
* to avoid code duplication.
*/
function internalExecuteContract(contractHash: string, payUrl: string,
offerUrl: string) {
/**
* Handle a failed payment.
*
* Try to notify the wallet first, before we show a potentially
* synchronous error message (such as an alert) or leave the page.
*/
function handleFailedPayment(status: any) {
const msg = {
type: "payment-failed",
detail: {},
};
chrome.runtime.sendMessage(msg, (resp) => {
alert("payment failed");
});
}
function handleResponse(evt: CustomEvent) {
console.log("handling taler-notify-payment");
// Payment timeout in ms.
let timeout_ms = 1000;
// Current request.
let r: XMLHttpRequest | null = null;
let timeoutHandle: number|null = null;
function sendPay() {
r = new XMLHttpRequest();
r.open("post", payUrl);
r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
r.send(JSON.stringify(evt.detail.payment));
r.onload = function() {
if (!r) {
throw Error("invariant");
}
switch (r.status) {
case 200:
window.location.href = subst(evt.detail.contract.fulfillment_url,
evt.detail.H_contract);
window.location.reload(true);
break;
default:
handleFailedPayment(r.status);
break;
}
r = null;
if (timeoutHandle != null) {
clearTimeout(timeoutHandle);
timeoutHandle = null;
}
};
function retry() {
if (r) {
r.abort();
r = null;
}
timeout_ms = Math.min(timeout_ms * 2, 10 * 1000);
console.log("sendPay timed out, retrying in ", timeout_ms, "ms");
sendPay();
}
timeoutHandle = setTimeout(retry, timeout_ms);
}
sendPay();
}
let detail = {
H_contract: contractHash,
offering_url: offerUrl
};
document.addEventListener("taler-notify-payment", handleResponse, false);
let eve = new CustomEvent('taler-execute-contract', {detail: detail});
document.dispatchEvent(eve);
}
function subst(url: string, H_contract: string) {
url = url.replace("${H_contract}", H_contract); url = url.replace("${H_contract}", H_contract);
url = url.replace("${$}", "$"); url = url.replace("${$}", "$");
return url; return url;
} }
const handlers = []; interface Handler {
type: string;
listener: (e: CustomEvent) => void;
}
const handlers: Handler[] = [];
function init() { function init() {
chrome.runtime.sendMessage({type: "ping"}, () => { chrome.runtime.sendMessage({type: "ping"}, (resp) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.log("extension not yet ready"); console.log("extension not yet ready");
window.setTimeout(init, 200); window.setTimeout(init, 200);
return; return;
} }
console.log("got pong");
registerHandlers(); registerHandlers();
// Hack to know when the extension is unloaded // Hack to know when the extension is unloaded
let port = chrome.runtime.connect(); let port = chrome.runtime.connect();
@ -59,15 +184,27 @@ namespace TalerNotify {
document.removeEventListener(handler.type, handler.listener); document.removeEventListener(handler.type, handler.listener);
} }
}); });
if (resp && resp.type === "fetch") {
console.log("it's fetch");
internalOfferContractFrom(resp.contractUrl);
document.documentElement.style.visibility = "hidden";
} else if (resp && resp.type === "execute") {
console.log("it's execute");
document.documentElement.style.visibility = "hidden";
internalExecuteContract(resp.contractHash, resp.payUrl, resp.offerUrl);
}
}); });
} }
console.log("loading Taler content script");
init(); init();
function registerHandlers() { function registerHandlers() {
const $ = (x) => document.getElementById(x); function addHandler(type: string, listener: (e: CustomEvent) => void) {
function addHandler(type, listener) {
document.addEventListener(type, listener); document.addEventListener(type, listener);
handlers.push({type, listener}); handlers.push({type, listener});
} }
@ -193,7 +330,7 @@ namespace TalerNotify {
console.log("got resp"); console.log("got resp");
console.dir(resp); console.dir(resp);
if (!resp.success) { if (!resp.success) {
console.log("got event detial:"); console.log("got event detail:");
console.dir(e.detail); console.dir(e.detail);
if (e.detail.offering_url) { if (e.detail.offering_url) {
console.log("offering url", e.detail.offering_url); console.log("offering url", e.detail.offering_url);

View File

@ -117,7 +117,7 @@ const paths = {
const tsBaseArgs = { const tsBaseArgs = {
target: "es5", target: "es6",
jsx: "react", jsx: "react",
experimentalDecorators: true, experimentalDecorators: true,
module: "system", module: "system",
@ -125,6 +125,8 @@ const tsBaseArgs = {
noLib: true, noLib: true,
noImplicitReturns: true, noImplicitReturns: true,
noFallthroughCasesInSwitch: true, noFallthroughCasesInSwitch: true,
strictNullChecks: true,
noImplicitAny: true,
}; };

File diff suppressed because it is too large Load Diff

16944
lib/decl/lib.es6.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ interface System {
config: any; config: any;
newModule(obj: Object): any; newModule(obj: Object): any;
normalizeSync(name: string): string; normalizeSync(name: string): string;
set(moduleName: string, module: any) set(moduleName: string, module: any): void;
} }

View File

@ -1,199 +0,0 @@
// Type definitions for WebRTC
// Project: http://dev.w3.org/2011/webrtc/
// Definitions by: Ken Smith <https://github.com/smithkl42/>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
// Taken from http://dev.w3.org/2011/webrtc/editor/getusermedia.html
// version: W3C Editor's Draft 29 June 2015
interface ConstrainBooleanParameters {
exact?: boolean;
ideal?: boolean;
}
interface NumberRange {
max?: number;
min?: number;
}
interface ConstrainNumberRange extends NumberRange {
exact?: number;
ideal?: number;
}
interface ConstrainStringParameters {
exact?: string | string[];
ideal?: string | string[];
}
interface MediaStreamConstraints {
video?: boolean | MediaTrackConstraints;
audio?: boolean | MediaTrackConstraints;
}
declare module W3C {
type LongRange = NumberRange;
type DoubleRange = NumberRange;
type ConstrainBoolean = boolean | ConstrainBooleanParameters;
type ConstrainNumber = number | ConstrainNumberRange;
type ConstrainLong = ConstrainNumber;
type ConstrainDouble = ConstrainNumber;
type ConstrainString = string | string[] | ConstrainStringParameters;
}
interface MediaTrackConstraints extends MediaTrackConstraintSet {
advanced?: MediaTrackConstraintSet[];
}
interface MediaTrackConstraintSet {
width?: W3C.ConstrainLong;
height?: W3C.ConstrainLong;
aspectRatio?: W3C.ConstrainDouble;
frameRate?: W3C.ConstrainDouble;
facingMode?: W3C.ConstrainString;
volume?: W3C.ConstrainDouble;
sampleRate?: W3C.ConstrainLong;
sampleSize?: W3C.ConstrainLong;
echoCancellation?: W3C.ConstrainBoolean;
latency?: W3C.ConstrainDouble;
deviceId?: W3C.ConstrainString;
groupId?: W3C.ConstrainString;
}
interface MediaTrackSupportedConstraints {
width?: boolean;
height?: boolean;
aspectRatio?: boolean;
frameRate?: boolean;
facingMode?: boolean;
volume?: boolean;
sampleRate?: boolean;
sampleSize?: boolean;
echoCancellation?: boolean;
latency?: boolean;
deviceId?: boolean;
groupId?: boolean;
}
interface MediaStream extends EventTarget {
id: string;
active: boolean;
onactive: EventListener;
oninactive: EventListener;
onaddtrack: (event: MediaStreamTrackEvent) => any;
onremovetrack: (event: MediaStreamTrackEvent) => any;
clone(): MediaStream;
stop(): void;
getAudioTracks(): MediaStreamTrack[];
getVideoTracks(): MediaStreamTrack[];
getTracks(): MediaStreamTrack[];
getTrackById(trackId: string): MediaStreamTrack;
addTrack(track: MediaStreamTrack): void;
removeTrack(track: MediaStreamTrack): void;
}
interface MediaStreamTrackEvent extends Event {
track: MediaStreamTrack;
}
declare enum MediaStreamTrackState {
"live",
"ended"
}
interface MediaStreamTrack extends EventTarget {
id: string;
kind: string;
label: string;
enabled: boolean;
muted: boolean;
remote: boolean;
readyState: MediaStreamTrackState;
onmute: EventListener;
onunmute: EventListener;
onended: EventListener;
onoverconstrained: EventListener;
clone(): MediaStreamTrack;
stop(): void;
getCapabilities(): MediaTrackCapabilities;
getConstraints(): MediaTrackConstraints;
getSettings(): MediaTrackSettings;
applyConstraints(constraints: MediaTrackConstraints): Promise<void>;
}
interface MediaTrackCapabilities {
width: number | W3C.LongRange;
height: number | W3C.LongRange;
aspectRatio: number | W3C.DoubleRange;
frameRate: number | W3C.DoubleRange;
facingMode: string;
volume: number | W3C.DoubleRange;
sampleRate: number | W3C.LongRange;
sampleSize: number | W3C.LongRange;
echoCancellation: boolean[];
latency: number | W3C.DoubleRange;
deviceId: string;
groupId: string;
}
interface MediaTrackSettings {
width: number;
height: number;
aspectRatio: number;
frameRate: number;
facingMode: string;
volume: number;
sampleRate: number;
sampleSize: number;
echoCancellation: boolean;
latency: number;
deviceId: string;
groupId: string;
}
interface MediaStreamError {
name: string;
message: string;
constraintName: string;
}
interface NavigatorGetUserMedia {
(constraints: MediaStreamConstraints,
successCallback: (stream: MediaStream) => void,
errorCallback: (error: MediaStreamError) => void): void;
}
interface Navigator {
getUserMedia: NavigatorGetUserMedia;
webkitGetUserMedia: NavigatorGetUserMedia;
mozGetUserMedia: NavigatorGetUserMedia;
msGetUserMedia: NavigatorGetUserMedia;
mediaDevices: MediaDevices;
}
interface MediaDevices {
getSupportedConstraints(): MediaTrackSupportedConstraints;
getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
enumerateDevices(): Promise<MediaDeviceInfo[]>;
}
interface MediaDeviceInfo {
label: string;
id: string;
kind: string;
facing: string;
}

View File

@ -33,7 +33,7 @@ export interface EmscFunGen {
export declare namespace Module { export declare namespace Module {
var cwrap: EmscFunGen; var cwrap: EmscFunGen;
function _free(ptr: number); function _free(ptr: number): void;
function _malloc(n: number): number; function _malloc(n: number): number;
@ -41,9 +41,10 @@ export declare namespace Module {
function getValue(ptr: number, type: string, noSafe?: boolean): number; function getValue(ptr: number, type: string, noSafe?: boolean): number;
function setValue(ptr: number, value: number, type: string, noSafe?: boolean); function setValue(ptr: number, value: number, type: string,
noSafe?: boolean): void;
function writeStringToMemory(s: string, function writeStringToMemory(s: string,
buffer: number, buffer: number,
dontAddNull?: boolean); dontAddNull?: boolean): void;
} }

View File

@ -24,14 +24,14 @@ document.addEventListener(
declare var i18n: any; declare var i18n: any;
const JedModule = window["Jed"]; const JedModule: any = (window as any)["Jed"];
var jed; var jed: any;
class PluralNumber { class PluralNumber {
n: number; n: number;
constructor(n) { constructor(n: number) {
this.n = n; this.n = n;
} }
@ -62,7 +62,7 @@ function init () {
/** Convert template strings to a msgid */ /** Convert template strings to a msgid */
function toI18nString(strings) { function toI18nString(strings: string[]) {
let str = ""; let str = "";
for (let i = 0; i < strings.length; i++) { for (let i = 0; i < strings.length; i++) {
str += strings[i]; str += strings[i];
@ -75,7 +75,7 @@ function toI18nString(strings) {
/** Use the first number in values to determine plural form */ /** Use the first number in values to determine plural form */
function getPluralValue (values) { function getPluralValue (values: any) {
let n = null; let n = null;
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
if ("number" === typeof values[i] || values[i] instanceof PluralNumber) { if ("number" === typeof values[i] || values[i] instanceof PluralNumber) {
@ -88,11 +88,11 @@ function getPluralValue (values) {
} }
var i18n = <any>function i18n(strings, ...values) { var i18n = <any>function i18n(strings: string[], ...values: any[]) {
init(); init();
if ("object" !== typeof jed) { if ("object" !== typeof jed) {
// Fallback implementation in case i18n lib is not there // Fallback implementation in case i18n lib is not there
return String.raw(strings, ...values); return String.raw(strings as any, ...values);
} }
let str = toI18nString (strings); let str = toI18nString (strings);
@ -109,11 +109,11 @@ i18n.strings = {};
* Interpolate i18nized values with arbitrary objects. * Interpolate i18nized values with arbitrary objects.
* @return Array of strings/objects. * @return Array of strings/objects.
*/ */
i18n.parts = function(strings, ...values) { i18n.parts = function(strings: string[], ...values: any[]) {
init(); init();
if ("object" !== typeof jed) { if ("object" !== typeof jed) {
// Fallback implementation in case i18n lib is not there // Fallback implementation in case i18n lib is not there
let parts = []; let parts: string[] = [];
for (let i = 0; i < strings.length; i++) { for (let i = 0; i < strings.length; i++) {
parts.push(strings[i]); parts.push(strings[i]);
@ -127,7 +127,7 @@ i18n.parts = function(strings, ...values) {
let str = toI18nString (strings); let str = toI18nString (strings);
let n = getPluralValue (values); let n = getPluralValue (values);
let tr = jed.ngettext(str, str, n).split(/%(\d+)\$s/); let tr = jed.ngettext(str, str, n).split(/%(\d+)\$s/);
let parts = []; let parts: string[] = [];
for (let i = 0; i < tr.length; i++) { for (let i = 0; i < tr.length; i++) {
if (0 == i % 2) { if (0 == i % 2) {
parts.push(tr[i]); parts.push(tr[i]);
@ -144,7 +144,7 @@ i18n.parts = function(strings, ...values) {
* Pluralize based on first numeric parameter in the template. * Pluralize based on first numeric parameter in the template.
* @todo The plural argument is used for extraction by pogen.js * @todo The plural argument is used for extraction by pogen.js
*/ */
i18n.pluralize = function (singular, plural) { i18n.pluralize = function (singular: any, plural: any) {
return singular; return singular;
}; };
@ -154,4 +154,4 @@ i18n.pluralize = function (singular, plural) {
*/ */
i18n.number = function (n : number) { i18n.number = function (n : number) {
return new PluralNumber (n); return new PluralNumber (n);
} };

169
lib/shopApi.ts Normal file
View File

@ -0,0 +1,169 @@
/*
This file is part of TALER
(C) 2015 GNUnet e.V.
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/>
*/
/**
* Implementation of the shop API, either invoked via HTTP or
* via a JS DOM Events.
*
* @author Florian Dold
*/
function subst(url: string, H_contract: string) {
url = url.replace("${H_contract}", H_contract);
url = url.replace("${$}", "$");
return url;
}
export function createReserve(amount: any, callback_url: any, wt_types: any) {
let params = {
amount: JSON.stringify(amount),
callback_url: URI(callback_url)
.absoluteTo(document.location.href),
bank_url: document.location.href,
wt_types: JSON.stringify(wt_types),
};
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
}
export function confirmContract(contract_wrapper: any, replace_navigation: any) {
if (contract_wrapper) {
console.error("contract wrapper missing");
return;
}
const offer = contract_wrapper;
if (!offer.contract) {
console.error("contract field missing");
return;
}
const msg = {
type: "check-repurchase",
detail: {
contract: offer.contract
},
};
chrome.runtime.sendMessage(msg, (resp) => {
if (resp.error) {
console.error("wallet backend error", resp);
return;
}
if (resp.isRepurchase) {
console.log("doing repurchase");
console.assert(resp.existingFulfillmentUrl);
console.assert(resp.existingContractHash);
window.location.href = subst(resp.existingFulfillmentUrl,
resp.existingContractHash);
} else {
const uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
const params = {
offer: JSON.stringify(offer),
merchantPageUrl: document.location.href,
};
const target = uri.query(params).href();
if (replace_navigation === true) {
document.location.replace(target);
} else {
document.location.href = target;
}
}
});
}
/**
* Fetch a payment (coin deposit permissions) for a given contract.
* If we don't have the payment for the contract, redirect to
* offering url instead.
*/
export function fetchPayment(H_contract: any, offering_url: any) {
const msg = {
type: "fetch-payment",
detail: {H_contract},
};
chrome.runtime.sendMessage(msg, (resp) => {
console.log("got resp");
console.dir(resp);
if (!resp.success) {
if (offering_url) {
console.log("offering url", offering_url);
window.location.href = offering_url;
} else {
console.error("fetch-payment failed");
}
return;
}
let contract = resp.contract;
if (!contract) {
throw Error("contract missing");
}
// We have the details for then payment, the merchant page
// is responsible to give it to the merchant.
let evt = new CustomEvent("taler-notify-payment", {
detail: {
H_contract: H_contract,
contract: resp.contract,
payment: resp.payReq,
}
});
document.dispatchEvent(evt);
});
}
/**
* Offer a contract to the wallet after
* downloading it from the given URL.
*/
function offerContractFrom(url: string) {
var contract_request = new XMLHttpRequest();
console.log("downloading contract from '" + url + "'");
contract_request.open("GET", url, true);
contract_request.onload = function (e) {
if (contract_request.readyState == 4) {
if (contract_request.status == 200) {
console.log("response text:",
contract_request.responseText);
var contract_wrapper = JSON.parse(contract_request.responseText);
if (!contract_wrapper) {
console.error("response text was invalid json");
alert("Failure to download contract (invalid json)");
return;
}
confirmContract(contract_wrapper, true);
} else {
alert("Failure to download contract from merchant " +
"(" + contract_request.status + "):\n" +
contract_request.responseText);
}
}
};
contract_request.onerror = function (e) {
alert("Failure requesting the contract:\n"
+ contract_request.statusText);
};
contract_request.send();
}

4390
lib/vendor/mithril.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -25,18 +25,39 @@
*/ */
export namespace Checkable { export namespace Checkable {
export function SchemaError(message) {
type Path = (number|string)[];
interface SchemaErrorConstructor {
new (err: string): SchemaError;
}
interface SchemaError {
name: string;
message: string;
}
interface Prop {
propertyKey: any;
checker: any;
type: any;
elementChecker?: any;
elementProp?: any;
}
export let SchemaError = (function SchemaError(message: string) {
this.name = 'SchemaError'; this.name = 'SchemaError';
this.message = message; this.message = message;
this.stack = (<any>new Error()).stack; this.stack = (<any>new Error()).stack;
} }) as any as SchemaErrorConstructor;
SchemaError.prototype = new Error; SchemaError.prototype = new Error;
let chkSym = Symbol("checkable"); let chkSym = Symbol("checkable");
function checkNumber(target, prop, path): any { function checkNumber(target: any, prop: Prop, path: Path): any {
if ((typeof target) !== "number") { if ((typeof target) !== "number") {
throw new SchemaError(`expected number for ${path}`); throw new SchemaError(`expected number for ${path}`);
} }
@ -44,7 +65,7 @@ export namespace Checkable {
} }
function checkString(target, prop, path): any { function checkString(target: any, prop: Prop, path: Path): any {
if (typeof target !== "string") { if (typeof target !== "string") {
throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`); throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`);
} }
@ -52,7 +73,7 @@ export namespace Checkable {
} }
function checkAnyObject(target, prop, path): any { function checkAnyObject(target: any, prop: Prop, path: Path): any {
if (typeof target !== "object") { if (typeof target !== "object") {
throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`); throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`);
} }
@ -60,12 +81,12 @@ export namespace Checkable {
} }
function checkAny(target, prop, path): any { function checkAny(target: any, prop: Prop, path: Path): any {
return target; return target;
} }
function checkList(target, prop, path): any { function checkList(target: any, prop: Prop, path: Path): any {
if (!Array.isArray(target)) { if (!Array.isArray(target)) {
throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`); throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
} }
@ -77,7 +98,7 @@ export namespace Checkable {
} }
function checkOptional(target, prop, path): any { function checkOptional(target: any, prop: Prop, path: Path): any {
console.assert(prop.propertyKey); console.assert(prop.propertyKey);
prop.elementChecker(target, prop.elementChecker(target,
prop.elementProp, prop.elementProp,
@ -86,7 +107,7 @@ export namespace Checkable {
} }
function checkValue(target, prop, path): any { function checkValue(target: any, prop: Prop, path: Path): any {
let type = prop.type; let type = prop.type;
if (!type) { if (!type) {
throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`); throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
@ -123,8 +144,8 @@ export namespace Checkable {
} }
export function Class(target) { export function Class(target: any) {
target.checked = (v) => { target.checked = (v: any) => {
return checkValue(v, { return checkValue(v, {
propertyKey: "(root)", propertyKey: "(root)",
type: target, type: target,
@ -135,7 +156,7 @@ export namespace Checkable {
} }
export function Value(type) { export function Value(type: any) {
if (!type) { if (!type) {
throw Error("Type does not exist yet (wrong order of definitions?)"); throw Error("Type does not exist yet (wrong order of definitions?)");
} }
@ -152,7 +173,7 @@ export namespace Checkable {
} }
export function List(type) { export function List(type: any) {
let stub = {}; let stub = {};
type(stub, "(list-element)"); type(stub, "(list-element)");
let elementProp = mkChk(stub).props[0]; let elementProp = mkChk(stub).props[0];
@ -174,7 +195,7 @@ export namespace Checkable {
} }
export function Optional(type) { export function Optional(type: any) {
let stub = {}; let stub = {};
type(stub, "(optional-element)"); type(stub, "(optional-element)");
let elementProp = mkChk(stub).props[0]; let elementProp = mkChk(stub).props[0];
@ -230,7 +251,7 @@ export namespace Checkable {
} }
function mkChk(target) { function mkChk(target: any) {
let chk = target[chkSym]; let chk = target[chkSym];
if (!chk) { if (!chk) {
chk = {props: []}; chk = {props: []};

View File

@ -27,9 +27,10 @@ import {Denomination} from "./types";
import {Offer} from "./wallet"; import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet"; import {CoinWithDenom} from "./wallet";
import {PayCoinInfo} from "./types"; import {PayCoinInfo} from "./types";
type RegistryEntry = {resolve: any; reject: any};
export class CryptoApi { export class CryptoApi {
private nextRpcId: number = 1; private nextRpcId: number = 1;
private rpcRegistry = {}; private rpcRegistry: {[n: number]: RegistryEntry} = {};
private cryptoWorker: Worker; private cryptoWorker: Worker;
@ -52,14 +53,14 @@ export class CryptoApi {
} }
private registerRpcId(resolve, reject): number { private registerRpcId(resolve: any, reject: any): number {
let id = this.nextRpcId++; let id = this.nextRpcId++;
this.rpcRegistry[id] = {resolve, reject}; this.rpcRegistry[id] = {resolve, reject};
return id; return id;
} }
private doRpc<T>(methodName: string, ...args): Promise<T> { private doRpc<T>(methodName: string, ...args: any[]): Promise<T> {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
let msg = { let msg = {
operation: methodName, operation: methodName,

View File

@ -28,6 +28,7 @@ import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet"; import {CoinWithDenom} from "./wallet";
import {CoinPaySig} from "./types"; import {CoinPaySig} from "./types";
import {Denomination} from "./types"; import {Denomination} from "./types";
import {Amount} from "./emscriptif";
export function main(worker: Worker) { export function main(worker: Worker) {
@ -43,7 +44,7 @@ export function main(worker: Worker) {
if (typeof msg.data.operation != "string") { if (typeof msg.data.operation != "string") {
console.error("RPC operation must be string"); console.error("RPC operation must be string");
} }
let f = RpcFunctions[msg.data.operation]; let f = (RpcFunctions as any)[msg.data.operation];
if (!f) { if (!f) {
console.error(`unknown operation: '${msg.data.operation}'`); console.error(`unknown operation: '${msg.data.operation}'`);
return; return;
@ -156,7 +157,7 @@ namespace RpcFunctions {
} }
export function rsaUnblind(sig, bk, pk): string { export function rsaUnblind(sig: string, bk: string, pk: string): string {
let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
native.RsaBlindingKeySecret.fromCrock(bk), native.RsaBlindingKeySecret.fromCrock(bk),
native.RsaPublicKey.fromCrock(pk)); native.RsaPublicKey.fromCrock(pk));
@ -170,11 +171,11 @@ namespace RpcFunctions {
*/ */
export function signDeposit(offer: Offer, export function signDeposit(offer: Offer,
cds: CoinWithDenom[]): PayCoinInfo { cds: CoinWithDenom[]): PayCoinInfo {
let ret = []; let ret: PayCoinInfo = [];
let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
let amountRemaining = new native.Amount(offer.contract.amount); let amountRemaining = new native.Amount(offer.contract.amount);
for (let cd of cds) { for (let cd of cds) {
let coinSpend; let coinSpend: Amount;
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) { if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
break; break;

View File

@ -15,6 +15,7 @@
*/ */
"use strict"; "use strict";
import Dictionary = _.Dictionary;
/** /**
* Declarations and helpers for * Declarations and helpers for
@ -83,27 +84,27 @@ export function openTalerDb(): Promise<IDBDatabase> {
} }
export function exportDb(db): Promise<any> { export function exportDb(db: IDBDatabase): Promise<any> {
let dump = { let dump = {
name: db.name, name: db.name,
version: db.version, version: db.version,
stores: {} stores: {} as Dictionary<any>,
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let tx = db.transaction(db.objectStoreNames); let tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", (e) => { tx.addEventListener("complete", () => {
resolve(dump); resolve(dump);
}); });
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
let name = db.objectStoreNames[i]; let name = db.objectStoreNames[i];
let storeDump = {}; let storeDump = {} as Dictionary<any>;
dump.stores[name] = storeDump; dump.stores[name] = storeDump;
let store = tx.objectStore(name) let store = tx.objectStore(name)
.openCursor() .openCursor()
.addEventListener("success", (e) => { .addEventListener("success", (e: Event) => {
let cursor = e.target.result; let cursor = (e.target as any).result;
if (cursor) { if (cursor) {
storeDump[cursor.key] = cursor.value; storeDump[cursor.key] = cursor.value;
cursor.continue(); cursor.continue();

View File

@ -36,11 +36,11 @@ const GNUNET_SYSERR = -1;
let Module = EmscWrapper.Module; let Module = EmscWrapper.Module;
let getEmsc: EmscWrapper.EmscFunGen = (...args) => Module.cwrap.apply(null, let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(null,
args); args);
var emsc = { var emsc = {
free: (ptr) => Module._free(ptr), free: (ptr: number) => Module._free(ptr),
get_value: getEmsc('TALER_WR_get_value', get_value: getEmsc('TALER_WR_get_value',
'number', 'number',
['number']), ['number']),
@ -164,13 +164,12 @@ enum RandomQuality {
abstract class ArenaObject { abstract class ArenaObject {
private _nativePtr: number; protected _nativePtr: number | undefined = undefined;
arena: Arena; arena: Arena;
abstract destroy(): void; abstract destroy(): void;
constructor(arena?: Arena) { constructor(arena?: Arena) {
this.nativePtr = null;
if (!arena) { if (!arena) {
if (arenaStack.length == 0) { if (arenaStack.length == 0) {
throw Error("No arena available") throw Error("No arena available")
@ -192,14 +191,14 @@ abstract class ArenaObject {
} }
free() { free() {
if (this.nativePtr !== undefined) { if (this.nativePtr) {
emsc.free(this.nativePtr); emsc.free(this.nativePtr);
this.nativePtr = undefined; this._nativePtr = undefined;
} }
} }
alloc(size: number) { alloc(size: number) {
if (this.nativePtr !== undefined) { if (this._nativePtr !== undefined) {
throw Error("Double allocation"); throw Error("Double allocation");
} }
this.nativePtr = emscAlloc.malloc(size); this.nativePtr = emscAlloc.malloc(size);
@ -212,21 +211,22 @@ abstract class ArenaObject {
this._nativePtr = n; this._nativePtr = n;
} }
set nativePtr(v) { set nativePtr(v: number) {
this.setNative(v); this.setNative(v);
} }
get nativePtr() { get nativePtr() {
return this.getNative(); return this.getNative();
} }
} }
interface Arena { interface Arena {
put(obj: ArenaObject): void; put(obj: ArenaObject): void;
destroy(): void; destroy(): void;
} }
class DefaultArena implements Arena { class DefaultArena implements Arena {
heap: Array<ArenaObject>; heap: Array<ArenaObject>;
@ -234,7 +234,7 @@ class DefaultArena implements Arena {
this.heap = []; this.heap = [];
} }
put(obj) { put(obj: ArenaObject) {
this.heap.push(obj); this.heap.push(obj);
} }
@ -269,7 +269,7 @@ class SyncArena extends DefaultArena {
super(); super();
} }
pub(obj) { pub(obj: ArenaObject) {
super.put(obj); super.put(obj);
if (!this.isScheduled) { if (!this.isScheduled) {
this.schedule(); this.schedule();
@ -308,14 +308,12 @@ export class Amount extends ArenaObject {
} }
destroy() { destroy() {
if (this.nativePtr != 0) { super.free();
emsc.free(this.nativePtr);
}
} }
static getZero(currency: string, a?: Arena): Amount { static getZero(currency: string, a?: Arena): Amount {
let am = new Amount(null, a); let am = new Amount(undefined, a);
let r = emsc.amount_get_zero(currency, am.getNative()); let r = emsc.amount_get_zero(currency, am.getNative());
if (r != GNUNET_OK) { if (r != GNUNET_OK) {
throw Error("invalid currency"); throw Error("invalid currency");
@ -442,7 +440,8 @@ abstract class PackedArenaObject extends ArenaObject {
} }
alloc() { alloc() {
if (this.nativePtr === null) { // FIXME: should the client be allowed to call alloc multiple times?
if (!this._nativePtr) {
this.nativePtr = emscAlloc.malloc(this.size()); this.nativePtr = emscAlloc.malloc(this.size());
} }
} }
@ -466,7 +465,7 @@ abstract class PackedArenaObject extends ArenaObject {
b = (b + 256) % 256; b = (b + 256) % 256;
bytes.push("0".concat(b.toString(16)).slice(-2)); bytes.push("0".concat(b.toString(16)).slice(-2));
} }
let lines = []; let lines: string[] = [];
for (let i = 0; i < bytes.length; i += 8) { for (let i = 0; i < bytes.length; i += 8) {
lines.push(bytes.slice(i, i + 8).join(",")); lines.push(bytes.slice(i, i + 8).join(","));
} }
@ -482,7 +481,7 @@ export class AmountNbo extends PackedArenaObject {
toJson(): any { toJson(): any {
let a = new DefaultArena(); let a = new DefaultArena();
let am = new Amount(null, a); let am = new Amount(undefined, a);
am.fromNbo(this); am.fromNbo(this);
let json = am.toJson(); let json = am.toJson();
a.destroy(); a.destroy();
@ -508,7 +507,7 @@ export class EddsaPrivateKey extends PackedArenaObject {
return obj; return obj;
} }
static fromCrock: (string) => EddsaPrivateKey; static fromCrock: (s: string) => EddsaPrivateKey;
} }
mixinStatic(EddsaPrivateKey, fromCrock); mixinStatic(EddsaPrivateKey, fromCrock);
@ -521,7 +520,7 @@ function fromCrock(s: string) {
} }
function mixin(obj, method, name?: string) { function mixin(obj: any, method: any, name?: string) {
if (!name) { if (!name) {
name = method.name; name = method.name;
} }
@ -532,7 +531,7 @@ function mixin(obj, method, name?: string) {
} }
function mixinStatic(obj, method, name?: string) { function mixinStatic(obj: any, method: any, name?: string) {
if (!name) { if (!name) {
name = method.name; name = method.name;
} }
@ -595,7 +594,7 @@ export class RsaBlindingKeySecret extends PackedArenaObject {
return o; return o;
} }
static fromCrock: (string) => RsaBlindingKeySecret; static fromCrock: (s: string) => RsaBlindingKeySecret;
} }
mixinStatic(RsaBlindingKeySecret, fromCrock); mixinStatic(RsaBlindingKeySecret, fromCrock);
@ -622,9 +621,9 @@ export class ByteArray extends PackedArenaObject {
return this.allocatedSize; return this.allocatedSize;
} }
constructor(desiredSize: number, init: number, a?: Arena) { constructor(desiredSize: number, init?: number, a?: Arena) {
super(a); super(a);
if (init === undefined || init === null) { if (init === undefined) {
this.nativePtr = emscAlloc.malloc(desiredSize); this.nativePtr = emscAlloc.malloc(desiredSize);
} else { } else {
this.nativePtr = init; this.nativePtr = init;
@ -642,7 +641,7 @@ export class ByteArray extends PackedArenaObject {
let hstr = emscAlloc.malloc(s.length + 1); let hstr = emscAlloc.malloc(s.length + 1);
Module.writeStringToMemory(s, hstr); Module.writeStringToMemory(s, hstr);
let decodedLen = Math.floor((s.length * 5) / 8); let decodedLen = Math.floor((s.length * 5) / 8);
let ba = new ByteArray(decodedLen, null, a); let ba = new ByteArray(decodedLen, undefined, a);
let res = emsc.string_to_data(hstr, s.length, ba.nativePtr, decodedLen); let res = emsc.string_to_data(hstr, s.length, ba.nativePtr, decodedLen);
emsc.free(hstr); emsc.free(hstr);
if (res != GNUNET_OK) { if (res != GNUNET_OK) {
@ -777,7 +776,7 @@ export class AbsoluteTimeNbo extends PackedArenaObject {
x.alloc(); x.alloc();
let r = /Date\(([0-9]+)\)/; let r = /Date\(([0-9]+)\)/;
let m = r.exec(s); let m = r.exec(s);
if (m.length != 2) { if (!m || m.length != 2) {
throw Error(); throw Error();
} }
let n = parseInt(m[1]) * 1000000; let n = parseInt(m[1]) * 1000000;
@ -899,11 +898,11 @@ interface Encodeable {
encode(arena?: Arena): ByteArray; encode(arena?: Arena): ByteArray;
} }
function makeEncode(encodeFn) { function makeEncode(encodeFn: any) {
function encode(arena?: Arena) { function encode(arena?: Arena) {
let ptr = emscAlloc.malloc(PTR_SIZE); let ptr = emscAlloc.malloc(PTR_SIZE);
let len = encodeFn(this.getNative(), ptr); let len = encodeFn(this.getNative(), ptr);
let res = new ByteArray(len, null, arena); let res = new ByteArray(len, undefined, arena);
res.setNative(Module.getValue(ptr, '*')); res.setNative(Module.getValue(ptr, '*'));
emsc.free(ptr); emsc.free(ptr);
return res; return res;

View File

@ -24,7 +24,7 @@
import {AmountJson} from "./types"; import {AmountJson} from "./types";
export function substituteFulfillmentUrl(url: string, vars) { export function substituteFulfillmentUrl(url: string, vars: any) {
url = url.replace("${H_contract}", vars.H_contract); url = url.replace("${H_contract}", vars.H_contract);
url = url.replace("${$}", "$"); url = url.replace("${$}", "$");
return url; return url;
@ -42,7 +42,7 @@ export function amountToPretty(amount: AmountJson): string {
* *
* See http://api.taler.net/wallet.html#general * See http://api.taler.net/wallet.html#general
*/ */
export function canonicalizeBaseUrl(url) { export function canonicalizeBaseUrl(url: string) {
let x = new URI(url); let x = new URI(url);
if (!x.protocol()) { if (!x.protocol()) {
x.protocol("https"); x.protocol("https");
@ -54,10 +54,10 @@ export function canonicalizeBaseUrl(url) {
} }
export function parsePrettyAmount(pretty: string): AmountJson { export function parsePrettyAmount(pretty: string): AmountJson|undefined {
const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty); const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
if (!res) { if (!res) {
return null; return undefined;
} }
return { return {
value: parseInt(res[1], 10), value: parseInt(res[1], 10),

View File

@ -66,19 +66,19 @@ export class BrowserHttpLib {
} }
postJson(url: string|uri.URI, body) { postJson(url: string|uri.URI, body: any) {
return this.req("post", url, {req: JSON.stringify(body)}); return this.req("post", url, {req: JSON.stringify(body)});
} }
postForm(url: string|uri.URI, form) { postForm(url: string|uri.URI, form: any) {
return this.req("post", url, {req: form}); return this.req("post", url, {req: form});
} }
} }
export class RequestException { export class RequestException {
constructor(detail) { constructor(detail: any) {
} }
} }

View File

@ -24,7 +24,7 @@
"use strict"; "use strict";
export function Query(db) { export function Query(db: IDBDatabase) {
return new QueryRoot(db); return new QueryRoot(db);
} }
@ -36,24 +36,27 @@ export interface QueryStream<T> {
indexJoin<S>(storeName: string, indexJoin<S>(storeName: string,
indexName: string, indexName: string,
keyFn: (obj: any) => any): QueryStream<[T,S]>; keyFn: (obj: any) => any): QueryStream<[T,S]>;
filter(f: (any) => boolean): QueryStream<T>; filter(f: (x: any) => boolean): QueryStream<T>;
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>; reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
flatMap(f: (T) => T[]): QueryStream<T>; flatMap(f: (x: T) => T[]): QueryStream<T>;
} }
/** /**
* Get an unresolved promise together with its extracted resolve / reject * Get an unresolved promise together with its extracted resolve / reject
* function. * function.
*
* @returns {{resolve: any, reject: any, promise: Promise<T>}}
*/ */
function openPromise<T>() { function openPromise<T>() {
let resolve, reject; let resolve: ((value?: T | PromiseLike<T>) => void) | null = null;
let reject: ((reason?: any) => void) | null = null;
const promise = new Promise<T>((res, rej) => { const promise = new Promise<T>((res, rej) => {
resolve = res; resolve = res;
reject = rej; reject = rej;
}); });
if (!(resolve && reject)) {
// Never happens, unless JS implementation is broken
throw Error();
}
return {resolve, reject, promise}; return {resolve, reject, promise};
} }
@ -61,7 +64,7 @@ function openPromise<T>() {
abstract class QueryStreamBase<T> implements QueryStream<T> { abstract class QueryStreamBase<T> implements QueryStream<T> {
abstract subscribe(f: (isDone: boolean, abstract subscribe(f: (isDone: boolean,
value: any, value: any,
tx: IDBTransaction) => void); tx: IDBTransaction) => void): void;
root: QueryRoot; root: QueryRoot;
@ -69,30 +72,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> {
this.root = root; this.root = root;
} }
flatMap(f: (T) => T[]): QueryStream<T> { flatMap(f: (x: T) => T[]): QueryStream<T> {
return new QueryStreamFlatMap(this, f); return new QueryStreamFlatMap(this, f);
} }
indexJoin<S>(storeName: string, indexJoin<S>(storeName: string,
indexName: string, indexName: string,
key: any): QueryStream<[T,S]> { key: any): QueryStream<[T,S]> {
this.root.addWork(null, storeName, false); this.root.addStoreAccess(storeName, false);
return new QueryStreamIndexJoin(this, storeName, indexName, key); return new QueryStreamIndexJoin(this, storeName, indexName, key);
} }
filter(f: (any) => boolean): QueryStream<T> { filter(f: (x: any) => boolean): QueryStream<T> {
return new QueryStreamFilter(this, f); return new QueryStreamFilter(this, f);
} }
reduce(f, acc?): Promise<any> { reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
let leakedResolve; let {resolve, promise} = openPromise();
let p = new Promise((resolve, reject) => { let acc = init;
leakedResolve = resolve;
});
this.subscribe((isDone, value) => { this.subscribe((isDone, value) => {
if (isDone) { if (isDone) {
leakedResolve(acc); resolve(acc);
return; return;
} }
acc = f(value, acc); acc = f(value, acc);
@ -100,22 +101,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> {
return Promise.resolve() return Promise.resolve()
.then(() => this.root.finish()) .then(() => this.root.finish())
.then(() => p); .then(() => promise);
} }
} }
type FilterFn = (e: any) => boolean;
type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
interface FlatMapFn<T> {
(v: T): T[];
}
class QueryStreamFilter<T> extends QueryStreamBase<T> { class QueryStreamFilter<T> extends QueryStreamBase<T> {
s: QueryStreamBase<T>; s: QueryStreamBase<T>;
filterFn; filterFn: FilterFn;
constructor(s: QueryStreamBase<T>, filterFn) { constructor(s: QueryStreamBase<T>, filterFn: FilterFn) {
super(s.root); super(s.root);
this.s = s; this.s = s;
this.filterFn = filterFn; this.filterFn = filterFn;
} }
subscribe(f) { subscribe(f: SubscribeFn) {
this.s.subscribe((isDone, value, tx) => { this.s.subscribe((isDone, value, tx) => {
if (isDone) { if (isDone) {
f(true, undefined, tx); f(true, undefined, tx);
@ -131,15 +138,15 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> {
class QueryStreamFlatMap<T> extends QueryStreamBase<T> { class QueryStreamFlatMap<T> extends QueryStreamBase<T> {
s: QueryStreamBase<T>; s: QueryStreamBase<T>;
flatMapFn; flatMapFn: (v: T) => T[];
constructor(s: QueryStreamBase<T>, flatMapFn) { constructor(s: QueryStreamBase<T>, flatMapFn: (v: T) => T[]) {
super(s.root); super(s.root);
this.s = s; this.s = s;
this.flatMap = flatMapFn; this.flatMapFn = flatMapFn;
} }
subscribe(f) { subscribe(f: SubscribeFn) {
this.s.subscribe((isDone, value, tx) => { this.s.subscribe((isDone, value, tx) => {
if (isDone) { if (isDone) {
f(true, undefined, tx); f(true, undefined, tx);
@ -154,13 +161,13 @@ class QueryStreamFlatMap<T> extends QueryStreamBase<T> {
} }
class QueryStreamIndexJoin<T> extends QueryStreamBase<T> { class QueryStreamIndexJoin<T,S> extends QueryStreamBase<[T, S]> {
s: QueryStreamBase<T>; s: QueryStreamBase<T>;
storeName; storeName: string;
key; key: any;
indexName; indexName: string;
constructor(s, storeName: string, indexName: string, key: any) { constructor(s: QueryStreamBase<T>, storeName: string, indexName: string, key: any) {
super(s.root); super(s.root);
this.s = s; this.s = s;
this.storeName = storeName; this.storeName = storeName;
@ -168,7 +175,7 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> {
this.indexName = indexName; this.indexName = indexName;
} }
subscribe(f) { subscribe(f: SubscribeFn) {
this.s.subscribe((isDone, value, tx) => { this.s.subscribe((isDone, value, tx) => {
if (isDone) { if (isDone) {
f(true, undefined, tx); f(true, undefined, tx);
@ -192,31 +199,31 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> {
class IterQueryStream<T> extends QueryStreamBase<T> { class IterQueryStream<T> extends QueryStreamBase<T> {
private storeName; private storeName: string;
private options; private options: any;
private subscribers; private subscribers: SubscribeFn[];
constructor(qr, storeName, options) { constructor(qr: QueryRoot, storeName: string, options: any) {
super(qr); super(qr);
this.options = options; this.options = options;
this.storeName = storeName; this.storeName = storeName;
this.subscribers = []; this.subscribers = [];
let doIt = (tx) => { let doIt = (tx: IDBTransaction) => {
const {indexName = void 0, only = void 0} = this.options; const {indexName = void 0, only = void 0} = this.options;
let s; let s: any;
if (indexName !== void 0) { if (indexName !== void 0) {
s = tx.objectStore(this.storeName) s = tx.objectStore(this.storeName)
.index(this.options.indexName); .index(this.options.indexName);
} else { } else {
s = tx.objectStore(this.storeName); s = tx.objectStore(this.storeName);
} }
let kr = undefined; let kr: IDBKeyRange|undefined = undefined;
if (only !== void 0) { if (only !== undefined) {
kr = IDBKeyRange.only(this.options.only); kr = IDBKeyRange.only(this.options.only);
} }
let req = s.openCursor(kr); let req = s.openCursor(kr);
req.onsuccess = (e) => { req.onsuccess = () => {
let cursor: IDBCursorWithValue = req.result; let cursor: IDBCursorWithValue = req.result;
if (cursor) { if (cursor) {
for (let f of this.subscribers) { for (let f of this.subscribers) {
@ -231,32 +238,33 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
} }
}; };
this.root.addWork(doIt, null, false); this.root.addWork(doIt);
} }
subscribe(f) { subscribe(f: SubscribeFn) {
this.subscribers.push(f); this.subscribers.push(f);
} }
} }
class QueryRoot { class QueryRoot {
private work = []; private work: ((t: IDBTransaction) => void)[] = [];
private db: IDBDatabase; private db: IDBDatabase;
private stores = new Set(); private stores = new Set();
private kickoffPromise; private kickoffPromise: Promise<void>;
/** /**
* Some operations is a write operation, * Some operations is a write operation,
* and we need to do a "readwrite" transaction/ * and we need to do a "readwrite" transaction/
*/ */
private hasWrite; private hasWrite: boolean;
constructor(db) { constructor(db: IDBDatabase) {
this.db = db; this.db = db;
} }
iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> { iter<T>(storeName: string,
{only = <string|undefined>undefined, indexName = <string|undefined>undefined} = {}): QueryStream<T> {
this.stores.add(storeName); this.stores.add(storeName);
return new IterQueryStream(this, storeName, {only, indexName}); return new IterQueryStream(this, storeName, {only, indexName});
} }
@ -266,7 +274,7 @@ class QueryRoot {
* Overrides if an existing object with the same key exists * Overrides if an existing object with the same key exists
* in the store. * in the store.
*/ */
put(storeName, val): QueryRoot { put(storeName: string, val: any): QueryRoot {
let doPut = (tx: IDBTransaction) => { let doPut = (tx: IDBTransaction) => {
tx.objectStore(storeName).put(val); tx.objectStore(storeName).put(val);
}; };
@ -280,7 +288,7 @@ class QueryRoot {
* Fails if the object's key is already present * Fails if the object's key is already present
* in the object store. * in the object store.
*/ */
putAll(storeName, iterable): QueryRoot { putAll(storeName: string, iterable: any[]): QueryRoot {
const doPutAll = (tx: IDBTransaction) => { const doPutAll = (tx: IDBTransaction) => {
for (const obj of iterable) { for (const obj of iterable) {
tx.objectStore(storeName).put(obj); tx.objectStore(storeName).put(obj);
@ -295,7 +303,7 @@ class QueryRoot {
* Fails if the object's key is already present * Fails if the object's key is already present
* in the object store. * in the object store.
*/ */
add(storeName, val): QueryRoot { add(storeName: string, val: any): QueryRoot {
const doAdd = (tx: IDBTransaction) => { const doAdd = (tx: IDBTransaction) => {
tx.objectStore(storeName).add(val); tx.objectStore(storeName).add(val);
}; };
@ -306,16 +314,16 @@ class QueryRoot {
/** /**
* Get one object from a store by its key. * Get one object from a store by its key.
*/ */
get(storeName, key): Promise<any> { get(storeName: any, key: any): Promise<any> {
if (key === void 0) { if (key === void 0) {
throw Error("key must not be undefined"); throw Error("key must not be undefined");
} }
const {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
const doGet = (tx) => { const doGet = (tx: IDBTransaction) => {
const req = tx.objectStore(storeName).get(key); const req = tx.objectStore(storeName).get(key);
req.onsuccess = (r) => { req.onsuccess = () => {
resolve(req.result); resolve(req.result);
}; };
}; };
@ -329,16 +337,16 @@ class QueryRoot {
/** /**
* Get one object from a store by its key. * Get one object from a store by its key.
*/ */
getIndexed(storeName, indexName, key): Promise<any> { getIndexed(storeName: string, indexName: string, key: any): Promise<any> {
if (key === void 0) { if (key === void 0) {
throw Error("key must not be undefined"); throw Error("key must not be undefined");
} }
const {resolve, promise} = openPromise(); const {resolve, promise} = openPromise();
const doGetIndexed = (tx) => { const doGetIndexed = (tx: IDBTransaction) => {
const req = tx.objectStore(storeName).index(indexName).get(key); const req = tx.objectStore(storeName).index(indexName).get(key);
req.onsuccess = (r) => { req.onsuccess = () => {
resolve(req.result); resolve(req.result);
}; };
}; };
@ -356,7 +364,7 @@ class QueryRoot {
if (this.kickoffPromise) { if (this.kickoffPromise) {
return this.kickoffPromise; return this.kickoffPromise;
} }
this.kickoffPromise = new Promise((resolve, reject) => { this.kickoffPromise = new Promise<void>((resolve, reject) => {
if (this.work.length == 0) { if (this.work.length == 0) {
resolve(); resolve();
return; return;
@ -376,8 +384,8 @@ class QueryRoot {
/** /**
* Delete an object by from the given object store. * Delete an object by from the given object store.
*/ */
delete(storeName: string, key): QueryRoot { delete(storeName: string, key: any): QueryRoot {
const doDelete = (tx) => { const doDelete = (tx: IDBTransaction) => {
tx.objectStore(storeName).delete(key); tx.objectStore(storeName).delete(key);
}; };
this.addWork(doDelete, storeName, true); this.addWork(doDelete, storeName, true);
@ -387,17 +395,21 @@ class QueryRoot {
/** /**
* Low-level function to add a task to the internal work queue. * Low-level function to add a task to the internal work queue.
*/ */
addWork(workFn: (IDBTransaction) => void, addWork(workFn: (t: IDBTransaction) => void,
storeName: string, storeName?: string,
isWrite: boolean) { isWrite?: boolean) {
this.work.push(workFn);
if (storeName) {
this.addStoreAccess(storeName, isWrite);
}
}
addStoreAccess(storeName: string, isWrite?: boolean) {
if (storeName) { if (storeName) {
this.stores.add(storeName); this.stores.add(storeName);
} }
if (isWrite) { if (isWrite) {
this.hasWrite = true; this.hasWrite = true;
} }
if (workFn) {
this.work.push(workFn);
}
} }
} }

View File

@ -392,5 +392,5 @@ export interface CheckRepurchaseResult {
export interface Notifier { export interface Notifier {
notify(); notify(): void;
} }

View File

@ -154,12 +154,12 @@ interface Transaction {
export interface Badge { export interface Badge {
setText(s: string): void; setText(s: string): void;
setColor(c: string): void; setColor(c: string): void;
startBusy(); startBusy(): void;
stopBusy(); stopBusy(): void;
} }
function deepEquals(x, y) { function deepEquals(x: any, y: any): boolean {
if (x === y) { if (x === y) {
return true; return true;
} }
@ -179,7 +179,7 @@ function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
} }
function getTalerStampSec(stamp: string): number { function getTalerStampSec(stamp: string): number|null {
const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/); const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
if (!m) { if (!m) {
return null; return null;
@ -188,7 +188,7 @@ function getTalerStampSec(stamp: string): number {
} }
function setTimeout(f, t) { function setTimeout(f: any, t: number) {
return chrome.extension.getBackgroundPage().setTimeout(f, t); return chrome.extension.getBackgroundPage().setTimeout(f, t);
} }
@ -211,13 +211,13 @@ interface HttpRequestLibrary {
get(url: string|uri.URI): Promise<HttpResponse>; get(url: string|uri.URI): Promise<HttpResponse>;
postJson(url: string|uri.URI, body): Promise<HttpResponse>; postJson(url: string|uri.URI, body: any): Promise<HttpResponse>;
postForm(url: string|uri.URI, form): Promise<HttpResponse>; postForm(url: string|uri.URI, form: any): Promise<HttpResponse>;
} }
function copy(o) { function copy(o: any) {
return JSON.parse(JSON.stringify(o)); return JSON.parse(JSON.stringify(o));
} }
@ -240,11 +240,18 @@ interface KeyUpdateInfo {
function getWithdrawDenomList(amountAvailable: AmountJson, function getWithdrawDenomList(amountAvailable: AmountJson,
denoms: Denomination[]): Denomination[] { denoms: Denomination[]): Denomination[] {
let remaining = Amounts.copy(amountAvailable); let remaining = Amounts.copy(amountAvailable);
let ds: Denomination[] = []; const ds: Denomination[] = [];
console.log("available denoms");
console.log(denoms);
denoms = denoms.filter(isWithdrawableDenom); denoms = denoms.filter(isWithdrawableDenom);
denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
console.log("withdrawable denoms");
console.log(denoms);
// This is an arbitrary number of coins // This is an arbitrary number of coins
// we can withdraw in one go. It's not clear if this limit // we can withdraw in one go. It's not clear if this limit
// is useful ... // is useful ...
@ -355,7 +362,7 @@ export class Wallet {
let x: number; let x: number;
function storeExchangeCoin(mc, url) { function storeExchangeCoin(mc: any, url: string) {
let exchange: IExchangeInfo = mc[0]; let exchange: IExchangeInfo = mc[0];
console.log("got coin for exchange", url); console.log("got coin for exchange", url);
let coin: Coin = mc[1]; let coin: Coin = mc[1];
@ -366,18 +373,16 @@ export class Wallet {
exchange.baseUrl); exchange.baseUrl);
return; return;
} }
let cd = { let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub);
coin: coin, if (!denom) {
denom: exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub)
};
if (!cd.denom) {
console.warn("denom not found (database inconsistent)"); console.warn("denom not found (database inconsistent)");
return; return;
} }
if (cd.denom.value.currency !== paymentAmount.currency) { if (denom.value.currency !== paymentAmount.currency) {
console.warn("same pubkey for different currencies"); console.warn("same pubkey for different currencies");
return; return;
} }
let cd = {coin, denom};
let x = m[url]; let x = m[url];
if (!x) { if (!x) {
m[url] = [cd]; m[url] = [cd];
@ -464,7 +469,7 @@ export class Wallet {
private recordConfirmPay(offer: Offer, private recordConfirmPay(offer: Offer,
payCoinInfo: PayCoinInfo, payCoinInfo: PayCoinInfo,
chosenExchange: string): Promise<void> { chosenExchange: string): Promise<void> {
let payReq = {}; let payReq: any = {};
payReq["amount"] = offer.contract.amount; payReq["amount"] = offer.contract.amount;
payReq["coins"] = payCoinInfo.map((x) => x.sig); payReq["coins"] = payCoinInfo.map((x) => x.sig);
payReq["H_contract"] = offer.H_contract; payReq["H_contract"] = offer.H_contract;
@ -581,7 +586,7 @@ export class Wallet {
* Retrieve all necessary information for looking up the contract * Retrieve all necessary information for looking up the contract
* with the given hash. * with the given hash.
*/ */
executePayment(H_contract): Promise<any> { executePayment(H_contract: string): Promise<any> {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
return Query(this.db) return Query(this.db)
.get("transactions", H_contract) .get("transactions", H_contract)
@ -607,7 +612,7 @@ export class Wallet {
* First fetch information requred to withdraw from the reserve, * First fetch information requred to withdraw from the reserve,
* then deplete the reserve, withdrawing coins until it is empty. * then deplete the reserve, withdrawing coins until it is empty.
*/ */
private processReserve(reserveRecord): void { private processReserve(reserveRecord: any): void {
let retryDelayMs = 100; let retryDelayMs = 100;
const opId = "reserve-" + reserveRecord.reserve_pub; const opId = "reserve-" + reserveRecord.reserve_pub;
this.startOperation(opId); this.startOperation(opId);
@ -637,7 +642,7 @@ export class Wallet {
} }
private processPreCoin(preCoin, retryDelayMs = 100): void { private processPreCoin(preCoin: any, retryDelayMs = 100): void {
this.withdrawExecute(preCoin) this.withdrawExecute(preCoin)
.then((c) => this.storeCoin(c)) .then((c) => this.storeCoin(c))
.catch((e) => { .catch((e) => {
@ -803,7 +808,7 @@ export class Wallet {
/** /**
* Withdraw coins from a reserve until it is empty. * Withdraw coins from a reserve until it is empty.
*/ */
private depleteReserve(reserve, exchange: IExchangeInfo): Promise<void> { private depleteReserve(reserve: any, exchange: IExchangeInfo): Promise<void> {
let denomsAvailable: Denomination[] = copy(exchange.active_denoms); let denomsAvailable: Denomination[] = copy(exchange.active_denoms);
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount, let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
denomsAvailable); denomsAvailable);
@ -912,7 +917,7 @@ export class Wallet {
* Optionally link the reserve entry to the new or existing * Optionally link the reserve entry to the new or existing
* exchange entry in then DB. * exchange entry in then DB.
*/ */
updateExchangeFromUrl(baseUrl): Promise<IExchangeInfo> { updateExchangeFromUrl(baseUrl: string): Promise<IExchangeInfo> {
baseUrl = canonicalizeBaseUrl(baseUrl); baseUrl = canonicalizeBaseUrl(baseUrl);
let reqUrl = URI("keys").absoluteTo(baseUrl); let reqUrl = URI("keys").absoluteTo(baseUrl);
return this.http.get(reqUrl).then((resp) => { return this.http.get(reqUrl).then((resp) => {
@ -927,8 +932,8 @@ export class Wallet {
private updateExchangeFromJson(baseUrl: string, private updateExchangeFromJson(baseUrl: string,
exchangeKeysJson: KeysJson): Promise<IExchangeInfo> { exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
let updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date); const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
if (!updateTimeSec) { if (updateTimeSec === null) {
throw Error("invalid update time"); throw Error("invalid update time");
} }
@ -947,7 +952,7 @@ export class Wallet {
console.log("making fresh exchange"); console.log("making fresh exchange");
} else { } else {
if (updateTimeSec < r.last_update_time) { if (updateTimeSec < r.last_update_time) {
console.log("outdated /keys, not updating") console.log("outdated /keys, not updating");
return Promise.resolve(r); return Promise.resolve(r);
} }
exchangeInfo = r; exchangeInfo = r;
@ -966,9 +971,9 @@ export class Wallet {
{indexName: "exchangeBaseUrl", only: baseUrl}) {indexName: "exchangeBaseUrl", only: baseUrl})
.reduce((coin: Coin, suspendedCoins: Coin[]) => { .reduce((coin: Coin, suspendedCoins: Coin[]) => {
if (!updatedExchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) { if (!updatedExchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
return [].concat(suspendedCoins, [coin]); return Array.prototype.concat(suspendedCoins, [coin]);
} }
return [].concat(suspendedCoins); return Array.prototype.concat(suspendedCoins);
}, []) }, [])
.then((suspendedCoins: Coin[]) => { .then((suspendedCoins: Coin[]) => {
let q = Query(this.db); let q = Query(this.db);
@ -999,8 +1004,8 @@ export class Wallet {
let found = false; let found = false;
for (let oldDenom of exchangeInfo.all_denoms) { for (let oldDenom of exchangeInfo.all_denoms) {
if (oldDenom.denom_pub === newDenom.denom_pub) { if (oldDenom.denom_pub === newDenom.denom_pub) {
let a = Object.assign({}, oldDenom); let a: any = Object.assign({}, oldDenom);
let b = Object.assign({}, newDenom); let b: any = Object.assign({}, newDenom);
// pub hash is only there for convenience in the wallet // pub hash is only there for convenience in the wallet
delete a["pub_hash"]; delete a["pub_hash"];
delete b["pub_hash"]; delete b["pub_hash"];
@ -1048,7 +1053,7 @@ export class Wallet {
* that is currenctly available for spending in the wallet. * that is currenctly available for spending in the wallet.
*/ */
getBalances(): Promise<any> { getBalances(): Promise<any> {
function collectBalances(c: Coin, byCurrency) { function collectBalances(c: Coin, byCurrency: any) {
if (c.suspended) { if (c.suspended) {
return byCurrency; return byCurrency;
} }
@ -1074,7 +1079,7 @@ export class Wallet {
* Retrive the full event history for this wallet. * Retrive the full event history for this wallet.
*/ */
getHistory(): Promise<any> { getHistory(): Promise<any> {
function collect(x, acc) { function collect(x: any, acc: any) {
acc.push(x); acc.push(x);
return acc; return acc;
} }
@ -1099,7 +1104,7 @@ export class Wallet {
[contract.merchant_pub, contract.repurchase_correlation_id]) [contract.merchant_pub, contract.repurchase_correlation_id])
.then((result: Transaction) => { .then((result: Transaction) => {
console.log("db result", result); console.log("db result", result);
let isRepurchase; let isRepurchase: boolean;
if (result) { if (result) {
console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id); console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id);
return { return {

View File

@ -15,7 +15,13 @@
*/ */
import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from "./wallet"; import {
Wallet,
Offer,
Badge,
ConfirmReserveRequest,
CreateReserveRequest
} from "./wallet";
import {deleteDb, exportDb, openTalerDb} from "./db"; import {deleteDb, exportDb, openTalerDb} from "./db";
import {BrowserHttpLib} from "./http"; import {BrowserHttpLib} from "./http";
import {Checkable} from "./checkable"; import {Checkable} from "./checkable";
@ -48,11 +54,17 @@ function makeHandlers(db: IDBDatabase,
return exportDb(db); return exportDb(db);
}, },
["ping"]: function(detail, sender) { ["ping"]: function(detail, sender) {
return Promise.resolve({}); if (!sender || !sender.tab || !sender.tab.id) {
return Promise.resolve();
}
let id: number = sender.tab.id;
let info: any = <any>paymentRequestCookies[id];
delete paymentRequestCookies[id];
return Promise.resolve(info);
}, },
["reset"]: function(detail, sender) { ["reset"]: function(detail, sender) {
if (db) { if (db) {
let tx = db.transaction(db.objectStoreNames, 'readwrite'); let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite');
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
tx.objectStore(db.objectStoreNames[i]).clear(); tx.objectStore(db.objectStoreNames[i]).clear();
} }
@ -81,7 +93,7 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmReserve(req); return wallet.confirmReserve(req);
}, },
["confirm-pay"]: function(detail, sender) { ["confirm-pay"]: function(detail, sender) {
let offer; let offer: Offer;
try { try {
offer = Offer.checked(detail.offer); offer = Offer.checked(detail.offer);
} catch (e) { } catch (e) {
@ -100,7 +112,7 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmPay(offer); return wallet.confirmPay(offer);
}, },
["check-pay"]: function(detail, sender) { ["check-pay"]: function(detail, sender) {
let offer; let offer: Offer;
try { try {
offer = Offer.checked(detail.offer); offer = Offer.checked(detail.offer);
} catch (e) { } catch (e) {
@ -173,14 +185,14 @@ class ChromeBadge implements Badge {
} }
function dispatch(handlers, req, sender, sendResponse) { function dispatch(handlers: any, req: any, sender: any, sendResponse: any) {
if (req.type in handlers) { if (req.type in handlers) {
Promise Promise
.resolve() .resolve()
.then(() => { .then(() => {
const p = handlers[req.type](req.detail, sender); const p = handlers[req.type](req.detail, sender);
return p.then((r) => { return p.then((r: any) => {
sendResponse(r); sendResponse(r);
}) })
}) })
@ -231,12 +243,58 @@ class ChromeNotifier implements Notifier {
} }
/**
* Mapping from tab ID to payment information (if any).
*/
let paymentRequestCookies: {[n: number]: any} = {};
function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[],
url: string, tabId: number): any {
const headers: {[s: string]: string} = {};
for (let kv of headerList) {
if (kv.value) {
headers[kv.name.toLowerCase()] = kv.value;
}
}
const contractUrl = headers["x-taler-contract-url"];
if (contractUrl !== undefined) {
paymentRequestCookies[tabId] = {type: "fetch", contractUrl};
return;
}
const contractHash = headers["x-taler-contract-hash"];
if (contractHash !== undefined) {
const payUrl = headers["x-taler-pay-url"];
if (payUrl === undefined) {
console.log("malformed 402, X-Taler-Pay-Url missing");
return;
}
// Offer URL is optional
const offerUrl = headers["x-taler-offer-url"];
paymentRequestCookies[tabId] = {
type: "execute",
offerUrl,
payUrl,
contractHash
};
return;
}
// looks like it's not a taler request, it might be
// for a different payment system (or the shop is buggy)
console.log("ignoring non-taler 402 response");
}
export function wxMain() { export function wxMain() {
chrome.browserAction.setBadgeText({text: ""}); chrome.browserAction.setBadgeText({text: ""});
chrome.tabs.query({}, function(tabs) { chrome.tabs.query({}, function(tabs) {
for (let tab of tabs) { for (let tab of tabs) {
if (!tab.url) { if (!tab.url || !tab.id) {
return; return;
} }
let uri = URI(tab.url); let uri = URI(tab.url);
@ -255,11 +313,14 @@ export function wxMain() {
console.error("could not open database"); console.error("could not open database");
console.error(e); console.error(e);
}) })
.then((db) => { .then((db: IDBDatabase) => {
let http = new BrowserHttpLib(); let http = new BrowserHttpLib();
let badge = new ChromeBadge(); let badge = new ChromeBadge();
let notifier = new ChromeNotifier(); let notifier = new ChromeNotifier();
let wallet = new Wallet(db, http, badge, notifier); let wallet = new Wallet(db, http, badge, notifier);
// Handlers for messages coming directly from the content
// script on the page
let handlers = makeHandlers(db, wallet); let handlers = makeHandlers(db, wallet);
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
try { try {
@ -276,6 +337,19 @@ export function wxMain() {
return false; return false;
} }
}); });
// Handlers for catching HTTP requests
chrome.webRequest.onHeadersReceived.addListener((details) => {
if (details.statusCode != 402) {
return;
}
console.log(`got 402 from ${details.url}`);
return handleHttpPayment(details.responseHeaders || [],
details.url,
details.tabId);
}, {urls: ["<all_urls>"]}, ["responseHeaders", "blocking"]);
}) })
.catch((e) => { .catch((e) => {
console.error("could not initialize wallet messaging"); console.error("could not initialize wallet messaging");

View File

@ -14,6 +14,8 @@
"permissions": [ "permissions": [
"storage", "storage",
"tabs", "tabs",
"webRequest",
"webRequestBlocking",
"http://*/*", "http://*/*",
"https://*/*" "https://*/*"
], ],

View File

@ -34,7 +34,7 @@
"po2json": "git+https://github.com/mikeedwards/po2json", "po2json": "git+https://github.com/mikeedwards/po2json",
"systemjs": "^0.19.14", "systemjs": "^0.19.14",
"through2": "^2.0.1", "through2": "^2.0.1",
"typescript": "^1.9.0-dev.20160225", "typescript": "^2.0.2",
"typhonjs-istanbul-instrument-jspm": "^0.1.0", "typhonjs-istanbul-instrument-jspm": "^0.1.0",
"vinyl": "^1.1.1" "vinyl": "^1.1.1"
} }

View File

@ -11,8 +11,8 @@
<script src="../lib/vendor/lodash.core.min.js"></script> <script src="../lib/vendor/lodash.core.min.js"></script>
<script src="../lib/vendor/system-csp-production.src.js"></script> <script src="../lib/vendor/system-csp-production.src.js"></script>
<script src="../lib/vendor/jed.js"></script> <script src="../lib/vendor/jed.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/i18n.js"></script> <script src="../lib/i18n.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/module-trampoline.js"></script> <script src="../lib/module-trampoline.js"></script>
<style> <style>

View File

@ -26,11 +26,11 @@
import MithrilComponent = _mithril.MithrilComponent; import MithrilComponent = _mithril.MithrilComponent;
import {substituteFulfillmentUrl} from "../lib/wallet/helpers"; import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
import m from "mithril"; import m from "mithril";
import {Contract} from "../lib/wallet/types"; import {Contract, AmountJson} from "../lib/wallet/types";
"use strict"; "use strict";
function prettyAmount(amount) { function prettyAmount(amount: AmountJson) {
let v = amount.value + amount.fraction / 1e6; let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`; return `${v.toFixed(2)} ${amount.currency}`;
} }
@ -40,7 +40,7 @@ const Details = {
controller() { controller() {
return {collapsed: m.prop(true)}; return {collapsed: m.prop(true)};
}, },
view(ctrl, contract: Contract) { view(ctrl: any, contract: Contract) {
if (ctrl.collapsed()) { if (ctrl.collapsed()) {
return m("div", [ return m("div", [
m("button.linky", { m("button.linky", {
@ -71,11 +71,11 @@ export function main() {
let offer = JSON.parse(query.offer); let offer = JSON.parse(query.offer);
console.dir(offer); console.dir(offer);
let contract = offer.contract; let contract = offer.contract;
let error = null; let error: string|null = null;
let payDisabled = true; let payDisabled = true;
var Contract = { var Contract = {
view(ctrl) { view(ctrl: any) {
return [ return [
m("p", m("p",
i18n.parts`${m("strong", contract.merchant.name)} i18n.parts`${m("strong", contract.merchant.name)}
@ -95,7 +95,7 @@ export function main() {
} }
}; };
m.mount(document.getElementById("contract"), Contract); m.mount(document.getElementById("contract")!, Contract);
function checkPayment() { function checkPayment() {
chrome.runtime.sendMessage({type: 'check-pay', detail: {offer}}, (resp) => { chrome.runtime.sendMessage({type: 'check-pay', detail: {offer}}, (resp) => {

View File

@ -8,8 +8,8 @@
<script src="../lib/vendor/mithril.js"></script> <script src="../lib/vendor/mithril.js"></script>
<script src="../lib/vendor/system-csp-production.src.js"></script> <script src="../lib/vendor/system-csp-production.src.js"></script>
<script src="../lib/vendor/jed.js"></script> <script src="../lib/vendor/jed.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/i18n.js"></script> <script src="../lib/i18n.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/module-trampoline.js"></script> <script src="../lib/module-trampoline.js"></script>
<style> <style>

View File

@ -27,7 +27,6 @@
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers"; import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types"; import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
import m from "mithril"; import m from "mithril";
import {IExchangeInfo} from "../lib/wallet/types";
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types"; import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
import MithrilComponent = _mithril.MithrilComponent; import MithrilComponent = _mithril.MithrilComponent;
import {Denomination} from "../lib/wallet/types"; import {Denomination} from "../lib/wallet/types";
@ -41,10 +40,10 @@ import {getReserveCreationInfo} from "../lib/wallet/wxApi";
*/ */
class DelayTimer { class DelayTimer {
ms: number; ms: number;
f; f: () => void;
timerId: number = null; timerId: number|undefined = undefined;
constructor(ms: number, f) { constructor(ms: number, f: () => void) {
this.f = f; this.f = f;
this.ms = ms; this.ms = ms;
} }
@ -58,7 +57,7 @@ class DelayTimer {
} }
stop() { stop() {
if (this.timerId !== null) { if (this.timerId != undefined) {
window.clearTimeout(this.timerId); window.clearTimeout(this.timerId);
} }
} }
@ -67,11 +66,10 @@ class DelayTimer {
class Controller { class Controller {
url = m.prop<string>(); url = m.prop<string>();
statusString = null; statusString: string | null = null;
isValidExchange = false; isValidExchange = false;
reserveCreationInfo: ReserveCreationInfo = null; reserveCreationInfo?: ReserveCreationInfo;
private timer: DelayTimer; private timer: DelayTimer;
private request: XMLHttpRequest;
amount: AmountJson; amount: AmountJson;
callbackUrl: string; callbackUrl: string;
wtTypes: string[]; wtTypes: string[];
@ -97,7 +95,7 @@ class Controller {
private update() { private update() {
this.timer.stop(); this.timer.stop();
const doUpdate = () => { const doUpdate = () => {
this.reserveCreationInfo = null; this.reserveCreationInfo = undefined;
if (!this.url()) { if (!this.url()) {
this.statusString = i18n`Error: URL is empty`; this.statusString = i18n`Error: URL is empty`;
m.redraw(true); m.redraw(true);
@ -126,7 +124,7 @@ class Controller {
.catch((e) => { .catch((e) => {
console.log("get exchange info rejected"); console.log("get exchange info rejected");
if (e.hasOwnProperty("httpStatus")) { if (e.hasOwnProperty("httpStatus")) {
this.statusString = `Error: request failed with status ${this.request.status}`; this.statusString = `Error: request failed with status ${e.httpStatus}`;
} else if (e.hasOwnProperty("errorResponse")) { } else if (e.hasOwnProperty("errorResponse")) {
let resp = e.errorResponse; let resp = e.errorResponse;
this.statusString = `Error: ${resp.error} (${resp.hint})`; this.statusString = `Error: ${resp.error} (${resp.hint})`;
@ -143,11 +141,7 @@ class Controller {
reset() { reset() {
this.isValidExchange = false; this.isValidExchange = false;
this.statusString = null; this.statusString = null;
this.reserveCreationInfo = null; this.reserveCreationInfo = undefined;
if (this.request) {
this.request.abort();
this.request = null;
}
} }
confirmReserve(rci: ReserveCreationInfo, confirmReserve(rci: ReserveCreationInfo,
@ -155,7 +149,7 @@ class Controller {
amount: AmountJson, amount: AmountJson,
callback_url: string) { callback_url: string) {
const d = {exchange, amount}; const d = {exchange, amount};
const cb = (rawResp) => { const cb = (rawResp: any) => {
if (!rawResp) { if (!rawResp) {
throw Error("empty response"); throw Error("empty response");
} }
@ -195,127 +189,122 @@ class Controller {
} }
function view(ctrl: Controller): any { function view(ctrl: Controller): any {
let controls = []; function* f(): IterableIterator<any> {
let mx = (x, ...args) => controls.push(m(x, ...args)); yield m("p",
i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
ctrl.amount))} from your bank account into your wallet.`);
mx("p", if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
i18n.parts`You are about to withdraw ${m("strong", amountToPretty( yield viewComplex(ctrl);
ctrl.amount))} from your bank account into your wallet.`); return;
}
if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) { yield viewSimple(ctrl);
return controls.concat(viewComplex(ctrl));
} }
return Array.from(f());
return controls.concat(viewSimple(ctrl));
} }
function viewSimple(ctrl: Controller) { function viewSimple(ctrl: Controller) {
let controls = []; function *f() {
let mx = (x, ...args) => controls.push(m(x, ...args)); if (ctrl.statusString) {
yield m("p", "Error: ", ctrl.statusString);
if (ctrl.statusString) { yield m("button.linky", {
mx("p", "Error: ", ctrl.statusString); onclick: () => {
mx("button.linky", { ctrl.complexViewRequested = true;
onclick: () => { }
ctrl.complexViewRequested = true; }, "advanced options");
} }
}, "advanced options"); else if (ctrl.reserveCreationInfo != undefined) {
yield m("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
ctrl.url(),
ctrl.amount,
ctrl.callbackUrl),
disabled: !ctrl.isValidExchange
},
"Accept fees and withdraw");
yield m("span.spacer");
yield m("button.linky", {
onclick: () => {
ctrl.complexViewRequested = true;
}
}, "advanced options");
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
} else {
yield m("p", "Please wait ...");
}
} }
else if (ctrl.reserveCreationInfo) {
mx("button.accept", { return Array.from(f());
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo, }
function viewComplex(ctrl: Controller) {
function *f() {
yield m("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
ctrl.url(), ctrl.url(),
ctrl.amount, ctrl.amount,
ctrl.callbackUrl), ctrl.callbackUrl),
disabled: !ctrl.isValidExchange disabled: !ctrl.isValidExchange
}, },
"Accept fees and withdraw"); "Accept fees and withdraw");
mx("span.spacer"); yield m("span.spacer");
mx("button.linky", { yield m("button.linky", {
onclick: () => { onclick: () => {
ctrl.complexViewRequested = true; ctrl.complexViewRequested = false;
} }
}, "advanced options"); }, "back to simple view");
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount; yield m("br");
mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
} else {
mx("p", "Please wait ...");
}
return controls; yield m("input", {
} className: "url",
type: "text",
spellcheck: false,
value: ctrl.url(),
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
yield m("br");
function viewComplex(ctrl: Controller) { if (ctrl.statusString) {
let controls = []; yield m("p", ctrl.statusString);
let mx = (x, ...args) => controls.push(m(x, ...args)); } else if (!ctrl.reserveCreationInfo) {
yield m("p", "Checking URL, please wait ...");
mx("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo,
ctrl.url(),
ctrl.amount,
ctrl.callbackUrl),
disabled: !ctrl.isValidExchange
},
"Accept fees and withdraw");
mx("span.spacer");
mx("button.linky", {
onclick: () => {
ctrl.complexViewRequested = false;
} }
}, "back to simple view");
mx("br"); if (ctrl.reserveCreationInfo) {
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
mx("input", yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
{ if (ctrl.detailCollapsed()) {
className: "url", yield m("button.linky", {
type: "text", onclick: () => {
spellcheck: false, ctrl.detailCollapsed(false);
value: ctrl.url(), }
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)), }, "show more details");
}); } else {
yield m("button.linky", {
mx("br"); onclick: () => {
ctrl.detailCollapsed(true);
if (ctrl.statusString) { }
mx("p", ctrl.statusString); }, "hide details");
} else if (!ctrl.reserveCreationInfo) { yield m("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
mx("p", "Checking URL, please wait ..."); }
}
if (ctrl.reserveCreationInfo) {
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
if (ctrl.detailCollapsed()) {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(false);
}
}, "show more details");
} else {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(true);
}
}, "hide details");
mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
} }
} }
return Array.from(f());
return m("div", controls);
} }
function renderReserveCreationDetails(rci: ReserveCreationInfo) { function renderReserveCreationDetails(rci: ReserveCreationInfo) {
let denoms = rci.selectedDenoms; let denoms = rci.selectedDenoms;
let countByPub = {}; let countByPub: {[s: string]: number} = {};
let uniq = []; let uniq: Denomination[] = [];
denoms.forEach((x: Denomination) => { denoms.forEach((x: Denomination) => {
let c = countByPub[x.denom_pub] || 0; let c = countByPub[x.denom_pub] || 0;
@ -358,7 +347,7 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo) {
function getSuggestedExchange(currency: string): Promise<string> { function getSuggestedExchange(currency: string): Promise<string> {
// TODO: make this request go to the wallet backend // TODO: make this request go to the wallet backend
// Right now, this is a stub. // Right now, this is a stub.
const defaultExchange = { const defaultExchange: {[s: string]: string} = {
"KUDOS": "https://exchange.demo.taler.net", "KUDOS": "https://exchange.demo.taler.net",
"PUDOS": "https://exchange.test.taler.net", "PUDOS": "https://exchange.test.taler.net",
}; };
@ -373,6 +362,7 @@ function getSuggestedExchange(currency: string): Promise<string> {
} }
export function main() { export function main() {
const url = URI(document.location.href); const url = URI(document.location.href);
const query: any = URI.parseQuery(url.query()); const query: any = URI.parseQuery(url.query());
@ -383,14 +373,14 @@ export function main() {
getSuggestedExchange(amount.currency) getSuggestedExchange(amount.currency)
.then((suggestedExchangeUrl) => { .then((suggestedExchangeUrl) => {
const controller = () => new Controller(suggestedExchangeUrl, amount, callback_url, wt_types); const controller = function () { return new Controller(suggestedExchangeUrl, amount, callback_url, wt_types); };
var ExchangeSelection = {controller, view}; const ExchangeSelection = {controller, view};
m.mount(document.getElementById("exchange-selection"), ExchangeSelection); m.mount(document.getElementById("exchange-selection")!, ExchangeSelection);
}) })
.catch((e) => { .catch((e) => {
// TODO: provide more context information, maybe factor it out into a // TODO: provide more context information, maybe factor it out into a
// TODO:generic error reporting function or component. // TODO:generic error reporting function or component.
document.body.innerText = `Fatal error: "${e.message}".`; document.body.innerText = `Fatal error: "${e.message}".`;
console.error(`got backend error "${e.message}"`); console.error(`got error "${e.message}"`, e);
}); });
} }

View File

@ -21,30 +21,37 @@
* @author Florian Dold * @author Florian Dold
*/ */
function replacer(match, pIndent, pKey, pVal, pEnd) { function replacer(match: string, pIndent: string, pKey: string, pVal: string,
pEnd: string) {
var key = '<span class=json-key>'; var key = '<span class=json-key>';
var val = '<span class=json-value>'; var val = '<span class=json-value>';
var str = '<span class=json-string>'; var str = '<span class=json-string>';
var r = pIndent || ''; var r = pIndent || '';
if (pKey) if (pKey) {
r = r + key + pKey.replace(/[": ]/g, '') + '</span>: '; r = r + key + pKey.replace(/[": ]/g, '') + '</span>: ';
if (pVal) }
r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>'; if (pVal) {
r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>';
}
return r + (pEnd || ''); return r + (pEnd || '');
} }
function prettyPrint(obj) { function prettyPrint(obj: any) {
var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
return JSON.stringify(obj, null, 3) return JSON.stringify(obj, null as any, 3)
.replace(/&/g, '&amp;').replace(/\\"/g, '&quot;') .replace(/&/g, '&amp;').replace(/\\"/g, '&quot;')
.replace(/</g, '&lt;').replace(/>/g, '&gt;') .replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(jsonLine, replacer); .replace(jsonLine, replacer);
} }
document.addEventListener("DOMContentLoaded", (e) => { document.addEventListener("DOMContentLoaded", () => {
chrome.runtime.sendMessage({type:'dump-db'}, (resp) => { chrome.runtime.sendMessage({type: 'dump-db'}, (resp) => {
document.getElementById('dump').innerHTML = prettyPrint(resp); const el = document.getElementById('dump');
if (!el) {
throw Error();
}
el.innerHTML = prettyPrint(resp);
}); });
}); });

View File

@ -29,12 +29,15 @@
"use strict"; "use strict";
import {substituteFulfillmentUrl} from "../lib/wallet/helpers"; import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
import {Wallet} from "../lib/wallet/wallet";
import {AmountJson} from "../lib/wallet/types";
declare var m: any; declare var m: any;
declare var i18n: any; declare var i18n: any;
function onUpdateNotification(f) { function onUpdateNotification(f: () => void) {
let port = chrome.runtime.connect({name: "notifications"}); let port = chrome.runtime.connect({name: "notifications"});
port.onMessage.addListener((msg, port) => { port.onMessage.addListener((msg, port) => {
f(); f();
@ -56,7 +59,7 @@ export function main() {
console.log("this is popup"); console.log("this is popup");
function makeTab(target, name) { function makeTab(target: string, name: string) {
let cssClass = ""; let cssClass = "";
if (target == m.route()) { if (target == m.route()) {
cssClass = "active"; cssClass = "active";
@ -79,8 +82,8 @@ namespace WalletNavBar {
} }
function openInExtension(element, isInitialized) { function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
element.addEventListener("click", (e) => { element.addEventListener("click", (e: Event) => {
chrome.tabs.create({ chrome.tabs.create({
"url": element.href "url": element.href
}); });
@ -88,13 +91,15 @@ function openInExtension(element, isInitialized) {
}); });
} }
namespace WalletBalance { namespace WalletBalance {
export function controller() { export function controller() {
return new Controller(); return new Controller();
} }
class Controller { class Controller {
myWallet; myWallet: any;
gotError = false; gotError = false;
constructor() { constructor() {
@ -128,7 +133,7 @@ namespace WalletBalance {
if (!wallet) { if (!wallet) {
throw Error("Could not retrieve wallet"); throw Error("Could not retrieve wallet");
} }
let listing = _.map(wallet, x => m("p", formatAmount(x))); let listing = _.map(wallet, (x: any) => m("p", formatAmount(x)));
if (listing.length > 0) { if (listing.length > 0) {
return listing; return listing;
} }
@ -141,13 +146,13 @@ namespace WalletBalance {
} }
function formatTimestamp(t) { function formatTimestamp(t: number) {
let x = new Date(t); let x = new Date(t);
return x.toLocaleString(); return x.toLocaleString();
} }
function formatAmount(amount) { function formatAmount(amount: AmountJson) {
let v = amount.value + amount.fraction / 1e6; let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`; return `${v.toFixed(2)} ${amount.currency}`;
} }
@ -158,7 +163,7 @@ function abbrevKey(s: string) {
} }
function retryPayment(url, contractHash) { function retryPayment(url: string, contractHash: string) {
return function() { return function() {
chrome.tabs.create({ chrome.tabs.create({
"url": substituteFulfillmentUrl(url, "url": substituteFulfillmentUrl(url,
@ -168,7 +173,7 @@ function retryPayment(url, contractHash) {
} }
function formatHistoryItem(historyItem) { function formatHistoryItem(historyItem: any) {
const d = historyItem.detail; const d = historyItem.detail;
const t = historyItem.timestamp; const t = historyItem.timestamp;
console.log("hist item", historyItem); console.log("hist item", historyItem);
@ -210,7 +215,7 @@ namespace WalletHistory {
} }
class Controller { class Controller {
myHistory; myHistory: any;
gotError = false; gotError = false;
constructor() { constructor() {
@ -287,7 +292,7 @@ var WalletDebug = {
}; };
function openExtensionPage(page) { function openExtensionPage(page: string) {
return function() { return function() {
chrome.tabs.create({ chrome.tabs.create({
"url": chrome.extension.getURL(page) "url": chrome.extension.getURL(page)
@ -296,7 +301,7 @@ function openExtensionPage(page) {
} }
function openTab(page) { function openTab(page: string) {
return function() { return function() {
chrome.tabs.create({ chrome.tabs.create({
"url": page "url": page

View File

@ -1,9 +1,9 @@
import * as Emsc from '../../lib/wallet/emscriptif'; import * as Emsc from '../../lib/wallet/emscriptif';
declare var HttpMockLib; declare var HttpMockLib: any;
export function declareTests(assert, context, it) { export function declareTests(assert: any, context: any, it: any) {
it("calls native emscripten code", function() { it("calls native emscripten code", function() {
let x = new Emsc.Amount({value: 42, fraction: 42, currency: "EUR"}); let x = new Emsc.Amount({value: 42, fraction: 42, currency: "EUR"});

View File

@ -1,17 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es6",
"jsx": "react", "jsx": "react",
"experimentalDecorators": true, "experimentalDecorators": true,
"module": "system", "module": "system",
"sourceMap": true, "sourceMap": true,
"noLib": true, "noLib": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"noImplicitAny": true
}, },
"files": [ "files": [
"lib/i18n.ts", "lib/i18n.ts",
"lib/refs.ts", "lib/refs.ts",
"lib/shopApi.ts",
"lib/wallet/checkable.ts", "lib/wallet/checkable.ts",
"lib/wallet/cryptoApi.ts", "lib/wallet/cryptoApi.ts",
"lib/wallet/cryptoLib.ts", "lib/wallet/cryptoLib.ts",