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 {
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("${$}", "$");
return url;
}
const handlers = [];
interface Handler {
type: string;
listener: (e: CustomEvent) => void;
}
const handlers: Handler[] = [];
function init() {
chrome.runtime.sendMessage({type: "ping"}, () => {
chrome.runtime.sendMessage({type: "ping"}, (resp) => {
if (chrome.runtime.lastError) {
console.log("extension not yet ready");
window.setTimeout(init, 200);
return;
}
console.log("got pong");
registerHandlers();
// Hack to know when the extension is unloaded
let port = chrome.runtime.connect();
@ -59,15 +184,27 @@ namespace TalerNotify {
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();
function registerHandlers() {
const $ = (x) => document.getElementById(x);
function addHandler(type, listener) {
function addHandler(type: string, listener: (e: CustomEvent) => void) {
document.addEventListener(type, listener);
handlers.push({type, listener});
}
@ -193,7 +330,7 @@ namespace TalerNotify {
console.log("got resp");
console.dir(resp);
if (!resp.success) {
console.log("got event detial:");
console.log("got event detail:");
console.dir(e.detail);
if (e.detail.offering_url) {
console.log("offering url", e.detail.offering_url);

View File

@ -117,7 +117,7 @@ const paths = {
const tsBaseArgs = {
target: "es5",
target: "es6",
jsx: "react",
experimentalDecorators: true,
module: "system",
@ -125,6 +125,8 @@ const tsBaseArgs = {
noLib: true,
noImplicitReturns: 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;
newModule(obj: Object): any;
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 {
var cwrap: EmscFunGen;
function _free(ptr: number);
function _free(ptr: number): void;
function _malloc(n: number): number;
@ -41,9 +41,10 @@ export declare namespace Module {
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,
buffer: number,
dontAddNull?: boolean);
dontAddNull?: boolean): void;
}

View File

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

220
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
"use strict"
/* eslint-disable no-undef */
@ -35,11 +10,11 @@
global.m = m
}
/* 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"
m.version = function () {
return "v0.2.2-rc.1"
return "v0.2.5"
}
var hasOwn = {}.hasOwnProperty
@ -63,9 +38,24 @@
function noop() {}
/* eslint-disable max-len */
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/
/* eslint-enable max-len */
var voidElements = {
AREA: 1,
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
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame
@ -106,7 +96,7 @@
classes.push(match[2])
} else if (match[3][0] === "[") {
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)
*/
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]
}
@ -247,9 +239,11 @@
})
var actions = []
for (var prop in existing) if (hasOwn.call(existing, prop)) {
for (var prop in existing) {
if (hasOwn.call(existing, prop)) {
actions.push(existing[prop])
}
}
var changes = actions.sort(sortChanges)
var newCached = new Array(cached.length)
@ -379,8 +373,10 @@
if (cached.controllers) {
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)
} else {
nodes = [$document.createTextNode(data)]
if (!parentElement.nodeName.match(voidElements)) {
if (!(parentElement.nodeName in voidElements)) {
insertNode(parentElement, nodes[0], index)
}
}
@ -761,7 +757,9 @@
var unloaders = []
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({
controller: controller,
handler: controller.onunload
@ -773,11 +771,32 @@
}
var forcing = false
function checkView(data, view, cached, cachedControllers, controllers, views) {
var controller = getController(cached.views, view, cachedControllers, data.controller)
function checkView(
data,
view,
cached,
cachedControllers,
controllers,
views
) {
var controller = getController(
cached.views,
view,
cachedControllers,
data.controller)
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.key = key
updateLists(views, controllers, view, controller)
@ -842,6 +861,9 @@
// set attributes first, then create children
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,
namespace, configs)
@ -865,7 +887,7 @@
controllers)
}
if (isNew || shouldReattach === true && node != null) {
if (!isNew && shouldReattach === true && node != null) {
insertNode(parentElement, node, index)
}
@ -984,24 +1006,28 @@
}
function copyStyleAttrs(node, dataAttr, cachedAttr) {
for (var rule in dataAttr) if (hasOwn.call(dataAttr, rule)) {
for (var rule in dataAttr) {
if (hasOwn.call(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(cachedAttr, rule)) {
if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
}
}
}
function shouldUseSetAttribute(attrName) {
return attrName !== "list" &&
attrName !== "style" &&
attrName !== "form" &&
attrName !== "type" &&
attrName !== "width" &&
attrName !== "height"
var shouldUseSetAttribute = {
list: 1,
style: 1,
form: 1,
type: 1,
width: 1,
height: 1
}
function setSingleAttr(
@ -1032,7 +1058,7 @@
attrName === "className" ? "class" : attrName,
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
// should use setAttribute instead)
//
@ -1064,7 +1090,7 @@
tag,
namespace
) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || ($document.activeElement === node)) {
cachedAttrs[attrName] = dataAttr
try {
return setSingleAttr(
@ -1087,7 +1113,8 @@
}
function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) if (hasOwn.call(dataAttrs, attrName)) {
for (var attrName in dataAttrs) {
if (hasOwn.call(dataAttrs, attrName)) {
if (trySetAttr(
node,
attrName,
@ -1099,6 +1126,7 @@
continue
}
}
}
return cachedAttrs
}
@ -1148,9 +1176,41 @@
$document.createRange().createContextualFragment(data))
} catch (e) {
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) {
var nextSibling = parentElement.childNodes[index]
if (nextSibling) {
@ -1278,7 +1338,7 @@
}
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)) {
return propify(store)
}
@ -1323,8 +1383,10 @@
}
m.component = function (component) {
for (var args = [], i = 1; i < arguments.length; i++) {
args.push(arguments[i])
var args = new Array(arguments.length - 1)
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i]
}
return parameterize(component, args)
@ -1417,7 +1479,7 @@
try {
// lastRedrawId is a positive number if a second redraw is requested
// 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
if (lastRedrawId && !force) {
// when setTimeout: only reschedule redraw if time between now
@ -1476,7 +1538,7 @@
m.withAttr = function (prop, withAttrCallback, callbackThis) {
return function (e) {
e = e || event
e = e || window.event
/* eslint-disable no-invalid-this */
var currentTarget = e.currentTarget || this
var _this = callbackThis || this
@ -1558,9 +1620,11 @@
params = {}
}
for (var i in args) if (hasOwn.call(args, i)) {
for (var i in args) {
if (hasOwn.call(args, i)) {
params[i] = args[i]
}
}
var querystring = buildQueryString(params)
var currentPath
@ -1585,8 +1649,16 @@
var method = replaceHistory ? "replaceState" : "pushState"
computePreRedrawHook = setScroll
computePostRedrawHook = function () {
try {
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)
} else {
@ -1635,7 +1707,8 @@
return true
}
for (var route in router) if (hasOwn.call(router, route)) {
for (var route in router) {
if (hasOwn.call(router, route)) {
if (route === path) {
m.mount(root, router[route])
return true
@ -1661,6 +1734,7 @@
}
}
}
}
function routeUnobtrusive(e) {
e = e || event
@ -1703,7 +1777,8 @@
var duplicates = {}
var str = []
for (var prop in object) if (hasOwn.call(object, prop)) {
for (var prop in object) {
if (hasOwn.call(object, prop)) {
var key = prefix ? prefix + "[" + prop + "]" : prop
var value = object[prop]
@ -1729,6 +1804,8 @@
encodeURIComponent(value))
}
}
}
return str.join("&")
}
@ -1927,7 +2004,7 @@
m.deferred.onerror = function (e) {
if (type.call(e) === "[object Error]" &&
!e.constructor.toString().match(/ Error/)) {
!/ Error/.test(e.constructor.toString())) {
pendingRequests = 0
throw e
}
@ -1936,7 +2013,7 @@
m.sync = function (args) {
var deferred = m.deferred()
var outstanding = args.length
var results = new Array(outstanding)
var results = []
var method = "resolve"
function synchronizer(pos, resolved) {
@ -1965,7 +2042,7 @@
function identity(value) { return value }
function handleJsonp(options) {
var callbackKey = "mithril_callback_" +
var callbackKey = options.callbackName || "mithril_callback_" +
new Date().getTime() + "_" +
(Math.round(Math.random() * 1e16)).toString(36)
@ -2073,16 +2150,14 @@
}
function parameterizeUrl(url, data) {
var tokens = url.match(/:[a-z]\w+/gi)
if (tokens && data) {
forEach(tokens, function (token) {
if (data) {
url = url.replace(/:[a-z]\w+/gi, function (token){
var key = token.slice(1)
url = url.replace(token, data[key])
var value = data[key] || token
delete data[key]
return value
})
}
return url
}
@ -2143,6 +2218,7 @@
}
} catch (e) {
deferred.reject(e)
m.deferred.onerror(e)
} finally {
if (options.background !== true) m.endComputation()
}
@ -2154,4 +2230,4 @@
}
return m
})
}); // eslint-disable-line

View File

@ -25,18 +25,39 @@
*/
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.message = message;
this.stack = (<any>new Error()).stack;
}
}) as any as SchemaErrorConstructor;
SchemaError.prototype = new Error;
let chkSym = Symbol("checkable");
function checkNumber(target, prop, path): any {
function checkNumber(target: any, prop: Prop, path: Path): any {
if ((typeof target) !== "number") {
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") {
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") {
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;
}
function checkList(target, prop, path): any {
function checkList(target: any, prop: Prop, path: Path): any {
if (!Array.isArray(target)) {
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);
prop.elementChecker(target,
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;
if (!type) {
throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
@ -123,8 +144,8 @@ export namespace Checkable {
}
export function Class(target) {
target.checked = (v) => {
export function Class(target: any) {
target.checked = (v: any) => {
return checkValue(v, {
propertyKey: "(root)",
type: target,
@ -135,7 +156,7 @@ export namespace Checkable {
}
export function Value(type) {
export function Value(type: any) {
if (!type) {
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 = {};
type(stub, "(list-element)");
let elementProp = mkChk(stub).props[0];
@ -174,7 +195,7 @@ export namespace Checkable {
}
export function Optional(type) {
export function Optional(type: any) {
let stub = {};
type(stub, "(optional-element)");
let elementProp = mkChk(stub).props[0];
@ -230,7 +251,7 @@ export namespace Checkable {
}
function mkChk(target) {
function mkChk(target: any) {
let chk = target[chkSym];
if (!chk) {
chk = {props: []};

View File

@ -27,9 +27,10 @@ import {Denomination} from "./types";
import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet";
import {PayCoinInfo} from "./types";
type RegistryEntry = {resolve: any; reject: any};
export class CryptoApi {
private nextRpcId: number = 1;
private rpcRegistry = {};
private rpcRegistry: {[n: number]: RegistryEntry} = {};
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++;
this.rpcRegistry[id] = {resolve, reject};
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) => {
let msg = {
operation: methodName,

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@
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("${$}", "$");
return url;
@ -42,7 +42,7 @@ export function amountToPretty(amount: AmountJson): string {
*
* See http://api.taler.net/wallet.html#general
*/
export function canonicalizeBaseUrl(url) {
export function canonicalizeBaseUrl(url: string) {
let x = new URI(url);
if (!x.protocol()) {
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);
if (!res) {
return null;
return undefined;
}
return {
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)});
}
postForm(url: string|uri.URI, form) {
postForm(url: string|uri.URI, form: any) {
return this.req("post", url, {req: form});
}
}
export class RequestException {
constructor(detail) {
constructor(detail: any) {
}
}

View File

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

View File

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

View File

@ -154,12 +154,12 @@ interface Transaction {
export interface Badge {
setText(s: string): void;
setColor(c: string): void;
startBusy();
stopBusy();
startBusy(): void;
stopBusy(): void;
}
function deepEquals(x, y) {
function deepEquals(x: any, y: any): boolean {
if (x === y) {
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]*)\)\/?/);
if (!m) {
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);
}
@ -211,13 +211,13 @@ interface HttpRequestLibrary {
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));
}
@ -240,11 +240,18 @@ interface KeyUpdateInfo {
function getWithdrawDenomList(amountAvailable: AmountJson,
denoms: Denomination[]): Denomination[] {
let remaining = Amounts.copy(amountAvailable);
let ds: Denomination[] = [];
const ds: Denomination[] = [];
console.log("available denoms");
console.log(denoms);
denoms = denoms.filter(isWithdrawableDenom);
denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
console.log("withdrawable denoms");
console.log(denoms);
// This is an arbitrary number of coins
// we can withdraw in one go. It's not clear if this limit
// is useful ...
@ -355,7 +362,7 @@ export class Wallet {
let x: number;
function storeExchangeCoin(mc, url) {
function storeExchangeCoin(mc: any, url: string) {
let exchange: IExchangeInfo = mc[0];
console.log("got coin for exchange", url);
let coin: Coin = mc[1];
@ -366,18 +373,16 @@ export class Wallet {
exchange.baseUrl);
return;
}
let cd = {
coin: coin,
denom: exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub)
};
if (!cd.denom) {
let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub);
if (!denom) {
console.warn("denom not found (database inconsistent)");
return;
}
if (cd.denom.value.currency !== paymentAmount.currency) {
if (denom.value.currency !== paymentAmount.currency) {
console.warn("same pubkey for different currencies");
return;
}
let cd = {coin, denom};
let x = m[url];
if (!x) {
m[url] = [cd];
@ -464,7 +469,7 @@ export class Wallet {
private recordConfirmPay(offer: Offer,
payCoinInfo: PayCoinInfo,
chosenExchange: string): Promise<void> {
let payReq = {};
let payReq: any = {};
payReq["amount"] = offer.contract.amount;
payReq["coins"] = payCoinInfo.map((x) => x.sig);
payReq["H_contract"] = offer.H_contract;
@ -581,7 +586,7 @@ export class Wallet {
* Retrieve all necessary information for looking up the contract
* with the given hash.
*/
executePayment(H_contract): Promise<any> {
executePayment(H_contract: string): Promise<any> {
return Promise.resolve().then(() => {
return Query(this.db)
.get("transactions", H_contract)
@ -607,7 +612,7 @@ export class Wallet {
* First fetch information requred to withdraw from the reserve,
* then deplete the reserve, withdrawing coins until it is empty.
*/
private processReserve(reserveRecord): void {
private processReserve(reserveRecord: any): void {
let retryDelayMs = 100;
const opId = "reserve-" + reserveRecord.reserve_pub;
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)
.then((c) => this.storeCoin(c))
.catch((e) => {
@ -803,7 +808,7 @@ export class Wallet {
/**
* 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 denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
denomsAvailable);
@ -912,7 +917,7 @@ export class Wallet {
* Optionally link the reserve entry to the new or existing
* exchange entry in then DB.
*/
updateExchangeFromUrl(baseUrl): Promise<IExchangeInfo> {
updateExchangeFromUrl(baseUrl: string): Promise<IExchangeInfo> {
baseUrl = canonicalizeBaseUrl(baseUrl);
let reqUrl = URI("keys").absoluteTo(baseUrl);
return this.http.get(reqUrl).then((resp) => {
@ -927,8 +932,8 @@ export class Wallet {
private updateExchangeFromJson(baseUrl: string,
exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
let updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
if (!updateTimeSec) {
const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
if (updateTimeSec === null) {
throw Error("invalid update time");
}
@ -947,7 +952,7 @@ export class Wallet {
console.log("making fresh exchange");
} else {
if (updateTimeSec < r.last_update_time) {
console.log("outdated /keys, not updating")
console.log("outdated /keys, not updating");
return Promise.resolve(r);
}
exchangeInfo = r;
@ -966,9 +971,9 @@ export class Wallet {
{indexName: "exchangeBaseUrl", only: baseUrl})
.reduce((coin: Coin, suspendedCoins: Coin[]) => {
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[]) => {
let q = Query(this.db);
@ -999,8 +1004,8 @@ export class Wallet {
let found = false;
for (let oldDenom of exchangeInfo.all_denoms) {
if (oldDenom.denom_pub === newDenom.denom_pub) {
let a = Object.assign({}, oldDenom);
let b = Object.assign({}, newDenom);
let a: any = Object.assign({}, oldDenom);
let b: any = Object.assign({}, newDenom);
// pub hash is only there for convenience in the wallet
delete a["pub_hash"];
delete b["pub_hash"];
@ -1048,7 +1053,7 @@ export class Wallet {
* that is currenctly available for spending in the wallet.
*/
getBalances(): Promise<any> {
function collectBalances(c: Coin, byCurrency) {
function collectBalances(c: Coin, byCurrency: any) {
if (c.suspended) {
return byCurrency;
}
@ -1074,7 +1079,7 @@ export class Wallet {
* Retrive the full event history for this wallet.
*/
getHistory(): Promise<any> {
function collect(x, acc) {
function collect(x: any, acc: any) {
acc.push(x);
return acc;
}
@ -1099,7 +1104,7 @@ export class Wallet {
[contract.merchant_pub, contract.repurchase_correlation_id])
.then((result: Transaction) => {
console.log("db result", result);
let isRepurchase;
let isRepurchase: boolean;
if (result) {
console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id);
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 {BrowserHttpLib} from "./http";
import {Checkable} from "./checkable";
@ -48,11 +54,17 @@ function makeHandlers(db: IDBDatabase,
return exportDb(db);
},
["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) {
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++) {
tx.objectStore(db.objectStoreNames[i]).clear();
}
@ -81,7 +93,7 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmReserve(req);
},
["confirm-pay"]: function(detail, sender) {
let offer;
let offer: Offer;
try {
offer = Offer.checked(detail.offer);
} catch (e) {
@ -100,7 +112,7 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmPay(offer);
},
["check-pay"]: function(detail, sender) {
let offer;
let offer: Offer;
try {
offer = Offer.checked(detail.offer);
} 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) {
Promise
.resolve()
.then(() => {
const p = handlers[req.type](req.detail, sender);
return p.then((r) => {
return p.then((r: any) => {
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() {
chrome.browserAction.setBadgeText({text: ""});
chrome.tabs.query({}, function(tabs) {
for (let tab of tabs) {
if (!tab.url) {
if (!tab.url || !tab.id) {
return;
}
let uri = URI(tab.url);
@ -255,11 +313,14 @@ export function wxMain() {
console.error("could not open database");
console.error(e);
})
.then((db) => {
.then((db: IDBDatabase) => {
let http = new BrowserHttpLib();
let badge = new ChromeBadge();
let notifier = new ChromeNotifier();
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);
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
try {
@ -276,6 +337,19 @@ export function wxMain() {
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) => {
console.error("could not initialize wallet messaging");

View File

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

View File

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

View File

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

View File

@ -26,11 +26,11 @@
import MithrilComponent = _mithril.MithrilComponent;
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
import m from "mithril";
import {Contract} from "../lib/wallet/types";
import {Contract, AmountJson} from "../lib/wallet/types";
"use strict";
function prettyAmount(amount) {
function prettyAmount(amount: AmountJson) {
let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`;
}
@ -40,7 +40,7 @@ const Details = {
controller() {
return {collapsed: m.prop(true)};
},
view(ctrl, contract: Contract) {
view(ctrl: any, contract: Contract) {
if (ctrl.collapsed()) {
return m("div", [
m("button.linky", {
@ -71,11 +71,11 @@ export function main() {
let offer = JSON.parse(query.offer);
console.dir(offer);
let contract = offer.contract;
let error = null;
let error: string|null = null;
let payDisabled = true;
var Contract = {
view(ctrl) {
view(ctrl: any) {
return [
m("p",
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() {
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/system-csp-production.src.js"></script>
<script src="../lib/vendor/jed.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/i18n.js"></script>
<script src="../i18n/strings.js"></script>
<script src="../lib/module-trampoline.js"></script>
<style>

View File

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

View File

@ -29,12 +29,15 @@
"use strict";
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 i18n: any;
function onUpdateNotification(f) {
function onUpdateNotification(f: () => void) {
let port = chrome.runtime.connect({name: "notifications"});
port.onMessage.addListener((msg, port) => {
f();
@ -56,7 +59,7 @@ export function main() {
console.log("this is popup");
function makeTab(target, name) {
function makeTab(target: string, name: string) {
let cssClass = "";
if (target == m.route()) {
cssClass = "active";
@ -79,8 +82,8 @@ namespace WalletNavBar {
}
function openInExtension(element, isInitialized) {
element.addEventListener("click", (e) => {
function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
element.addEventListener("click", (e: Event) => {
chrome.tabs.create({
"url": element.href
});
@ -88,13 +91,15 @@ function openInExtension(element, isInitialized) {
});
}
namespace WalletBalance {
export function controller() {
return new Controller();
}
class Controller {
myWallet;
myWallet: any;
gotError = false;
constructor() {
@ -128,7 +133,7 @@ namespace WalletBalance {
if (!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) {
return listing;
}
@ -141,13 +146,13 @@ namespace WalletBalance {
}
function formatTimestamp(t) {
function formatTimestamp(t: number) {
let x = new Date(t);
return x.toLocaleString();
}
function formatAmount(amount) {
function formatAmount(amount: AmountJson) {
let v = amount.value + amount.fraction / 1e6;
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() {
chrome.tabs.create({
"url": substituteFulfillmentUrl(url,
@ -168,7 +173,7 @@ function retryPayment(url, contractHash) {
}
function formatHistoryItem(historyItem) {
function formatHistoryItem(historyItem: any) {
const d = historyItem.detail;
const t = historyItem.timestamp;
console.log("hist item", historyItem);
@ -210,7 +215,7 @@ namespace WalletHistory {
}
class Controller {
myHistory;
myHistory: any;
gotError = false;
constructor() {
@ -287,7 +292,7 @@ var WalletDebug = {
};
function openExtensionPage(page) {
function openExtensionPage(page: string) {
return function() {
chrome.tabs.create({
"url": chrome.extension.getURL(page)
@ -296,7 +301,7 @@ function openExtensionPage(page) {
}
function openTab(page) {
function openTab(page: string) {
return function() {
chrome.tabs.create({
"url": page

View File

@ -1,9 +1,9 @@
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() {
let x = new Emsc.Amount({value: 42, fraction: 42, currency: "EUR"});

View File

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