Replace handlebars with mithril, hooks for i18n.

The wallet is now a single page application.
This commit is contained in:
Florian Dold 2016-01-24 02:29:13 +01:00
parent 1a0a302ad9
commit b8627813be
30 changed files with 19255 additions and 382 deletions

View File

@ -38,5 +38,6 @@ System.import("../lib/wallet/wxmessaging")
wxmessaging.wxMain();
})
.catch((e) => {
console.error("import failed", e.stack);
console.log("wallet failed");
console.error(e.stack);
});

View File

@ -17,6 +17,7 @@
// query the availability of Taler.
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
"use strict";
console.log("Taler injected");
document.addEventListener("taler-probe", function (e) {
var evt = new Event("taler-wallet-present");
document.dispatchEvent(evt);

View File

@ -21,6 +21,8 @@
"use strict";
console.log("Taler injected");
document.addEventListener("taler-probe", function(e) {
let evt = new Event("taler-wallet-present");
document.dispatchEvent(evt);
@ -99,4 +101,4 @@ document.addEventListener('taler-execute-payment', function(e: CustomEvent) {
document.dispatchEvent(new CustomEvent("taler-payment-result", {detail: detail}));
};
});
});
});

View File

@ -56,7 +56,7 @@ const paths = {
"lib/vendor/*",
"lib/emscripten/libwrapper.js"
],
}
};

View File

@ -1,29 +0,0 @@
/*
This file is part of TALER
(C) 2016 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, If not, see <http://www.gnu.org/licenses/>
*/
Handlebars.registerHelper("prettyAmount", function (amount) {
let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`;
});
Handlebars.registerHelper("prettyAmountNoCurrency", function (amount) {
let v = amount.value + amount.fraction / 1e6;
return v.toFixed(2);
});
Handlebars.registerHelper('objectStringifier', function (o) {
return JSON.stringify(o);
});

16525
extension/lib/decl/lodash.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,25 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
declare var i18n: any;
"use strict";
var i18n = <any>function i18n(strings, ...values) {
// TODO: actually look up translation
return String.raw(strings, ...values);
};
document.addEventListener("DOMContentLoaded", (e) => {
// Interpolate i8nized values with arbitrary objects and
// return array of strings/objects.
i18n.parts = function(strings, ...values) {
let parts = [];
for (let i = 0; i < strings.length; i++) {
parts.push(strings[i]);
if (i < values.length) {
parts.push(values[i]);
}
}
return parts;
};
});

879
extension/lib/mithril.d.ts vendored Normal file
View File

@ -0,0 +1,879 @@
// Mithril type definitions for Typescript
/**
* This is the module containing all the types/declarations/etc. for Mithril
*/
declare interface MithrilStatic {
/**
* Creates a virtual element for use with m.render, m.mount, etc.
*
* @param selector A simple CSS selector. May include SVG tags. Nested
* selectors are not supported.
* @param attributes Attributes to add. Any DOM attribute may be used
* as an attribute, although innerHTML and the like may be overwritten
* silently.
* @param children Child elements, components, and text to add.
* @return A virtual element.
*
* @see m.render
* @see m.mount
* @see m.component
*/
<T extends MithrilController>(selector: string,
attributes: MithrilAttributes,
...children: Array<string |
MithrilVirtualElement<T> |
MithrilComponent<T>>): MithrilVirtualElement<T>;
/**
* Initializes a component for use with m.render, m.mount, etc.
*
* @param component A component.
* @param args Arguments to optionally pass to the component.
* @return A component.
*
* @see m.render
* @see m.mount
* @see m
*/
<T extends MithrilController>(component: MithrilComponent<T>,
...args: any[]): MithrilComponent<T>;
/**
* Creates a virtual element for use with m.render, m.mount, etc.
*
* @param selector A simple CSS selector. Nested selectors are not
* supported.
* @param children Child elements, components, and text to add.
* @return A virtual element.
*
* @see m.render
* @see m.mount
* @see m.component
*/
<T extends MithrilController>(selector: string,
...children: Array<string |
MithrilVirtualElement<T> |
MithrilComponent<T>>): MithrilVirtualElement<T>;
/**
* Initializes a component for use with m.render, m.mount, etc.
* Shorthand for m.component.
*
* @param selector A component.
* @param args Arguments to optionally pass to the component.
* @return A component.
*
* @see m.render
* @see m.mount
* @see m.component
*/
<T extends MithrilController>(component: MithrilComponent<T>,
...args: any[]): MithrilComponent<T>;
/**
* Creates a getter-setter function that wraps a Mithril promise. Useful
* for uniform data access, m.withAttr, etc.
*
* @param promise A thennable to initialize the property with. It may
* optionally be a Mithril promise.
* @return A getter-setter function wrapping the promise.
*
* @see m.withAttr
*/
prop<T>(promise: Thennable<T>) : MithrilPromiseProperty<T>;
/**
* Creates a getter-setter function that wraps a simple value. Useful
* for uniform data access, m.withAttr, etc.
*
* @param value A value to initialize the property with
* @return A getter-setter function wrapping the value.
*
* @see m.withAttr
*/
prop<T>(value: T): MithrilBasicProperty<T>;
/**
* Creates a getter-setter function that wraps a simple value. Useful
* for uniform data access, m.withAttr, etc.
*
* @return A getter-setter function wrapping the value.
*
* @see m.withAttr
*/
prop<T>(): MithrilBasicProperty<T>;
/**
* Returns a event handler that can be bound to an element, firing with
* the specified property.
*
* @param property The property to get from the event.
* @param callback The handler to use the value from the event.
* @return A function suitable for listening to an event.
*/
withAttr(property: string,
callback: (value: any) => void,
callbackThis: any): (e: Event) => any;
/**
* @deprecated Use m.mount instead
*/
module<T extends MithrilController>(rootElement: Node,
component: MithrilComponent<T>): T;
/**
* Mounts a component to a base DOM node.
*
* @param rootElement The base node.
* @param component The component to mount.
* @return An instance of the top-level component's controller
*/
mount<T extends MithrilController>(rootElement: Node,
component: MithrilComponent<T>): T;
/**
* Initializes a component for use with m.render, m.mount, etc.
*
* @param selector A component.
* @param args Arguments to optionally pass to the component.
* @return A component.
*
* @see m.render
* @see m.mount
* @see m
*/
component<T extends MithrilController>(component: MithrilComponent<T>,
...args: any[]): MithrilComponent<T>;
/**
* Trust this string of HTML.
*
* @param html The HTML to trust
* @return A String object instance with an added internal flag to mark
* it as trusted.
*/
trust(html: string): MithrilTrustedString;
/**
* Render a virtual DOM tree.
*
* @param rootElement The base element/node to render the tree from.
* @param children One or more child nodes to add to the tree.
* @param forceRecreation If true, overwrite the entire tree without
* diffing against it.
*/
render<T extends MithrilController>(rootElement: Element,
children: MithrilVirtualElement<T>|MithrilVirtualElement<T>[],
forceRecreation?: boolean): void;
redraw: {
/**
* Force a redraw the active component. It redraws asynchronously by
* default to allow for simultaneous events to run before redrawing,
* such as the event combination keypress + input frequently used for
* input.
*
* @param force If true, redraw synchronously.
*/
(force?: boolean): void;
strategy: {
/**
* Gets the current redraw strategy, which returns one of the
* following:
*
* "all" - recreates the DOM tree from scratch
* "diff" - recreates the DOM tree from scratch
* "none" - leaves the DOM tree intact
*
* This is useful for event handlers, which may want to cancel
* the next redraw if the event doesn't update the UI.
*
* @return The current strategy
*/
(): string;
/**
* Sets the current redraw strategy. The parameter must be one of
* the following values:
*
* "all" - recreates the DOM tree from scratch
* "diff" - recreates the DOM tree from scratch
* "none" - leaves the DOM tree intact
*
* This is useful for event handlers, which may want to cancel
* the next redraw if the event doesn't update the UI.
*
* @param value The value to set
* @return The new strategy
*/
(value: string): string;
/**
* @private
* Implementation detail - it's a MithrilBasicProperty instance
*/
toJSON(): string;
}
}
route: {
/**
* Enable routing, mounting a controller based on the route. It
* automatically mounts the components for you, starting with the one
* specified by the default route.
*
* @param rootElement The element to mount the active controller to.
* @param defaultRoute The route to start with.
* @param routes A key-value mapping of pathname to controller.
*/
<T extends MithrilController>(rootElement: Element,
defaultRoute: string,
routes: MithrilRoutes): void;
/**
* This allows m.route to be used as the `config` attribute for a
* virtual element, particularly useful for cases like this:
*
* ```ts
* // Note that the '#' is not required in `href`, thanks to the
* `config` setting.
* m("a[href='/dashboard/alicesmith']", {config: m.route});
* ```
*/
<T extends MithrilController>(element: Element,
isInitialized: boolean,
context?: MithrilContext,
vdom?: MithrilVirtualElement<T>): void;
/**
* Programmatically redirect to another route.
*
* @param path The route to go to.
* @param params Parameters to pass as a query string.
* @param shouldReplaceHistory Whether to replace the current history
* instead of adding a new one.
*/
(path: string, params?: any, shouldReplaceHistory?: boolean): void;
/**
* Gets the current route.
*
* @return The current route.
*/
(): string;
/**
* Gets a route parameter.
*
* @param key The key to get.
* @return The value associated with the parameter key.
*/
param(key: string): string;
/**
* The current routing mode. This may be changed before calling
* m.route to change the part of the URL used to perform the routing.
*
* The value can be set to one of the following, defaulting to
* "hash":
*
* "search" - Uses the query string. This allows for named anchors to
* work on the page, but changes cause IE8 and lower to refresh the
* page.
*
* "hash" - Uses the hash. This is the only routing mode that does
* not cause page refreshes on any browser, but it does not support
* named anchors.
*
* "pathname" - Uses the URL pathname. This requires server-side
* setup to support bookmarking and page refreshes. It always causes
* page refreshes on IE8 and lower. Note that this requires that the
* application to be run from the root of the URL.
*/
mode: string;
/**
* Serialize an object into a query string.
*
* @param data The data to serialize.
* @return The serialized string.
*/
buildQueryString(data: Object): String
/**
* Parse a query string into an object.
*
* @param data The data to parse.
* @return The parsed object data.
*/
parseQueryString(data: String): Object
}
/**
* Send a request to a server to server. Note that the `url` option is
* required.
*
* @param options The options to use
* @return A promise to the returned data for "GET" requests, or a void
* promise for any other request type.
*
* @see MithrilXHROptions for the available options.
*/
request<T>(options: MithrilXHROptions<T>): MithrilPromise<T>;
deferred: {
/**
* Create a Mithril deferred object. It behaves synchronously if
* possible, an intentional deviation from Promises/A+. Note that
* deferreds are completely separate from the redrawing system, and
* never trigger a redraw on their own.
*
* @return A new Mithril deferred instance.
*
* @see m.deferred.onerror for the error callback called for Error
* subclasses
*/
<T>(): MithrilDeferred<T>;
/**
* A callback for all uncaught native Error subclasses in deferreds.
* This defaults to synchronously rethrowing all errors, a deviation
* from Promises/A+, but the behavior is configurable. To restore
* Promises/A+-compatible behavior. simply set this to a no-op.
*/
onerror(e: Error): void;
}
/**
* Takes a list of promises or thennables and returns a Mithril promise
* that resolves once all in the list are resolved, or rejects if any of
* them reject.
*
* @param promises A list of promises to try to resolve.
* @return A promise that resolves to all the promises if all resolve, or
* rejects with the error contained in the first rejection.
*/
sync<T>(promises: Thennable<T>[]): MithrilPromise<T[]>;
/**
* Use this and endComputation if your views aren't redrawing after
* calls to third-party libraries. For integrating asynchronous code,
* this should be called before any asynchronous work is done. For
* synchronous code, this should be called at the beginning of the
* problematic segment. Note that these calls must be balanced, much like
* braces and parentheses. This is mostly used internally. Prefer
* m.redraw where possible, especially when making repeated calls.
*
* @see endComputation
* @see m.render
*/
startComputation(): void;
/**
* Use startComputation and this if your views aren't redrawing after
* calls to third-party libraries. For integrating asynchronous code,
* this should be called after all asynchronous work completes. For
* synchronous code, this should be called at the end of the problematic
* segment. Note that these calls must be balanced, much like braces and
* parentheses. This is mostly used internally. Prefer m.redraw where
* possible, especially when making repeated calls.
*
* @see startComputation
* @see m.render
*/
endComputation(): void;
/**
* This overwrites the internal version of window used by Mithril.
* It's mostly useful for testing, and is also used internally by
* Mithril to test itself. By default Mithril uses `window` for the
* dependency.
*
* @param mockWindow The mock to use for the window.
* @return The mock that was passed in.
*/
deps(mockWindow: Window): Window;
}
interface MithrilTrustedString extends String {
/** @private Implementation detail. Don't depend on it. */
$trusted: boolean;
}
/**
* The interface for a virtual element. It's best to consider this immutable
* for most use cases.
*
* @see m
*/
interface MithrilVirtualElement<T extends MithrilController> {
/**
* A key to optionally associate with this element.
*/
key?: number;
/**
* The tag name of this element.
*/
tag?: string;
/**
* The attributes of this element.
*/
attrs?: MithrilAttributes;
/**
* The children of this element.
*/
children?: Array<string|MithrilVirtualElement<T>|MithrilComponent<T>>;
}
/**
* An event passed by Mithril to unload event handlers.
*/
interface MithrilEvent {
/**
* Prevent the default behavior of scrolling the page and updating the
* URL on next route change.
*/
preventDefault(): void;
}
/**
* A context object for configuration functions.
*
* @see MithrilElementConfig
*/
interface MithrilContext {
/**
* A function to call when the node is unloaded. Useful for cleanup.
*/
onunload?(): any;
/**
* Set true if the backing DOM node needs to be retained between route
* changes if possible. Set false if this node needs to be recreated
* every single time, regardless of how "different" it is.
*/
retain?: boolean;
}
/**
* This represents a callback function for a virtual element's config
* attribute. It's a low-level function useful for extra cleanup after
* removal from the tree, storing instances of third-party classes that
* need to be associated with the DOM, etc.
*
* @see MithrilAttributes
* @see MithrilContext
*/
interface MithrilElementConfig {
/**
* A callback function for a virtual element's config attribute.
*
* @param element The associated DOM element.
* @param isInitialized Whether this is the first call for the virtual
* element or not.
* @param context The associated context for this element.
* @param vdom The associated virtual element.
*/
<T extends MithrilController>(element: Element,
isInitialized: boolean,
context: MithrilContext,
vdom: MithrilVirtualElement<T>): void;
}
/**
* This represents the attributes available for configuring virtual elements,
* beyond the applicable DOM attributes.
*
* @see m
*/
interface MithrilAttributes {
/**
* The class name(s) for this virtual element, as a space-separated list.
*/
className?: string;
/**
* The class name(s) for this virtual element, as a space-separated list.
*/
class?: string;
/**
* A custom, low-level configuration in case this element needs special
* cleanup after removal from the tree.
*
* @see MithrilElementConfig
*/
config?: MithrilElementConfig;
/**
* Any other virtual element properties including attributes and
* event handlers
*/
[property: string]: any;
}
/**
* The basis of a Mithril controller instance.
*/
interface MithrilController {
/**
* An optional handler to call when the associated virtual element is
* destroyed.
*
* @param evt An associated event.
*/
onunload?(evt: MithrilEvent): any;
}
/**
* This represents a controller function.
*
* @see MithrilControllerConstructor
*/
interface MithrilControllerFunction<T extends MithrilController> {
(): T;
}
/**
* This represents a controller constructor.
*
* @see MithrilControllerFunction
*/
interface MithrilControllerConstructor<T extends MithrilController> {
new(): T;
}
/**
* This represents a view factory.
*/
interface MithrilView<T extends MithrilController> {
/**
* Creates a view out of virtual elements.
*/
(ctrl: T): MithrilVirtualElement<T>;
}
/**
* This represents a Mithril component.
*
* @see m
* @see m.component
*/
interface MithrilComponent<T extends MithrilController> {
/**
* The component's controller.
*
* @see m.component
*/
controller?: MithrilControllerFunction<T> |
MithrilControllerConstructor<T>;
/**
* Creates a view out of virtual elements.
*
* @see m.component
*/
view(ctrl: T): MithrilVirtualElement<T>;
}
/**
* This is the base interface for property getter-setters
*
* @see m.prop
*/
interface MithrilProperty<T> {
/**
* Gets the contained value.
*
* @return The contained value.
*/
(): T;
/**
* Sets the contained value.
*
* @param value The new value to set.
* @return The newly set value.
*/
(value: T): T;
}
/**
* This represents a non-promise getter-setter functions.
*
* @see m.prop which returns objects that implement this interface.
*/
interface MithrilBasicProperty<T> extends MithrilProperty<T> {
/**
* Makes this serializable to JSON.
*/
toJSON(): T;
}
/**
* This represents a promise getter-setter function.
*
* @see m.prop which returns objects that implement this interface.
*/
interface MithrilPromiseProperty<T> extends MithrilPromise<T>,
MithrilProperty<MithrilPromise<T>> {
/**
* Gets the contained promise.
*
* @return The contained value.
*/
(): MithrilPromise<T>;
/**
* Sets the contained promise.
*
* @param value The new value to set.
* @return The newly set value.
*/
(value: MithrilPromise<T>): MithrilPromise<T>;
/**
* Sets the contained wrapped value.
*
* @param value The new value to set.
* @return The newly set value.
*/
(value: T): MithrilPromise<T>;
}
/**
* This represents a key-value mapping linking routes to components.
*/
interface MithrilRoutes {
/**
* The key represents the route. The value represents the corresponding
* component.
*/
[key: string]: MithrilComponent<MithrilController>;
}
/**
* This represents a Mithril deferred object.
*/
interface MithrilDeferred<T> {
/**
* Resolve this deferred's promise with a value.
*
* @param value The value to resolve the promise with.
*/
resolve(value?: T): void;
/**
* Reject this deferred with an error.
*
* @param value The reason for rejecting the promise.
*/
reject(reason?: any): void;
/**
* The backing promise.
*
* @see MithrilPromise
*/
promise: MithrilPromise<T>;
}
/**
* This represents a thennable success callback.
*/
interface MithrilSuccessCallback<T, U> {
(value: T): U | Thennable<U>;
}
/**
* This represents a thennable error callback.
*/
interface MithrilErrorCallback<T> {
(value: Error): T | Thennable<T>;
}
/**
* This represents a thennable.
*/
interface Thennable<T> {
then<U>(success: (value: T) => U): Thennable<U>;
then<U,V>(success: (value: T) => U,
error: (value: Error) => V): Thennable<U>|Thennable<V>;
catch?: <U>(error: (value: Error) => U) => Thennable<U>;
}
/**
* This represents a Mithril promise object.
*/
interface MithrilPromise<T> extends Thennable<T>, MithrilProperty<MithrilPromise<T>> {
/**
* Chain this promise with a simple success callback, propogating
* rejections.
*
* @param success The callback to call when the promise is resolved.
* @return The chained promise.
*/
then<U>(success: MithrilSuccessCallback<T,U>): MithrilPromise<U>;
/**
* Chain this promise with a success callback and error callback, without
* propogating rejections.
*
* @param success The callback to call when the promise is resolved.
* @param error The callback to call when the promise is rejected.
* @return The chained promise.
*/
then<U, V>(success: MithrilSuccessCallback<T, U>,
error: MithrilErrorCallback<V>): MithrilPromise<U> | MithrilPromise<V>;
/**
* Chain this promise with a single error callback, without propogating
* rejections.
*
* @param error The callback to call when the promise is rejected.
* @return The chained promise.
*/
catch<U>(error: MithrilErrorCallback<U>): MithrilPromise<T> |
MithrilPromise<U>;
}
/**
* This represents the available options for configuring m.request.
*
* @see m.request
*/
interface MithrilXHROptions<T> {
/**
* This represents the HTTP method used, one of the following:
*
* - "GET" (default)
* - "POST"
* - "PUT"
* - "DELETE"
* - "HEAD"
* - "OPTIONS"
*/
method?: string;
/**
* The URL to send the request to.
*/
url: string;
/**
* The username for HTTP authentication.
*/
user?: string;
/**
* The password for HTTP authentication.
*/
password?: string;
/**
* The data to be sent. It's automatically serialized in the right format
* depending on the method (with exception of HTML5 FormData), and put in
* the appropriate section of the request.
*/
data?: any;
/**
* Whether to run it in the background, i.e. true if it doesn't affect
* template rendering.
*/
background?: boolean;
/**
* Set an initial value while the request is working, to populate the
* promise getter-setter.
*/
initialValue?: T;
/**
* An optional preprocessor function to unwrap a successful response, in
* case the response contains metadata wrapping the data.
*
* @param data The data to unwrap.
* @return The unwrapped result.
*/
unwrapSuccess?(data: any): T;
/**
* An optional preprocessor function to unwrap an unsuccessful response,
* in case the response contains metadata wrapping the data.
*
* @param data The data to unwrap.
* @return The unwrapped result.
*/
unwrapError?(data: any): T;
/**
* An optional function to serialize the data. This defaults to
* `JSON.stringify`.
*
* @param dataToSerialize The data to serialize.
* @return The serialized form as a string.
*/
serialize?(dataToSerialize: any): string;
/**
* An optional function to deserialize the data. This defaults to
* `JSON.parse`.
*
* @param dataToSerialize The data to parse.
* @return The parsed form.
*/
deserialize?(dataToDeserialize: string): any;
/**
* An optional function to extract the data from a raw XMLHttpRequest,
* useful if the relevant data is in a response header or the status
* field.
*
* @param xhr The associated XMLHttpRequest.
* @param options The options passed to this request.
* @return string The serialized format.
*/
extract?(xhr: XMLHttpRequest, options: MithrilXHROptions<T>): string;
/**
* The parsed data, or its children if it's an array, will be passed to
* this class constructor if it's given, to parse it into classes.
*
* @param data The data to parse.
* @return The new instance for the list.
*/
type?: new (data: Object) => any;
/**
* An optional function to run between `open` and `send`, useful for
* adding request headers or using XHR2 features such as the `upload`
* property. It is even possible to override the XHR altogether with a
* similar object, such as an XDomainRequest instance.
*
* @param xhr The associated XMLHttpRequest.
* @param options The options passed to this request.
* @return The new XMLHttpRequest, or nothing if the same one is kept.
*/
config?(xhr: XMLHttpRequest, options: MithrilXHROptions<T>): any;
/**
* For JSONP requests, this must be the string "jsonp". Otherwise, it's
* ignored.
*/
dataType?: string;
/**
* For JSONP requests, this is the query string key for the JSONP
* request. This is useful for APIs that don't use common conventions,
* such as `www.example.com/?jsonpCallback=doSomething`. It defaults to
* `callback` for JSONP requests, and is ignored for any other kind of
* request.
*/
callbackKey?: string;
}

View File

@ -1,40 +0,0 @@
/*
This file is part of TALER
(C) 2016 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, If not, see <http://www.gnu.org/licenses/>
*/
/**
* Implement the "React" namespace so that we can use TSX literals.
* Just returns plain DOM elements, no fancy virtual DOM.
*/
"use strict";
let React = {
createElement: function(tag, props, ...children) {
let e = document.createElement(tag);
for (let k in props) {
e.setAttribute(k, props[k]);
}
for (let child of children) {
if ("string" === typeof child || "number" === typeof child) {
child = document.createTextNode(child);
}
e.appendChild(child);
}
return e;
}
};

View File

@ -3,4 +3,4 @@
/// <reference path="decl/lib.es6.d.ts" />
/// <reference path="decl/urijs/URIjs.d.ts" />
/// <reference path="decl/systemjs/systemjs.d.ts" />
/// <reference path="decl/systemjs/systemjs.d.ts" />

29
extension/lib/vendor/lodash.core.min.js vendored Normal file
View File

@ -0,0 +1,29 @@
/**
* @license
* lodash 4.0.0 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
* Build: `lodash core -o ./dist/lodash.core.js`
*/
;(function(){function n(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function t(n,t,r){for(var e=-1,u=n.length;++e<u;){var o=n[e],i=t(o);if(null!=i&&(c===ln?i===i:r(i,c)))var c=i,f=o}return f}function r(n,t,r){var e;return r(n,function(n,r,u){return t(n,r,u)?(e=n,false):void 0}),e}function e(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function u(n,t){return w(t,function(t){return n[t]})}function o(n){return n&&n.Object===Object?n:null}function i(n){return vn[n];
}function c(n){var t=false;if(null!=n&&typeof n.toString!="function")try{t=!!(n+"")}catch(r){}return t}function f(n,t){return n=typeof n=="number"||hn.test(n)?+n:-1,n>-1&&0==n%1&&(null==t?9007199254740991:t)>n}function a(n){if(Z(n)&&!Vn(n)){if(n instanceof l)return n;if(En.call(n,"__wrapped__")){var t=new l(n.__wrapped__,n.__chain__);return t.__actions__=k(n.__actions__),t}}return new l(n)}function l(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t}function p(n,t,r,e){return n===ln||H(n,xn[r])&&!En.call(e,r)?t:n;
}function s(n,t,r){if(typeof n!="function")throw new TypeError("Expected a function");return setTimeout(function(){n.apply(ln,r)},t)}function h(n,t){var r=true;return $n(n,function(n,e,u){return r=!!t(n,e,u)}),r}function v(n,t){var r=[];return $n(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function y(t,r,e,u){u||(u=[]);for(var o=-1,i=t.length;++o<i;){var c=t[o];Z(c)&&Q(c)&&(e||Vn(c)||L(c))?r?y(c,r,e,u):n(u,c):e||(u[u.length]=c)}return u}function _(n,t){return n&&qn(n,t,un)}function g(n,t){return v(t,function(t){
return W(n[t])})}function b(n,t,r,e,u){return n===t?true:null==n||null==t||!Y(n)&&!Z(t)?n!==n&&t!==t:j(n,t,b,r,e,u)}function j(n,t,r,e,u,o){var i=Vn(n),f=Vn(t),a="[object Array]",l="[object Array]";i||(a=kn.call(n),"[object Arguments]"==a&&(a="[object Object]")),f||(l=kn.call(t),"[object Arguments]"==l&&(l="[object Object]"));var p="[object Object]"==a&&!c(n),f="[object Object]"==l&&!c(t);return!(l=a==l)||i||p?2&u||(a=p&&En.call(n,"__wrapped__"),f=f&&En.call(t,"__wrapped__"),!a&&!f)?l?(o||(o=[]),(a=C(o,function(t){
return t[0]===n}))&&a[1]?a[1]==t:(o.push([n,t]),t=(i?R:$)(n,t,r,e,u,o),o.pop(),t)):false:r(a?n.value():n,f?t.value():t,e,u,o):I(n,t,a)}function d(n){var t=typeof n;return"function"==t?n:null==n?fn:("object"==t?O:E)(n)}function m(n){n=null==n?n:Object(n);var t,r=[];for(t in n)r.push(t);return r}function w(n,t){var r=-1,e=Q(n)?Array(n.length):[];return $n(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function O(n){var t=un(n),r=t.length;return function(e){if(null==e)return!r;for(e=Object(e);r--;){var u=t[r];if(!(u in e&&b(n[u],e[u],ln,true)))return false;
}return true}}function x(n,t){return n=Object(n),J(t,function(t,r){return r in n&&(t[r]=n[r]),t},{})}function E(n){return function(t){return null==t?ln:t[n]}}function A(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++e<u;)r[e]=n[e+t];return r}function k(n){return A(n,0,n.length)}function N(n,t){var r;return $n(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function S(t,r){return J(r,function(t,r){return r.func.apply(r.thisArg,n([t],r.args))},t);
}function T(n,t,r,e){r||(r={});for(var u=-1,o=t.length;++u<o;){var i=t[u],c=e?e(r[i],n[i],i,r,n):n[i],f=r,a=f[i];(!H(a,c)||H(a,xn[i])&&!En.call(f,i)||c===ln&&!(i in f))&&(f[i]=c)}return r}function F(n){return V(function(t,r){var e=-1,u=r.length,o=u>1?r[u-1]:ln,o=typeof o=="function"?(u--,o):ln;for(t=Object(t);++e<u;){var i=r[e];i&&n(t,i,o)}return t})}function B(n){return function(){var t=arguments,r=In(n.prototype),t=n.apply(r,t);return Y(t)?t:r}}function D(n,t,r){function e(){for(var o=-1,i=arguments.length,c=-1,f=r.length,a=Array(f+i),l=this&&this!==wn&&this instanceof e?u:n;++c<f;)a[c]=r[c];
for(;i--;)a[c++]=arguments[++o];return l.apply(t,a)}if(typeof n!="function")throw new TypeError("Expected a function");var u=B(n);return e}function R(n,t,r,e,u,o){var i=-1,c=1&u,f=n.length,a=t.length;if(f!=a&&!(2&u&&a>f))return false;for(a=true;++i<f;){var l=n[i],p=t[i];if(void 0!==ln){a=false;break}if(c){if(!N(t,function(n){return l===n||r(l,n,e,u,o)})){a=false;break}}else if(l!==p&&!r(l,p,e,u,o)){a=false;break}}return a}function I(n,t,r){switch(r){case"[object Boolean]":case"[object Date]":return+n==+t;case"[object Error]":
return n.name==t.name&&n.message==t.message;case"[object Number]":return n!=+n?t!=+t:n==+t;case"[object RegExp]":case"[object String]":return n==t+""}return false}function $(n,t,r,e,u,o){var i=2&u,c=1&u,f=un(n),a=f.length,l=un(t);if(a!=l.length&&!i)return false;for(var p=a;p--;){var s=f[p];if(!(i?s in t:En.call(t,s))||!c&&s!=l[p])return false}for(c=true;++p<a;){var s=f[p],l=n[s],h=t[s];if(void 0!==ln||l!==h&&!r(l,h,e,u,o)){c=false;break}i||(i="constructor"==s)}return c&&!i&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),
c}function q(n){var t=n?n.length:ln;if(X(t)&&(Vn(n)||tn(n)||L(n))){n=String;for(var r=-1,e=Array(t);++r<t;)e[r]=n(r);t=e}else t=null;return t}function M(n){var t=n&&n.constructor;return n===(typeof t=="function"&&t.prototype||xn)}function z(n){return n?n[0]:ln}function C(n,t){return r(n,d(t),$n)}function G(n,t){return $n(n,typeof t=="function"?t:fn)}function J(n,t,r){return e(n,d(t),r,3>arguments.length,$n)}function P(n){return null==n?0:(n=Q(n)?n:un(n),n.length)}function U(n,t){var r;if(typeof t!="function")throw new TypeError("Expected a function");
return n=Hn(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=ln),r}}function V(n){var t;if(typeof n!="function")throw new TypeError("Expected a function");return t=Rn(t===ln?n.length-1:Hn(t),0),function(){for(var r=arguments,e=-1,u=Rn(r.length-t,0),o=Array(u);++e<u;)o[e]=r[t+e];for(u=Array(t+1),e=-1;++e<t;)u[e]=r[e];return u[t]=o,n.apply(this,u)}}function H(n,t){return n===t||n!==n&&t!==t}function K(n,t){return n>t}function L(n){return Z(n)&&Q(n)&&En.call(n,"callee")&&(!Fn.call(n,"callee")||"[object Arguments]"==kn.call(n));
}function Q(n){return null!=n&&!(typeof n=="function"&&W(n))&&X(Mn(n))}function W(n){return n=Y(n)?kn.call(n):"","[object Function]"==n||"[object GeneratorFunction]"==n}function X(n){return typeof n=="number"&&n>-1&&0==n%1&&9007199254740991>=n}function Y(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function Z(n){return!!n&&typeof n=="object"}function nn(n){return typeof n=="number"||Z(n)&&"[object Number]"==kn.call(n)}function tn(n){return typeof n=="string"||!Vn(n)&&Z(n)&&"[object String]"==kn.call(n);
}function rn(n,t){return t>n}function en(n){return typeof n=="string"?n:null==n?"":n+""}function un(n){var t=M(n);if(!t&&!Q(n))return Dn(Object(n));var r,e=q(n),u=!!e,e=e||[],o=e.length;for(r in n)!En.call(n,r)||u&&("length"==r||f(r,o))||t&&"constructor"==r||e.push(r);return e}function on(n){for(var t=-1,r=M(n),e=m(n),u=e.length,o=q(n),i=!!o,o=o||[],c=o.length;++t<u;){var a=e[t];i&&("length"==a||f(a,c))||"constructor"==a&&(r||!En.call(n,a))||o.push(a)}return o}function cn(n){return n?u(n,un(n)):[];
}function fn(n){return n}function an(t,r,e){var u=un(r),o=g(r,u);null!=e||Y(r)&&(o.length||!u.length)||(e=r,r=t,t=this,o=g(r,un(r)));var i=Y(e)&&"chain"in e?e.chain:true,c=W(t);return $n(o,function(e){var u=r[e];t[e]=u,c&&(t.prototype[e]=function(){var r=this.__chain__;if(i||r){var e=t(this.__wrapped__);return(e.__actions__=k(this.__actions__)).push({func:u,args:arguments,thisArg:t}),e.__chain__=r,e}return u.apply(t,n([this.value()],arguments))})}),t}var ln,pn=/[&<>"'`]/g,sn=RegExp(pn.source),hn=/^(?:0|[1-9]\d*)$/,vn={
"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},yn={"function":true,object:true},_n=yn[typeof exports]&&exports&&!exports.nodeType?exports:null,gn=yn[typeof module]&&module&&!module.nodeType?module:null,bn=o(yn[typeof self]&&self),jn=o(yn[typeof window]&&window),dn=gn&&gn.exports===_n?_n:null,mn=o(yn[typeof this]&&this),wn=o(_n&&gn&&typeof global=="object"&&global)||jn!==(mn&&mn.window)&&jn||bn||mn||Function("return this")(),On=Array.prototype,xn=Object.prototype,En=xn.hasOwnProperty,An=0,kn=xn.toString,Nn=wn._,Sn=wn.f,Tn=Sn?Sn.g:ln,Fn=xn.propertyIsEnumerable,Bn=wn.isFinite,Dn=Object.keys,Rn=Math.max,In=function(){
function n(){}return function(t){if(Y(t)){n.prototype=t;var r=new n;n.prototype=ln}return r||{}}}(),$n=function(n,t){return function(r,e){if(null==r)return r;if(!Q(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}(_),qn=function(n){return function(t,r,e){var u=-1,o=Object(t);e=e(t);for(var i=e.length;i--;){var c=e[n?i:++u];if(false===r(o[c],c,o))break}return t}}();Tn&&!Fn.call({valueOf:1},"valueOf")&&(m=function(n){n=Tn(n);for(var t,r=[];!(t=n.next()).done;)r.push(t.value);
return r});var Mn=E("length"),zn=V(function(t,r){y(r);var e=Vn(t)?t:[Object(t)];return n(k(e),cn)}),Cn=V(function(n,t,r){var e=typeof t=="function";return w(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})}),Gn=Date.now,Jn=V(function(n,t,r){return D(n,t,r)}),Pn=V(function(n,t){return s(n,1,t)}),Un=V(function(n,t,r){return s(n,Kn(t)||0,r)}),Vn=Array.isArray,Hn=Number,Kn=Number,Ln=F(function(n,t){T(t,un(t),n)}),Qn=F(function(n,t){T(t,on(t),n)}),Wn=F(function(n,t,r){T(t,on(t),n,r)}),Xn=V(function(n){
return n.push(ln,p),Wn.apply(ln,n)}),Yn=V(function(n,t){return null==n?{}:x(n,y(t))}),Zn=d;l.prototype=In(a.prototype),l.prototype.constructor=l,a.assignIn=Qn,a.before=U,a.bind=Jn,a.chain=function(n){return n=a(n),n.__chain__=true,n},a.compact=function(n){return v(n,Boolean)},a.concat=zn,a.create=function(n,t){var r=In(n);return t?Ln(r,t):r},a.defaults=Xn,a.defer=Pn,a.delay=Un,a.filter=function(n,t){return v(n,d(t))},a.flatten=function(n){return n&&n.length?y(n):[]},a.flattenDeep=function(n){return n&&n.length?y(n,true):[];
},a.invokeMap=Cn,a.iteratee=Zn,a.keys=un,a.map=function(n,t){return w(n,d(t))},a.mixin=an,a.negate=function(n){if(typeof n!="function")throw new TypeError("Expected a function");return function(){return!n.apply(this,arguments)}},a.once=function(n){return U(2,n)},a.pick=Yn,a.slice=function(n,t,r){return n&&n.length?A(n,t,r):[]},a.sortBy=function(n,t){var r=0;return t=d(t),w(w(n,function(n,e,u){return{c:n,b:r++,a:t(n,e,u)}}).sort(function(n,t){var r;n:{r=n.a;var e=t.a;if(r!==e){var u=null===r,o=r===ln,i=r===r,c=null===e,f=e===ln,a=e===e;
if(r>e&&!c||!i||u&&!f&&a||o&&a){r=1;break n}if(e>r&&!u||!a||c&&!o&&i||f&&i){r=-1;break n}}r=0}return r||n.b-t.b}),E("c"))},a.tap=function(n,t){return t(n),n},a.thru=function(n,t){return t(n)},a.toArray=function(n){return Q(n)?n.length?k(n):[]:cn(n)},a.values=cn,a.each=G,a.extend=Qn,an(a,a),a.clone=function(n){return Y(n)?Vn(n)?k(n):T(n,un(n)):n},a.escape=function(n){return(n=en(n))&&sn.test(n)?n.replace(pn,i):n},a.every=function(n,t,r){return t=r?ln:t,h(n,d(t))},a.find=C,a.forEach=G,a.has=function(n,t){
return null!=n&&En.call(n,t)},a.head=z,a.identity=fn,a.indexOf=function(n,t,r){var e=n?n.length:0;r=typeof r=="number"?0>r?Rn(e+r,0):r:0,r=(r||0)-1;for(var u=t===t;++r<e;){var o=n[r];if(u?o===t:o!==o)return r}return-1},a.isArguments=L,a.isArray=Vn,a.isBoolean=function(n){return true===n||false===n||Z(n)&&"[object Boolean]"==kn.call(n)},a.isDate=function(n){return Z(n)&&"[object Date]"==kn.call(n)},a.isEmpty=function(n){return!Z(n)||W(n.splice)?!P(n):!un(n).length},a.isEqual=function(n,t){return b(n,t)},
a.isFinite=function(n){return typeof n=="number"&&Bn(n)},a.isFunction=W,a.isNaN=function(n){return nn(n)&&n!=+n},a.isNull=function(n){return null===n},a.isNumber=nn,a.isObject=Y,a.isRegExp=function(n){return Y(n)&&"[object RegExp]"==kn.call(n)},a.isString=tn,a.isUndefined=function(n){return n===ln},a.last=function(n){var t=n?n.length:0;return t?n[t-1]:ln},a.max=function(n){return n&&n.length?t(n,fn,K):ln},a.min=function(n){return n&&n.length?t(n,fn,rn):ln},a.noConflict=function(){return wn._=Nn,this;
},a.noop=function(){},a.now=Gn,a.reduce=J,a.result=function(n,t,r){return t=null==n?ln:n[t],t===ln&&(t=r),W(t)?t.call(n):t},a.size=P,a.some=function(n,t,r){return t=r?ln:t,N(n,d(t))},a.uniqueId=function(n){var t=++An;return en(n)+t},a.first=z,an(a,function(){var n={};return _(a,function(t,r){En.call(a.prototype,r)||(n[r]=t)}),n}(),{chain:false}),a.VERSION="4.0.0",$n("pop join replace reverse split push shift sort splice unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?String.prototype:On)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|join|replace|shift)$/.test(n);
a.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),a.prototype.toJSON=a.prototype.valueOf=a.prototype.value=function(){return S(this.__wrapped__,this.__actions__)},(jn||bn||{})._=a,typeof define=="function"&&typeof define.amd=="object"&&define.amd? define(function(){return a}):_n&&gn?(dn&&((gn.exports=a)._=a),_n._=a):wn._=a}).call(this);

1408
extension/lib/vendor/mithril.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,7 @@ const DB_VERSION = 1;
*/
export function openTalerDb(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
let req = indexedDB.open(DB_NAME, DB_VERSION);
const req = indexedDB.open(DB_NAME, DB_VERSION);
req.onerror = (e) => {
reject(e);
};
@ -45,16 +45,17 @@ export function openTalerDb(): Promise<IDBDatabase> {
console.log("DB: upgrade needed: oldVersion = " + e.oldVersion);
switch (e.oldVersion) {
case 0: // DB does not exist yet
let mints = db.createObjectStore("mints", {keyPath: "baseUrl"});
const mints = db.createObjectStore("mints", {keyPath: "baseUrl"});
mints.createIndex("pubKey", "keys.master_public_key");
db.createObjectStore("reserves", {keyPath: "reserve_pub"});
db.createObjectStore("denoms", {keyPath: "denomPub"});
let coins = db.createObjectStore("coins", {keyPath: "coinPub"});
const coins = db.createObjectStore("coins", {keyPath: "coinPub"});
coins.createIndex("mintBaseUrl", "mintBaseUrl");
db.createObjectStore("transactions", {keyPath: "contractHash"});
db.createObjectStore("precoins",
{keyPath: "coinPub", autoIncrement: true});
db.createObjectStore("history", {keyPath: "id", autoIncrement: true});
const history = db.createObjectStore("history", {keyPath: "id", autoIncrement: true});
history.createIndex("timestamp", "timestamp");
break;
}
};

View File

@ -39,7 +39,7 @@ export interface QueryStream<T> {
indexName: string,
keyFn: (obj: any) => any): QueryStream<[T,S]>;
filter(f: (any) => boolean): QueryStream<T>;
reduce<S>(f: (S, T) => S, acc?: S): Promise<S>;
reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
}
@ -166,7 +166,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
private storeName;
private options;
constructor(qr, storeName, options?) {
constructor(qr, storeName, options) {
super(qr);
this.options = options;
this.storeName = storeName;
@ -174,15 +174,16 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
subscribe(f) {
let doIt = (tx) => {
const {indexName = void 0, only = void 0} = this.options;
let s;
if (this.options && this.options.indexName) {
if (indexName !== void 0) {
s = tx.objectStore(this.storeName)
.index(this.options.indexName);
} else {
s = tx.objectStore(this.storeName);
}
let kr = undefined;
if (this.options && ("only" in this.options)) {
if (only !== void 0) {
kr = IDBKeyRange.only(this.options.only);
}
let req = s.openCursor(kr);
@ -218,23 +219,11 @@ class QueryRoot {
this.db = db;
}
iter<T>(storeName): QueryStream<T> {
iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> {
this.stores.add(storeName);
return new IterQueryStream(this, storeName);
return new IterQueryStream(this, storeName, {only, indexName});
}
iterOnly<T>(storeName, key): QueryStream<T> {
this.stores.add(storeName);
return new IterQueryStream(this, storeName, {only: key});
}
iterIndex<T>(storeName, indexName, key) {
this.stores.add(storeName);
return new IterQueryStream(this, storeName, {indexName: indexName});
}
/**
* Put an object into the given object store.
* Overrides if an existing object with the same key exists

View File

@ -181,6 +181,18 @@ function canonicalizeBaseUrl(url) {
return x.href()
}
function parsePrettyAmount(pretty: string): AmountJson_interface {
const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
if (!res) {
return null;
}
return {
value: parseInt(res[1], 10),
fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0,
currency: res[3]
}
}
interface HttpRequestLibrary {
req(method: string,
@ -310,7 +322,7 @@ export class Wallet {
let ps = allowedMints.map((info) => {
return Query(this.db)
.iterIndex("mints", "pubKey", info.master_pub)
.iter("mints", {indexName: "pubKey", only: info.master_pub})
.indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
.reduce(storeMintCoin);
});
@ -376,8 +388,20 @@ export class Wallet {
payReq: payReq
};
let historyEntry = {
type: "pay",
timestamp: (new Date).getTime(),
detail: {
merchantName: offer.contract.merchant.name,
amount: offer.contract.amount,
contractHash: offer.H_contract
}
};
return Query(this.db)
.put("transactions", t)
.put("history", historyEntry)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
}
@ -418,12 +442,17 @@ export class Wallet {
let reservePriv = EddsaPrivateKey.create();
let reservePub = reservePriv.getPublicKey();
let form = new FormData();
let now = (new Date()).toString();
let now: number = (new Date).getTime();
form.append(req.field_amount, req.amount_str);
form.append(req.field_reserve_pub, reservePub.toCrock());
form.append(req.field_mint, req.mint);
// TODO: set bank-specified fields.
let mintBaseUrl = canonicalizeBaseUrl(req.mint);
let requestedAmount = parsePrettyAmount(req.amount_str);
if (!requestedAmount) {
throw Error(`unrecognized amount ${req.amount_str}.`);
}
return this.http.postForm(req.post_url, form)
.then((hresp) => {
@ -441,7 +470,7 @@ export class Wallet {
last_query: null,
current_amount: null,
// XXX: set to actual amount
initial_amount: null
requested_amount: null
};
if (hresp.status != 200) {
@ -449,12 +478,22 @@ export class Wallet {
return resp;
}
let historyEntry = {
type: "create-reserve",
timestamp: now,
detail: {
requestedAmount,
reservePub: reserveRecord.reserve_pub,
}
};
resp.success = true;
// We can't show the page directly, so
// we show some generic page from the wallet.
resp.backlink = null;
return Query(this.db)
.put("reserves", reserveRecord)
.put("history", historyEntry)
.finish()
.then(() => {
// Do this in the background
@ -574,10 +613,18 @@ export class Wallet {
.then(doBadge.bind(this));
}
storeCoin(coin: Coin) {
Query(this.db)
storeCoin(coin: Coin): Promise<void> {
let historyEntry = {
type: "withdraw",
timestamp: (new Date).getTime(),
detail: {
coinPub: coin.coinPub,
}
};
return Query(this.db)
.delete("precoins", coin.coinPub)
.add("coins", coin)
.add("history", historyEntry)
.finish()
.then(() => {
this.updateBadge();
@ -624,7 +671,10 @@ export class Wallet {
}
let d = workList.pop();
this.withdraw(d, reserve)
.then(() => next());
.then(() => next())
.catch((e) => {
console.log("Failed to withdraw coin", e.stack);
});
};
// Asynchronous recursion
@ -647,7 +697,18 @@ export class Wallet {
if (!reserveInfo) {
throw Error();
}
let oldAmount = reserve.current_amount;
let newAmount = reserveInfo.balance;
reserve.current_amount = reserveInfo.balance;
let historyEntry = {
type: "reserve-update",
timestamp: (new Date).getTime(),
detail: {
reservePub,
oldAmount,
newAmount
}
};
return Query(this.db)
.put("reserves", reserve)
.finish()
@ -696,4 +757,14 @@ export class Wallet {
.iter("coins")
.reduce(collectBalances, {});
}
getHistory() {
function collect(x, acc) {
acc.push(x);
return acc;
}
return Query(this.db)
.iter("history", {indexName: "timestamp"})
.reduce(collect, [])
}
}

View File

@ -20,7 +20,12 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
function makeHandlers(wallet) {
return (_a = {},
_a["balances"] = function (db, detail, sendResponse) {
wallet.getBalances().then(sendResponse);
wallet.getBalances()
.then(sendResponse)
.catch(function (e) {
console.log("exception during 'balances'");
console.error(e.stack);
});
return true;
},
_a["dump-db"] = function (db, detail, sendResponse) {
@ -54,6 +59,10 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
}
sendResponse(resp);
})
.catch(function (e) {
console.error("exception during 'confirm-reserve'");
console.error(e.stack);
});
return true;
},
@ -63,6 +72,8 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
sendResponse({ success: true });
})
.catch(function (e) {
console.error("exception during 'confirm-pay'");
console.error(e.stack);
sendResponse({ error: e.message });
});
return true;
@ -77,18 +88,33 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
});
})
.catch(function (e) {
console.error("exception during 'execute-payment'");
console.error(e.stack);
sendResponse({ success: false, error: e.message });
});
// async sendResponse
return true;
},
_a["get-history"] = function (db, detail, sendResponse) {
// TODO: limit history length
wallet.getHistory()
.then(function (h) {
sendResponse(h);
})
.catch(function (e) {
console.error("exception during 'get-history'");
console.error(e.stack);
});
return true;
},
_a
);
var _a;
}
function wxMain() {
chrome.browserAction.setBadgeText({ text: "" });
db_3.openTalerDb().then(function (db) {
db_3.openTalerDb()
.then(function (db) {
var http = new http_1.BrowserHttpLib();
var badge = new ChromeBadge();
var wallet = new wallet_1.Wallet(db, http, badge);
@ -101,6 +127,10 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
console.error("Request type " + JSON.stringify(req) + " unknown, req " + req.type);
return false;
});
})
.catch(function (e) {
console.error("could not open database:");
console.error(e.stack);
});
}
exports_1("wxMain", wxMain);

View File

@ -36,7 +36,12 @@ import {Badge} from "./wallet";
function makeHandlers(wallet) {
return {
["balances"]: function(db, detail, sendResponse) {
wallet.getBalances().then(sendResponse);
wallet.getBalances()
.then(sendResponse)
.catch((e) => {
console.log("exception during 'balances'");
console.error(e.stack);
});
return true;
},
["dump-db"]: function(db, detail, sendResponse) {
@ -72,6 +77,10 @@ function makeHandlers(wallet) {
"pages/reserve-success.html");
}
sendResponse(resp);
})
.catch((e) => {
console.error("exception during 'confirm-reserve'");
console.error(e.stack);
});
return true;
},
@ -81,6 +90,8 @@ function makeHandlers(wallet) {
sendResponse({success: true})
})
.catch((e) => {
console.error("exception during 'confirm-pay'");
console.error(e.stack);
sendResponse({error: e.message});
});
return true;
@ -95,10 +106,24 @@ function makeHandlers(wallet) {
});
})
.catch((e) => {
console.error("exception during 'execute-payment'");
console.error(e.stack);
sendResponse({success: false, error: e.message});
});
// async sendResponse
return true;
},
["get-history"]: function(db, detail, sendResponse) {
// TODO: limit history length
wallet.getHistory()
.then((h) => {
sendResponse(h);
})
.catch((e) => {
console.error("exception during 'get-history'");
console.error(e.stack);
});
return true;
}
};
}
@ -117,19 +142,24 @@ class ChromeBadge implements Badge {
export function wxMain() {
chrome.browserAction.setBadgeText({text: ""});
openTalerDb().then((db) => {
let http = new BrowserHttpLib();
let badge = new ChromeBadge();
let wallet = new Wallet(db, http, badge);
let handlers = makeHandlers(wallet);
wallet.updateBadge();
chrome.runtime.onMessage.addListener(
function(req, sender, onresponse) {
if (req.type in handlers) {
return handlers[req.type](db, req.detail, onresponse);
}
console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`);
return false;
});
});
openTalerDb()
.then((db) => {
let http = new BrowserHttpLib();
let badge = new ChromeBadge();
let wallet = new Wallet(db, http, badge);
let handlers = makeHandlers(wallet);
wallet.updateBadge();
chrome.runtime.onMessage.addListener(
function(req, sender, onresponse) {
if (req.type in handlers) {
return handlers[req.type](db, req.detail, onresponse);
}
console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`);
return false;
});
})
.catch((e) => {
console.error("could not open database:");
console.error(e.stack);
});
}

View File

@ -19,7 +19,7 @@
"browser_action": {
"default_icon": "img/icon.png",
"default_title": "Taler",
"default_popup": "popup/balance-overview.html"
"default_popup": "popup/popup.html"
},
"content_scripts": [

View File

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
<script src="../lib/vendor/handlebars-v4.0.5.js" type="text/javascript"></script>
<script src="../lib/commonHelpers.js" type="text/javascript"></script>
<script src="balance-overview.js" type="text/javascript"></script>
<script id="balance-template" type="text/x-handlebars-template">
{{#each this}}
<p>{{prettyAmountNoCurrency this}} <a>{{@key}}</a></p>
{{else}}
<p>Looks like your wallet is empty. Want to get some
<a id="link-kudos" href="http://bank.demo.taler.net">KUDOS?</a>
{{/each}}
</script>
</head>
<body>
<div id="header" class="nav">
<a href="balance-overview.html" class="active">Wallet</a>
<a href="history.html">History</a>
<a href="reserves.html">Reserves</a>
<button id="debug">Debug!</button>
<button id="reset">Reset!</button>
</div>
<div id="content">
</div>
</body>
</html>

View File

@ -1,44 +0,0 @@
/*
This file is part of TALER
(C) 2016 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, If not, see <http://www.gnu.org/licenses/>
*/
"use strict";
document.addEventListener("DOMContentLoaded", function (e) {
console.log("content loaded");
chrome.runtime.sendMessage({ type: "balances" }, function (wallet) {
var context = document.getElementById("balance-template").innerHTML;
var template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet);
console.log("got wallet", JSON.stringify(wallet));
var el = document.getElementById("link-kudos");
if (el) {
el.onclick = function (e) {
var target = e.target;
chrome.tabs.create({
"url": target.href
});
};
}
});
document.getElementById("debug").addEventListener("click", function (e) {
chrome.tabs.create({
"url": chrome.extension.getURL("pages/debug.html")
});
});
document.getElementById("reset").addEventListener("click", function (e) {
chrome.runtime.sendMessage({ type: "reset" });
window.close();
});
});
//# sourceMappingURL=balance-overview.js.map

View File

@ -1,46 +0,0 @@
/*
This file is part of TALER
(C) 2016 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, If not, see <http://www.gnu.org/licenses/>
*/
"use strict";
document.addEventListener("DOMContentLoaded", (e) => {
console.log("content loaded");
chrome.runtime.sendMessage({type: "balances"}, function(wallet) {
let context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet);
console.log("got wallet", JSON.stringify(wallet));
let el = document.getElementById("link-kudos");
if (el) {
el.onclick = (e) => {
let target: any = e.target;
chrome.tabs.create({
"url": target.href
});
};
}
});
document.getElementById("debug").addEventListener("click", (e) => {
chrome.tabs.create({
"url": chrome.extension.getURL("pages/debug.html")
});
});
document.getElementById("reset").addEventListener("click", (e) => {
chrome.runtime.sendMessage({type: "reset"});
window.close();
});
});

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
<script src="history.js" type="text/javascript"></script>
<script id="balance-template" type="text/x-handlebars-template">
{{#each transactions}}
<p>bla</p>
{{else}}
There's nothing here. Go to
our <a href="http://demo.taler.net">demo site</a> to try GNU Taler.
{{/each}}
</script>
</head>
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
<a href="history.html" class="active">Transactions</a>
<a href="reserves.html">Reserves</a>
</div>
<div id="content">
(Loading...)
</div>
</body>
</html>

View File

@ -1,4 +1,5 @@
body {
min-height: 20em;
width: 30em;
margin: 0;
padding: 0

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
<script src="../lib/vendor/mithril.js"></script>
<script src="../lib/i18n.js"></script>
<script src="../lib/vendor/lodash.core.min.js"></script>
<script src="popup.js"></script>
</head>
<body>
<div id="nav"></div>
<div id="content"></div>
</body>
</html>

182
extension/popup/popup.tsx Normal file
View File

@ -0,0 +1,182 @@
/*
This file is part of TALER
(C) 2016 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, If not, see <http://www.gnu.org/licenses/>
*/
/// <reference path="../lib/mithril.d.ts" />
/// <reference path="../lib/decl/lodash.d.ts" />
"use strict";
declare var m: any;
declare var i18n: any;
document.addEventListener("DOMContentLoaded", function(event) {
m.route.mode = "hash";
m.route(document.getElementById("content"), "/balance", {
"/balance": WalletBalance,
"/history": WalletHistory,
"/debug": WalletDebug,
});
m.mount(document.getElementById("nav"), WalletNavBar);
});
function makeTab(target, name) {
let cssClass = "";
if (target == m.route()) {
cssClass = "active";
}
return m("a", {config: m.route, href: target, "class": cssClass}, name);
}
var WalletNavBar = {
view() {
return m("div#header.nav", [
makeTab("/balance", i18n`Balance`),
makeTab("/history", i18n`History`),
makeTab("/debug", i18n`Debug`),
]);
}
};
function openInExtension(element, isInitialized) {
element.addEventListener("click", (e) => {
chrome.tabs.create({
"url": element.href
});
e.preventDefault();
});
}
var WalletBalance = {
controller() {
var myWallet;
m.startComputation();
chrome.runtime.sendMessage({type: "balances"}, (wallet) => {
console.log("got wallet", wallet);
myWallet = wallet;
m.endComputation();
});
return () => myWallet;
},
view(getWallet) {
let wallet = getWallet();
if (!wallet) {
throw Error("Could not retrieve wallet");
}
let listing = _.map(wallet, x => m("p", formatAmount(x)));
if (listing.length > 0) {
return listing;
}
let link = m("a[href=https://demo.taler.net]", {config: openInExtension}, i18n`free KUDOS`);
return i18n.parts`You have no balance to show. Want to get some ${link}?`;
}
};
function formatTimestamp(t) {
let x = new Date(t);
return x.toLocaleString();
}
function formatAmount(amount) {
let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`;
}
function formatHistoryItem(historyItem) {
const d = historyItem.detail;
const t = historyItem.timestamp;
switch (historyItem.type) {
case "create-reserve":
return m("p",
i18n`Created reserve of ${formatAmount(d.requestedAmount)} at ${formatTimestamp(
t)}`);
case "withdraw":
return m("p",
i18n`Withdraw at ${formatTimestamp(t)}`);
case "pay":
return m("p",
[
i18n`Payment for ${formatAmount(d.amount)} to merchant ${d.merchantName}. `,
m("a[href=javascript:;]", "Retry")
]);
default:
return m("p", i18n`Unknown event (${historyItem.type})`);
}
}
var WalletHistory = {
controller() {
var myHistory;
m.startComputation();
chrome.runtime.sendMessage({type: "get-history"}, (wallet) => {
console.log("got history", history);
myHistory = wallet;
m.endComputation();
});
return () => myHistory;
},
view(getHistory) {
let history = getHistory();
if (!history) {
throw Error("Could not retrieve history");
}
let listing = _.map(history, formatHistoryItem);
if (listing.length > 0) {
return listing;
}
return i18n`Your wallet has no events recorded.`;
}
};
function confirmReset() {
if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
" wallet and LOSE ALL YOUR COINS?")) {
chrome.runtime.sendMessage({type: "reset"});
window.close();
try {
chrome.runtime.reload();
} catch (e) {
// Functionality missing in firefox, ignore!
}
}
}
var WalletDebug = {
view() {
return [
m("button", {onclick: openWalletAsTab}, "wallet tab"),
m("button", {onclick: confirmReset}, "reset")
]
}
};
function openWalletAsTab() {
chrome.tabs.create({
"url": chrome.extension.getURL("popup/popup.html")
});
}

View File

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
</head>
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
<a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
<div id="content">
<div id="reserve-create-sepa">
SEPA info here.
</div>
</div>
<div id="footer" class="nav">
<a href="reserves.html">List reserves</a>
<a href="reserve-create.html">Create reserve</a>
</div>
</body>
</html>

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
</head>
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
<a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
<div id="content">
<form id="reserve-create" action="reserve-create-sepa.html">
<table>
<tr>
<td class="label"><label for="mint">Mint URL:</label></td>
<td class="input"><input name="mint" type="text" /></td>
</tr>
<tr>
<td class="label"><label for="amount">Amount:</label></td>
<td class="input"><input name="amount" type="text" /></td>
</tr>
<tr>
<td class="label"></td>
<td class="input"><input type="submit" value="Create reserve " /></td>
</tr>
</table>
</form>
</div>
<div id="footer" class="nav">
<a href="reserves.html">List reserves</a>
<a href="reserve-create.html" class="active">Create reserve</a>
</div>
</body>
</html>

View File

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css" type="text/css">
</head>
<body>
<div id="header" class="nav">
<a href="balance-overview.html">Wallet</a>
<a href="history.html">Transactions</a>
<a href="reserves.html" class="active">Reserves</a>
</div>
<div id="content">
<div id="reserves">
There are no reserves available.
</div>
</div>
<div id="footer" class="nav">
<a href="reserves.html" class="active">List reserves</a>
<a href="reserve-create.html">Create reserve</a>
</div>
</body>
</html>

View File

@ -1,10 +1,24 @@
import * as Emsc from '../../lib/wallet/emscriptif';
declare var HttpMockLib;
export function declareTests(assert, context, it) {
it("works!", function() {
let x = new Emsc.Amount({value: 42, fraction:42, currency: "EUR"});
let x = new Emsc.Amount({value: 42, fraction: 42, currency: "EUR"});
let j = x.toJson();
assert("value" in j);
});
it("retries", function() {
let m = new HttpMockLib();
/*m.intercept()
.matchUrlContains()
.counterEquals(0)
.count()
.sen*/
})
}

View File

@ -8,8 +8,7 @@
"noLib": true
},
"files": [
"lib/commonHelpers.ts",
"lib/polyfill-react.ts",
"lib/i18n.ts",
"lib/refs.ts",
"lib/wallet/checkable.ts",
"lib/wallet/db.ts",
@ -21,10 +20,9 @@
"lib/wallet/wxmessaging.ts",
"background/main.ts",
"content_scripts/notify.ts",
"popup/balance-overview.tsx",
"popup/history.tsx",
"popup/popup.tsx",
"pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx",
"test/tests/taler.ts"
]
}
}