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

15472
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();
}

338
lib/vendor/mithril.js vendored
View File

@ -1,28 +1,3 @@
/*
The MIT License (MIT)
Copyright (c) 2014 Leo Horie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
;(function (global, factory) { // eslint-disable-line ;(function (global, factory) { // eslint-disable-line
"use strict" "use strict"
/* eslint-disable no-undef */ /* eslint-disable no-undef */
@ -35,11 +10,11 @@
global.m = m global.m = m
} }
/* eslint-enable no-undef */ /* eslint-enable no-undef */
})(typeof window !== "undefined" ? window : {}, function (global, undefined) { // eslint-disable-line })(typeof window !== "undefined" ? window : this, function (global, undefined) { // eslint-disable-line
"use strict" "use strict"
m.version = function () { m.version = function () {
return "v0.2.2-rc.1" return "v0.2.5"
} }
var hasOwn = {}.hasOwnProperty var hasOwn = {}.hasOwnProperty
@ -63,9 +38,24 @@
function noop() {} function noop() {}
/* eslint-disable max-len */ var voidElements = {
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ AREA: 1,
/* eslint-enable max-len */ BASE: 1,
BR: 1,
COL: 1,
COMMAND: 1,
EMBED: 1,
HR: 1,
IMG: 1,
INPUT: 1,
KEYGEN: 1,
LINK: 1,
META: 1,
PARAM: 1,
SOURCE: 1,
TRACK: 1,
WBR: 1
}
// caching commonly used variables // caching commonly used variables
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame var $document, $location, $requestAnimationFrame, $cancelAnimationFrame
@ -106,7 +96,7 @@
classes.push(match[2]) classes.push(match[2])
} else if (match[3][0] === "[") { } else if (match[3][0] === "[") {
var pair = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3]) var pair = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3])
cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true) cell.attrs[pair[1]] = pair[3] || ""
} }
} }
@ -151,7 +141,9 @@
* or splat (optional) * or splat (optional)
*/ */
function m(tag, pairs) { function m(tag, pairs) {
for (var args = [], i = 1; i < arguments.length; i++) { var args = []
for (var i = 1, length = arguments.length; i < length; i++) {
args[i - 1] = arguments[i] args[i - 1] = arguments[i]
} }
@ -247,8 +239,10 @@
}) })
var actions = [] var actions = []
for (var prop in existing) if (hasOwn.call(existing, prop)) { for (var prop in existing) {
actions.push(existing[prop]) if (hasOwn.call(existing, prop)) {
actions.push(existing[prop])
}
} }
var changes = actions.sort(sortChanges) var changes = actions.sort(sortChanges)
@ -379,8 +373,10 @@
if (cached.controllers) { if (cached.controllers) {
forEach(cached.controllers, function (controller) { forEach(cached.controllers, function (controller) {
if (controller.onunload) controller.onunload({preventDefault: noop}); if (controller.onunload) {
}); controller.onunload({preventDefault: noop})
}
})
} }
} }
} }
@ -481,7 +477,7 @@
nodes = injectHTML(parentElement, index, data) nodes = injectHTML(parentElement, index, data)
} else { } else {
nodes = [$document.createTextNode(data)] nodes = [$document.createTextNode(data)]
if (!parentElement.nodeName.match(voidElements)) { if (!(parentElement.nodeName in voidElements)) {
insertNode(parentElement, nodes[0], index) insertNode(parentElement, nodes[0], index)
} }
} }
@ -761,7 +757,9 @@
var unloaders = [] var unloaders = []
function updateLists(views, controllers, view, controller) { function updateLists(views, controllers, view, controller) {
if (controller.onunload != null) { if (controller.onunload != null &&
unloaders.map(function (u) { return u.handler })
.indexOf(controller.onunload) < 0) {
unloaders.push({ unloaders.push({
controller: controller, controller: controller,
handler: controller.onunload handler: controller.onunload
@ -773,11 +771,32 @@
} }
var forcing = false var forcing = false
function checkView(data, view, cached, cachedControllers, controllers, views) { function checkView(
var controller = getController(cached.views, view, cachedControllers, data.controller) data,
view,
cached,
cachedControllers,
controllers,
views
) {
var controller = getController(
cached.views,
view,
cachedControllers,
data.controller)
var key = data && data.attrs && data.attrs.key var key = data && data.attrs && data.attrs.key
data = pendingRequests === 0 || forcing || cachedControllers && cachedControllers.indexOf(controller) > -1 ? data.view(controller) : {tag: "placeholder"}
if (data.subtree === "retain") return data; if (pendingRequests === 0 ||
forcing ||
cachedControllers &&
cachedControllers.indexOf(controller) > -1) {
data = data.view(controller)
} else {
data = {tag: "placeholder"}
}
if (data.subtree === "retain") return data
data.attrs = data.attrs || {} data.attrs = data.attrs || {}
data.attrs.key = key data.attrs.key = key
updateLists(views, controllers, view, controller) updateLists(views, controllers, view, controller)
@ -842,6 +861,9 @@
// set attributes first, then create children // set attributes first, then create children
var attrs = constructAttrs(data, node, namespace, hasKeys) var attrs = constructAttrs(data, node, namespace, hasKeys)
// add the node to its parent before attaching children to it
insertNode(parentElement, node, index)
var children = constructChildren(data, node, cached, editable, var children = constructChildren(data, node, cached, editable,
namespace, configs) namespace, configs)
@ -865,7 +887,7 @@
controllers) controllers)
} }
if (isNew || shouldReattach === true && node != null) { if (!isNew && shouldReattach === true && node != null) {
insertNode(parentElement, node, index) insertNode(parentElement, node, index)
} }
@ -984,24 +1006,28 @@
} }
function copyStyleAttrs(node, dataAttr, cachedAttr) { function copyStyleAttrs(node, dataAttr, cachedAttr) {
for (var rule in dataAttr) if (hasOwn.call(dataAttr, rule)) { for (var rule in dataAttr) {
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) { if (hasOwn.call(dataAttr, rule)) {
node.style[rule] = dataAttr[rule] if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) {
node.style[rule] = dataAttr[rule]
}
} }
} }
for (rule in cachedAttr) if (hasOwn.call(cachedAttr, rule)) { for (rule in cachedAttr) {
if (!hasOwn.call(dataAttr, rule)) node.style[rule] = "" if (hasOwn.call(cachedAttr, rule)) {
if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
}
} }
} }
function shouldUseSetAttribute(attrName) { var shouldUseSetAttribute = {
return attrName !== "list" && list: 1,
attrName !== "style" && style: 1,
attrName !== "form" && form: 1,
attrName !== "type" && type: 1,
attrName !== "width" && width: 1,
attrName !== "height" height: 1
} }
function setSingleAttr( function setSingleAttr(
@ -1032,7 +1058,7 @@
attrName === "className" ? "class" : attrName, attrName === "className" ? "class" : attrName,
dataAttr) dataAttr)
} }
} else if (attrName in node && shouldUseSetAttribute(attrName)) { } else if (attrName in node && !shouldUseSetAttribute[attrName]) {
// handle cases that are properties (but ignore cases where we // handle cases that are properties (but ignore cases where we
// should use setAttribute instead) // should use setAttribute instead)
// //
@ -1064,7 +1090,7 @@
tag, tag,
namespace namespace
) { ) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) { if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || ($document.activeElement === node)) {
cachedAttrs[attrName] = dataAttr cachedAttrs[attrName] = dataAttr
try { try {
return setSingleAttr( return setSingleAttr(
@ -1087,16 +1113,18 @@
} }
function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) { function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) if (hasOwn.call(dataAttrs, attrName)) { for (var attrName in dataAttrs) {
if (trySetAttr( if (hasOwn.call(dataAttrs, attrName)) {
node, if (trySetAttr(
attrName, node,
dataAttrs[attrName], attrName,
cachedAttrs[attrName], dataAttrs[attrName],
cachedAttrs, cachedAttrs[attrName],
tag, cachedAttrs,
namespace)) { tag,
continue namespace)) {
continue
}
} }
} }
return cachedAttrs return cachedAttrs
@ -1148,9 +1176,41 @@
$document.createRange().createContextualFragment(data)) $document.createRange().createContextualFragment(data))
} catch (e) { } catch (e) {
parentElement.insertAdjacentHTML("beforeend", data) parentElement.insertAdjacentHTML("beforeend", data)
replaceScriptNodes(parentElement)
} }
} }
// Replace script tags inside given DOM element with executable ones.
// Will also check children recursively and replace any found script
// tags in same manner.
function replaceScriptNodes(node) {
if (node.tagName === "SCRIPT") {
node.parentNode.replaceChild(buildExecutableNode(node), node)
} else {
var children = node.childNodes
if (children && children.length) {
for (var i = 0; i < children.length; i++) {
replaceScriptNodes(children[i])
}
}
}
return node
}
// Replace script element with one whose contents are executable.
function buildExecutableNode(node){
var scriptEl = document.createElement("script")
var attrs = node.attributes
for (var i = 0; i < attrs.length; i++) {
scriptEl.setAttribute(attrs[i].name, attrs[i].value)
}
scriptEl.text = node.innerHTML
return scriptEl
}
function injectHTML(parentElement, index, data) { function injectHTML(parentElement, index, data) {
var nextSibling = parentElement.childNodes[index] var nextSibling = parentElement.childNodes[index]
if (nextSibling) { if (nextSibling) {
@ -1278,7 +1338,7 @@
} }
m.prop = function (store) { m.prop = function (store) {
if ((store != null && isObject(store) || isFunction(store)) && if ((store != null && (isObject(store) || isFunction(store)) || ((typeof Promise !== "undefined") && (store instanceof Promise))) &&
isFunction(store.then)) { isFunction(store.then)) {
return propify(store) return propify(store)
} }
@ -1323,8 +1383,10 @@
} }
m.component = function (component) { m.component = function (component) {
for (var args = [], i = 1; i < arguments.length; i++) { var args = new Array(arguments.length - 1)
args.push(arguments[i])
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i]
} }
return parameterize(component, args) return parameterize(component, args)
@ -1417,7 +1479,7 @@
try { try {
// lastRedrawId is a positive number if a second redraw is requested // lastRedrawId is a positive number if a second redraw is requested
// before the next animation frame // before the next animation frame
// lastRedrawID is null if it's the first redraw and not an event // lastRedrawId is null if it's the first redraw and not an event
// handler // handler
if (lastRedrawId && !force) { if (lastRedrawId && !force) {
// when setTimeout: only reschedule redraw if time between now // when setTimeout: only reschedule redraw if time between now
@ -1476,7 +1538,7 @@
m.withAttr = function (prop, withAttrCallback, callbackThis) { m.withAttr = function (prop, withAttrCallback, callbackThis) {
return function (e) { return function (e) {
e = e || event e = e || window.event
/* eslint-disable no-invalid-this */ /* eslint-disable no-invalid-this */
var currentTarget = e.currentTarget || this var currentTarget = e.currentTarget || this
var _this = callbackThis || this var _this = callbackThis || this
@ -1558,8 +1620,10 @@
params = {} params = {}
} }
for (var i in args) if (hasOwn.call(args, i)) { for (var i in args) {
params[i] = args[i] if (hasOwn.call(args, i)) {
params[i] = args[i]
}
} }
var querystring = buildQueryString(params) var querystring = buildQueryString(params)
@ -1585,8 +1649,16 @@
var method = replaceHistory ? "replaceState" : "pushState" var method = replaceHistory ? "replaceState" : "pushState"
computePreRedrawHook = setScroll computePreRedrawHook = setScroll
computePostRedrawHook = function () { computePostRedrawHook = function () {
global.history[method](null, $document.title, try {
modes[m.route.mode] + currentRoute) global.history[method](null, $document.title,
modes[m.route.mode] + currentRoute)
} catch (err) {
// In the event of a pushState or replaceState failure,
// fallback to a standard redirect. This is specifically
// to address a Safari security error when attempting to
// call pushState more than 100 times.
$location[m.route.mode] = currentRoute
}
} }
redirect(modes[m.route.mode] + currentRoute) redirect(modes[m.route.mode] + currentRoute)
} else { } else {
@ -1635,29 +1707,31 @@
return true return true
} }
for (var route in router) if (hasOwn.call(router, route)) { for (var route in router) {
if (route === path) { if (hasOwn.call(router, route)) {
m.mount(root, router[route]) if (route === path) {
return true
}
var matcher = new RegExp("^" + route
.replace(/:[^\/]+?\.{3}/g, "(.*?)")
.replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
if (matcher.test(path)) {
/* eslint-disable no-loop-func */
path.replace(matcher, function () {
var keys = route.match(/:[^\/]+/g) || []
var values = [].slice.call(arguments, 1, -2)
forEach(keys, function (key, i) {
routeParams[key.replace(/:|\./g, "")] =
decodeURIComponent(values[i])
})
m.mount(root, router[route]) m.mount(root, router[route])
}) return true
/* eslint-enable no-loop-func */ }
return true
var matcher = new RegExp("^" + route
.replace(/:[^\/]+?\.{3}/g, "(.*?)")
.replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
if (matcher.test(path)) {
/* eslint-disable no-loop-func */
path.replace(matcher, function () {
var keys = route.match(/:[^\/]+/g) || []
var values = [].slice.call(arguments, 1, -2)
forEach(keys, function (key, i) {
routeParams[key.replace(/:|\./g, "")] =
decodeURIComponent(values[i])
})
m.mount(root, router[route])
})
/* eslint-enable no-loop-func */
return true
}
} }
} }
} }
@ -1703,32 +1777,35 @@
var duplicates = {} var duplicates = {}
var str = [] var str = []
for (var prop in object) if (hasOwn.call(object, prop)) { for (var prop in object) {
var key = prefix ? prefix + "[" + prop + "]" : prop if (hasOwn.call(object, prop)) {
var value = object[prop] var key = prefix ? prefix + "[" + prop + "]" : prop
var value = object[prop]
if (value === null) { if (value === null) {
str.push(encodeURIComponent(key)) str.push(encodeURIComponent(key))
} else if (isObject(value)) { } else if (isObject(value)) {
str.push(buildQueryString(value, key)) str.push(buildQueryString(value, key))
} else if (isArray(value)) { } else if (isArray(value)) {
var keys = [] var keys = []
duplicates[key] = duplicates[key] || {} duplicates[key] = duplicates[key] || {}
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
forEach(value, function (item) { forEach(value, function (item) {
/* eslint-enable no-loop-func */ /* eslint-enable no-loop-func */
if (!duplicates[key][item]) { if (!duplicates[key][item]) {
duplicates[key][item] = true duplicates[key][item] = true
keys.push(encodeURIComponent(key) + "=" + keys.push(encodeURIComponent(key) + "=" +
encodeURIComponent(item)) encodeURIComponent(item))
} }
}) })
str.push(keys.join("&")) str.push(keys.join("&"))
} else if (value !== undefined) { } else if (value !== undefined) {
str.push(encodeURIComponent(key) + "=" + str.push(encodeURIComponent(key) + "=" +
encodeURIComponent(value)) encodeURIComponent(value))
}
} }
} }
return str.join("&") return str.join("&")
} }
@ -1927,7 +2004,7 @@
m.deferred.onerror = function (e) { m.deferred.onerror = function (e) {
if (type.call(e) === "[object Error]" && if (type.call(e) === "[object Error]" &&
!e.constructor.toString().match(/ Error/)) { !/ Error/.test(e.constructor.toString())) {
pendingRequests = 0 pendingRequests = 0
throw e throw e
} }
@ -1936,7 +2013,7 @@
m.sync = function (args) { m.sync = function (args) {
var deferred = m.deferred() var deferred = m.deferred()
var outstanding = args.length var outstanding = args.length
var results = new Array(outstanding) var results = []
var method = "resolve" var method = "resolve"
function synchronizer(pos, resolved) { function synchronizer(pos, resolved) {
@ -1965,7 +2042,7 @@
function identity(value) { return value } function identity(value) { return value }
function handleJsonp(options) { function handleJsonp(options) {
var callbackKey = "mithril_callback_" + var callbackKey = options.callbackName || "mithril_callback_" +
new Date().getTime() + "_" + new Date().getTime() + "_" +
(Math.round(Math.random() * 1e16)).toString(36) (Math.round(Math.random() * 1e16)).toString(36)
@ -2073,16 +2150,14 @@
} }
function parameterizeUrl(url, data) { function parameterizeUrl(url, data) {
var tokens = url.match(/:[a-z]\w+/gi) if (data) {
url = url.replace(/:[a-z]\w+/gi, function (token){
if (tokens && data) {
forEach(tokens, function (token) {
var key = token.slice(1) var key = token.slice(1)
url = url.replace(token, data[key]) var value = data[key] || token
delete data[key] delete data[key]
return value
}) })
} }
return url return url
} }
@ -2143,6 +2218,7 @@
} }
} catch (e) { } catch (e) {
deferred.reject(e) deferred.reject(e)
m.deferred.onerror(e)
} finally { } finally {
if (options.background !== true) m.endComputation() if (options.background !== true) m.endComputation()
} }
@ -2154,4 +2230,4 @@
} }
return m return m
}) }); // eslint-disable-line

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",