/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 21);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var bind = __webpack_require__(14);
var isBuffer = __webpack_require__(36);

/*global toString:true*/

// utils is a library of generic helper functions non-specific to axios

var toString = Object.prototype.toString;

/**
 * Determine if a value is an Array
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Array, otherwise false
 */
function isArray(val) {
  return toString.call(val) === '[object Array]';
}

/**
 * Determine if a value is an ArrayBuffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an ArrayBuffer, otherwise false
 */
function isArrayBuffer(val) {
  return toString.call(val) === '[object ArrayBuffer]';
}

/**
 * Determine if a value is a FormData
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an FormData, otherwise false
 */
function isFormData(val) {
  return (typeof FormData !== 'undefined') && (val instanceof FormData);
}

/**
 * Determine if a value is a view on an ArrayBuffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false
 */
function isArrayBufferView(val) {
  var result;
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
    result = ArrayBuffer.isView(val);
  } else {
    result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
  }
  return result;
}

/**
 * Determine if a value is a String
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a String, otherwise false
 */
function isString(val) {
  return typeof val === 'string';
}

/**
 * Determine if a value is a Number
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Number, otherwise false
 */
function isNumber(val) {
  return typeof val === 'number';
}

/**
 * Determine if a value is undefined
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if the value is undefined, otherwise false
 */
function isUndefined(val) {
  return typeof val === 'undefined';
}

/**
 * Determine if a value is an Object
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Object, otherwise false
 */
function isObject(val) {
  return val !== null && typeof val === 'object';
}

/**
 * Determine if a value is a Date
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Date, otherwise false
 */
function isDate(val) {
  return toString.call(val) === '[object Date]';
}

/**
 * Determine if a value is a File
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a File, otherwise false
 */
function isFile(val) {
  return toString.call(val) === '[object File]';
}

/**
 * Determine if a value is a Blob
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Blob, otherwise false
 */
function isBlob(val) {
  return toString.call(val) === '[object Blob]';
}

/**
 * Determine if a value is a Function
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Function, otherwise false
 */
function isFunction(val) {
  return toString.call(val) === '[object Function]';
}

/**
 * Determine if a value is a Stream
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Stream, otherwise false
 */
function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}

/**
 * Determine if a value is a URLSearchParams object
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a URLSearchParams object, otherwise false
 */
function isURLSearchParams(val) {
  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}

/**
 * Trim excess whitespace off the beginning and end of a string
 *
 * @param {String} str The String to trim
 * @returns {String} The String freed of excess whitespace
 */
function trim(str) {
  return str.replace(/^\s*/, '').replace(/\s*$/, '');
}

/**
 * Determine if we're running in a standard browser environment
 *
 * This allows axios to run in a web worker, and react-native.
 * Both environments support XMLHttpRequest, but not fully standard globals.
 *
 * web workers:
 *  typeof window -> undefined
 *  typeof document -> undefined
 *
 * react-native:
 *  navigator.product -> 'ReactNative'
 */
function isStandardBrowserEnv() {
  if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
    return false;
  }
  return (
    typeof window !== 'undefined' &&
    typeof document !== 'undefined'
  );
}

/**
 * Iterate over an Array or an Object invoking a function for each item.
 *
 * If `obj` is an Array callback will be called passing
 * the value, index, and complete array for each item.
 *
 * If 'obj' is an Object callback will be called passing
 * the value, key, and complete object for each property.
 *
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  if (typeof obj !== 'object' && !isArray(obj)) {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

/**
 * Accepts varargs expecting each argument to be an object, then
 * immutably merges the properties of each object and returns result.
 *
 * When multiple objects contain the same key the later object in
 * the arguments list will take precedence.
 *
 * Example:
 *
 * ```js
 * var result = merge({foo: 123}, {foo: 456});
 * console.log(result.foo); // outputs 456
 * ```
 *
 * @param {Object} obj1 Object to merge
 * @returns {Object} Result of all merge properties
 */
function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (typeof result[key] === 'object' && typeof val === 'object') {
      result[key] = merge(result[key], val);
    } else {
      result[key] = val;
    }
  }

  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}

/**
 * Extends object a by mutably adding to it the properties of object b.
 *
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

module.exports = {
  isArray: isArray,
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,
  merge: merge,
  extend: extend,
  trim: trim
};


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2018 GNUnet e.V. and INRIA

 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/>
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Types and helper functions for dealing with Taler amounts.
 */
/**
 * Imports.
 */
const checkable_1 = __webpack_require__(3);
/**
 * Number of fractional units that one value unit represents.
 */
exports.fractionalBase = 1e8;
/**
 * Non-negative financial amount.  Fractional values are expressed as multiples
 * of 1e-8.
 */
let AmountJson = class AmountJson {
};
__decorate([
    checkable_1.Checkable.Number()
], AmountJson.prototype, "value", void 0);
__decorate([
    checkable_1.Checkable.Number()
], AmountJson.prototype, "fraction", void 0);
__decorate([
    checkable_1.Checkable.String()
], AmountJson.prototype, "currency", void 0);
AmountJson = __decorate([
    checkable_1.Checkable.Class()
], AmountJson);
exports.AmountJson = AmountJson;
/**
 * Get the largest amount that is safely representable.
 */
function getMaxAmount(currency) {
    return {
        currency,
        fraction: Math.pow(2, 32),
        value: Number.MAX_SAFE_INTEGER,
    };
}
exports.getMaxAmount = getMaxAmount;
/**
 * Get an amount that represents zero units of a currency.
 */
function getZero(currency) {
    return {
        currency,
        fraction: 0,
        value: 0,
    };
}
exports.getZero = getZero;
/**
 * Add two amounts.  Return the result and whether
 * the addition overflowed.  The overflow is always handled
 * by saturating and never by wrapping.
 *
 * Throws when currencies don't match.
 */
function add(first, ...rest) {
    const currency = first.currency;
    let value = first.value + Math.floor(first.fraction / exports.fractionalBase);
    if (value > Number.MAX_SAFE_INTEGER) {
        return { amount: getMaxAmount(currency), saturated: true };
    }
    let fraction = first.fraction % exports.fractionalBase;
    for (const x of rest) {
        if (x.currency !== currency) {
            throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
        }
        value = value + x.value + Math.floor((fraction + x.fraction) / exports.fractionalBase);
        fraction = Math.floor((fraction + x.fraction) % exports.fractionalBase);
        if (value > Number.MAX_SAFE_INTEGER) {
            return { amount: getMaxAmount(currency), saturated: true };
        }
    }
    return { amount: { currency, value, fraction }, saturated: false };
}
exports.add = add;
/**
 * Subtract two amounts.  Return the result and whether
 * the subtraction overflowed.  The overflow is always handled
 * by saturating and never by wrapping.
 *
 * Throws when currencies don't match.
 */
function sub(a, ...rest) {
    const currency = a.currency;
    let value = a.value;
    let fraction = a.fraction;
    for (const b of rest) {
        if (b.currency !== currency) {
            throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
        }
        if (fraction < b.fraction) {
            if (value < 1) {
                return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
            }
            value--;
            fraction += exports.fractionalBase;
        }
        console.assert(fraction >= b.fraction);
        fraction -= b.fraction;
        if (value < b.value) {
            return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
        }
        value -= b.value;
    }
    return { amount: { currency, value, fraction }, saturated: false };
}
exports.sub = sub;
/**
 * Compare two amounts.  Returns 0 when equal, -1 when a < b
 * and +1 when a > b.  Throws when currencies don't match.
 */
function cmp(a, b) {
    if (a.currency !== b.currency) {
        throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
    }
    const av = a.value + Math.floor(a.fraction / exports.fractionalBase);
    const af = a.fraction % exports.fractionalBase;
    const bv = b.value + Math.floor(b.fraction / exports.fractionalBase);
    const bf = b.fraction % exports.fractionalBase;
    switch (true) {
        case av < bv:
            return -1;
        case av > bv:
            return 1;
        case af < bf:
            return -1;
        case af > bf:
            return 1;
        case af === bf:
            return 0;
        default:
            throw Error("assertion failed");
    }
}
exports.cmp = cmp;
/**
 * Create a copy of an amount.
 */
function copy(a) {
    return {
        currency: a.currency,
        fraction: a.fraction,
        value: a.value,
    };
}
exports.copy = copy;
/**
 * Divide an amount.  Throws on division by zero.
 */
function divide(a, n) {
    if (n === 0) {
        throw Error(`Division by 0`);
    }
    if (n === 1) {
        return { value: a.value, fraction: a.fraction, currency: a.currency };
    }
    const r = a.value % n;
    return {
        currency: a.currency,
        fraction: Math.floor(((r * exports.fractionalBase) + a.fraction) / n),
        value: Math.floor(a.value / n),
    };
}
exports.divide = divide;
/**
 * Check if an amount is non-zero.
 */
function isNonZero(a) {
    return a.value > 0 || a.fraction > 0;
}
exports.isNonZero = isNonZero;
/**
 * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
 */
function parse(s) {
    const res = s.match(/([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?/);
    if (!res) {
        return undefined;
    }
    return {
        currency: res[1],
        fraction: Math.round(exports.fractionalBase * Number.parseFloat(res[3] || "0")),
        value: Number.parseInt(res[2]),
    };
}
exports.parse = parse;
function parseOrThrow(s) {
    const res = parse(s);
    if (!res) {
        throw Error(`Can't parse amount: "${s}"`);
    }
    return res;
}
exports.parseOrThrow = parseOrThrow;
/**
 * Convert the amount to a float.
 */
function toFloat(a) {
    return a.value + (a.fraction / exports.fractionalBase);
}
exports.toFloat = toFloat;
/**
 * Convert a float to a Taler amount.
 * Loss of precision possible.
 */
function fromFloat(floatVal, currency) {
    return {
        currency,
        fraction: Math.floor((floatVal - Math.floor(floatVal)) * exports.fractionalBase),
        value: Math.floor(floatVal),
    };
}
exports.fromFloat = fromFloat;
/**
 * Convert to standard human-readable string representation that's
 * also used in JSON formats.
 */
function toString(a) {
    return `${a.currency}:${a.value + (a.fraction / exports.fractionalBase)}`;
}
exports.toString = toString;
function check(a) {
    if (typeof a !== "string") {
        return false;
    }
    try {
        const parsedAmount = parse(a);
        return !!parsedAmount;
    }
    catch (_a) {
        return false;
    }
}
exports.check = check;


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Definition of an object store.
 */
class Store {
    constructor(name, storeParams, validator) {
        this.name = name;
        this.storeParams = storeParams;
        this.validator = validator;
    }
}
exports.Store = Store;
/**
 * Definition of an index.
 */
class Index {
    constructor(s, indexName, keyPath, options) {
        this.indexName = indexName;
        this.keyPath = keyPath;
        const defaultOptions = {
            multiEntry: false,
        };
        this.options = Object.assign({}, defaultOptions, (options || {}));
        this.storeName = s.name;
    }
}
exports.Index = Index;
class BaseQueryValue {
    constructor(root) {
        this.root = root;
    }
    map(f) {
        return new MapQueryValue(this, f);
    }
    cond(f, onTrue, onFalse) {
        return new Promise((resolve, reject) => {
            this.subscribeOne((v, tx) => {
                if (f(v)) {
                    onTrue(new QueryRoot(this.root.db));
                }
                else {
                    onFalse(new QueryRoot(this.root.db));
                }
            });
            resolve();
        });
    }
}
class FirstQueryValue extends BaseQueryValue {
    constructor(stream) {
        super(stream.root);
        this.gotValue = false;
        this.s = stream;
    }
    subscribeOne(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (this.gotValue) {
                return;
            }
            if (isDone) {
                f(undefined, tx);
            }
            else {
                f(value, tx);
            }
            this.gotValue = true;
        });
    }
}
class MapQueryValue extends BaseQueryValue {
    constructor(v, mapFn) {
        super(v.root);
        this.v = v;
        this.mapFn = mapFn;
    }
    subscribeOne(f) {
        this.v.subscribeOne((v, tx) => this.mapFn(v));
    }
}
/**
 * Exception that should be thrown by client code to abort a transaction.
 */
exports.AbortTransaction = Symbol("abort_transaction");
/**
 * Get an unresolved promise together with its extracted resolve / reject
 * function.
 */
function openPromise() {
    let resolve = null;
    let reject = null;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    if (!(resolve && reject)) {
        // Never happens, unless JS implementation is broken
        throw Error();
    }
    return { resolve, reject, promise };
}
exports.openPromise = openPromise;
class QueryStreamBase {
    constructor(root) {
        this.root = root;
    }
    first() {
        return new FirstQueryValue(this);
    }
    flatMap(f) {
        return new QueryStreamFlatMap(this, f);
    }
    map(f) {
        return new QueryStreamMap(this, f);
    }
    indexJoin(index, keyFn) {
        this.root.addStoreAccess(index.storeName, false);
        return new QueryStreamIndexJoin(this, index.storeName, index.indexName, keyFn);
    }
    indexJoinLeft(index, keyFn) {
        this.root.addStoreAccess(index.storeName, false);
        return new QueryStreamIndexJoinLeft(this, index.storeName, index.indexName, keyFn);
    }
    keyJoin(store, keyFn) {
        this.root.addStoreAccess(store.name, false);
        return new QueryStreamKeyJoin(this, store.name, keyFn);
    }
    filter(f) {
        return new QueryStreamFilter(this, f);
    }
    toArray() {
        const { resolve, promise } = openPromise();
        const values = [];
        this.subscribe((isDone, value) => {
            if (isDone) {
                resolve(values);
                return;
            }
            values.push(value);
        });
        return Promise.resolve()
            .then(() => this.root.finish())
            .then(() => promise);
    }
    fold(f, init) {
        const { resolve, promise } = openPromise();
        let acc = init;
        this.subscribe((isDone, value) => {
            if (isDone) {
                resolve(acc);
                return;
            }
            acc = f(value, acc);
        });
        return Promise.resolve()
            .then(() => this.root.finish())
            .then(() => promise);
    }
    forEach(f) {
        const { resolve, promise } = openPromise();
        this.subscribe((isDone, value) => {
            if (isDone) {
                resolve();
                return;
            }
            f(value);
        });
        return Promise.resolve()
            .then(() => this.root.finish())
            .then(() => promise);
    }
    run() {
        const { resolve, promise } = openPromise();
        this.subscribe((isDone, value) => {
            if (isDone) {
                resolve();
                return;
            }
        });
        return Promise.resolve()
            .then(() => this.root.finish())
            .then(() => promise);
    }
}
class QueryStreamFilter extends QueryStreamBase {
    constructor(s, filterFn) {
        super(s.root);
        this.s = s;
        this.filterFn = filterFn;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            if (this.filterFn(value)) {
                f(false, value, tx);
            }
        });
    }
}
class QueryStreamFlatMap extends QueryStreamBase {
    constructor(s, flatMapFn) {
        super(s.root);
        this.s = s;
        this.flatMapFn = flatMapFn;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            const values = this.flatMapFn(value);
            for (const v in values) {
                f(false, v, tx);
            }
        });
    }
}
class QueryStreamMap extends QueryStreamBase {
    constructor(s, mapFn) {
        super(s.root);
        this.s = s;
        this.mapFn = mapFn;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            const mappedValue = this.mapFn(value);
            f(false, mappedValue, tx);
        });
    }
}
class QueryStreamIndexJoin extends QueryStreamBase {
    constructor(s, storeName, indexName, key) {
        super(s.root);
        this.s = s;
        this.storeName = storeName;
        this.indexName = indexName;
        this.key = key;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            const s = tx.objectStore(this.storeName).index(this.indexName);
            const req = s.openCursor(IDBKeyRange.only(this.key(value)));
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    f(false, { left: value, right: cursor.value }, tx);
                    cursor.continue();
                }
            };
        });
    }
}
class QueryStreamIndexJoinLeft extends QueryStreamBase {
    constructor(s, storeName, indexName, key) {
        super(s.root);
        this.s = s;
        this.storeName = storeName;
        this.indexName = indexName;
        this.key = key;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            const s = tx.objectStore(this.storeName).index(this.indexName);
            const req = s.openCursor(IDBKeyRange.only(this.key(value)));
            let gotMatch = false;
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    gotMatch = true;
                    f(false, { left: value, right: cursor.value }, tx);
                    cursor.continue();
                }
                else {
                    if (!gotMatch) {
                        f(false, { left: value }, tx);
                    }
                }
            };
        });
    }
}
class QueryStreamKeyJoin extends QueryStreamBase {
    constructor(s, storeName, key) {
        super(s.root);
        this.s = s;
        this.storeName = storeName;
        this.key = key;
    }
    subscribe(f) {
        this.s.subscribe((isDone, value, tx) => {
            if (isDone) {
                f(true, undefined, tx);
                return;
            }
            const s = tx.objectStore(this.storeName);
            const req = s.openCursor(IDBKeyRange.only(this.key(value)));
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    f(false, { left: value, right: cursor.value }, tx);
                    cursor.continue();
                }
                else {
                    f(true, undefined, tx);
                }
            };
        });
    }
}
class IterQueryStream extends QueryStreamBase {
    constructor(qr, storeName, options) {
        super(qr);
        this.options = options;
        this.storeName = storeName;
        this.subscribers = [];
        const doIt = (tx) => {
            const { indexName = void 0, only = void 0 } = this.options;
            let s;
            if (indexName !== void 0) {
                s = tx.objectStore(this.storeName)
                    .index(this.options.indexName);
            }
            else {
                s = tx.objectStore(this.storeName);
            }
            let kr;
            if (only !== undefined) {
                kr = IDBKeyRange.only(this.options.only);
            }
            const req = s.openCursor(kr);
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    for (const f of this.subscribers) {
                        f(false, cursor.value, tx);
                    }
                    cursor.continue();
                }
                else {
                    for (const f of this.subscribers) {
                        f(true, undefined, tx);
                    }
                }
            };
        };
        this.root.addWork(doIt);
    }
    subscribe(f) {
        this.subscribers.push(f);
    }
}
/**
 * Root wrapper around an IndexedDB for queries with a fluent interface.
 */
class QueryRoot {
    constructor(db) {
        this.db = db;
        this.work = [];
        this.stores = new Set();
        this.finished = false;
        this.keys = {};
    }
    /**
     * Get a named key that was created during the query.
     */
    key(keyName) {
        return this.keys[keyName];
    }
    checkFinished() {
        if (this.finished) {
            throw Error("Can't add work to query after it was started");
        }
    }
    /**
     * Get a stream of all objects in the store.
     */
    iter(store) {
        this.checkFinished();
        this.stores.add(store.name);
        this.scheduleFinish();
        return new IterQueryStream(this, store.name, {});
    }
    /**
     * Count the number of objects in a store.
     */
    count(store) {
        this.checkFinished();
        const { resolve, promise } = openPromise();
        const doCount = (tx) => {
            const s = tx.objectStore(store.name);
            const req = s.count();
            req.onsuccess = () => {
                resolve(req.result);
            };
        };
        this.addWork(doCount, store.name, false);
        return Promise.resolve()
            .then(() => this.finish())
            .then(() => promise);
    }
    /**
     * Delete all objects in a store that match a predicate.
     */
    deleteIf(store, predicate) {
        this.checkFinished();
        const doDeleteIf = (tx) => {
            const s = tx.objectStore(store.name);
            const req = s.openCursor();
            let n = 0;
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    if (predicate(cursor.value, n++)) {
                        cursor.delete();
                    }
                    cursor.continue();
                }
            };
        };
        this.addWork(doDeleteIf, store.name, true);
        return this;
    }
    iterIndex(index, only) {
        this.checkFinished();
        this.stores.add(index.storeName);
        this.scheduleFinish();
        return new IterQueryStream(this, index.storeName, {
            indexName: index.indexName,
            only,
        });
    }
    /**
     * Put an object into the given object store.
     * Overrides if an existing object with the same key exists
     * in the store.
     */
    put(store, val, keyName) {
        this.checkFinished();
        const doPut = (tx) => {
            const req = tx.objectStore(store.name).put(val);
            if (keyName) {
                req.onsuccess = () => {
                    this.keys[keyName] = req.result;
                };
            }
        };
        this.scheduleFinish();
        this.addWork(doPut, store.name, true);
        return this;
    }
    /**
     * Put an object into a store or return an existing record.
     */
    putOrGetExisting(store, val, key) {
        this.checkFinished();
        const { resolve, promise } = openPromise();
        const doPutOrGet = (tx) => {
            const objstore = tx.objectStore(store.name);
            const req = objstore.get(key);
            req.onsuccess = () => {
                if (req.result !== undefined) {
                    resolve(req.result);
                }
                else {
                    const req2 = objstore.add(val);
                    req2.onsuccess = () => {
                        resolve(val);
                    };
                }
            };
        };
        this.scheduleFinish();
        this.addWork(doPutOrGet, store.name, true);
        return promise;
    }
    putWithResult(store, val) {
        this.checkFinished();
        const { resolve, promise } = openPromise();
        const doPutWithResult = (tx) => {
            const req = tx.objectStore(store.name).put(val);
            req.onsuccess = () => {
                resolve(req.result);
            };
            this.scheduleFinish();
        };
        this.addWork(doPutWithResult, store.name, true);
        return Promise.resolve()
            .then(() => this.finish())
            .then(() => promise);
    }
    /**
     * Update objects inside a transaction.
     *
     * If the mutation function throws AbortTransaction, the whole transaction will be aborted.
     * If the mutation function returns undefined or null, no modification will be made.
     */
    mutate(store, key, f) {
        this.checkFinished();
        const doPut = (tx) => {
            const req = tx.objectStore(store.name).openCursor(IDBKeyRange.only(key));
            req.onsuccess = () => {
                const cursor = req.result;
                if (cursor) {
                    const value = cursor.value;
                    let modifiedValue;
                    try {
                        modifiedValue = f(value);
                    }
                    catch (e) {
                        if (e === exports.AbortTransaction) {
                            tx.abort();
                            return;
                        }
                        throw e;
                    }
                    if (modifiedValue !== undefined && modifiedValue !== null) {
                        cursor.update(modifiedValue);
                    }
                    cursor.continue();
                }
            };
        };
        this.scheduleFinish();
        this.addWork(doPut, store.name, true);
        return this;
    }
    /**
     * Add all object from an iterable to the given object store.
     */
    putAll(store, iterable) {
        this.checkFinished();
        const doPutAll = (tx) => {
            for (const obj of iterable) {
                tx.objectStore(store.name).put(obj);
            }
        };
        this.scheduleFinish();
        this.addWork(doPutAll, store.name, true);
        return this;
    }
    /**
     * Add an object to the given object store.
     * Fails if the object's key is already present
     * in the object store.
     */
    add(store, val) {
        this.checkFinished();
        const doAdd = (tx) => {
            tx.objectStore(store.name).add(val);
        };
        this.scheduleFinish();
        this.addWork(doAdd, store.name, true);
        return this;
    }
    /**
     * Get one object from a store by its key.
     */
    get(store, key) {
        this.checkFinished();
        if (key === void 0) {
            throw Error("key must not be undefined");
        }
        const { resolve, promise } = openPromise();
        const doGet = (tx) => {
            const req = tx.objectStore(store.name).get(key);
            req.onsuccess = () => {
                resolve(req.result);
            };
        };
        this.addWork(doGet, store.name, false);
        return Promise.resolve()
            .then(() => this.finish())
            .then(() => promise);
    }
    /**
     * Get get objects from a store by their keys.
     * If no object for a key exists, the resulting position in the array
     * contains 'undefined'.
     */
    getMany(store, keys) {
        this.checkFinished();
        const { resolve, promise } = openPromise();
        const results = [];
        const doGetMany = (tx) => {
            for (const key of keys) {
                if (key === void 0) {
                    throw Error("key must not be undefined");
                }
                const req = tx.objectStore(store.name).get(key);
                req.onsuccess = () => {
                    results.push(req.result);
                    if (results.length === keys.length) {
                        resolve(results);
                    }
                };
            }
        };
        this.addWork(doGetMany, store.name, false);
        return Promise.resolve()
            .then(() => this.finish())
            .then(() => promise);
    }
    /**
     * Get one object from a store by its key.
     */
    getIndexed(index, key) {
        this.checkFinished();
        if (key === void 0) {
            throw Error("key must not be undefined");
        }
        const { resolve, promise } = openPromise();
        const doGetIndexed = (tx) => {
            const req = tx.objectStore(index.storeName)
                .index(index.indexName)
                .get(key);
            req.onsuccess = () => {
                resolve(req.result);
            };
        };
        this.addWork(doGetIndexed, index.storeName, false);
        return Promise.resolve()
            .then(() => this.finish())
            .then(() => promise);
    }
    scheduleFinish() {
        if (!this.finishScheduled) {
            Promise.resolve().then(() => this.finish());
            this.finishScheduled = true;
        }
    }
    /**
     * Finish the query, and start the query in the first place if necessary.
     */
    finish() {
        if (this.kickoffPromise) {
            return this.kickoffPromise;
        }
        this.kickoffPromise = new Promise((resolve, reject) => {
            // At this point, we can't add any more work
            this.finished = true;
            if (this.work.length === 0) {
                resolve();
                return;
            }
            const mode = this.hasWrite ? "readwrite" : "readonly";
            const tx = this.db.transaction(Array.from(this.stores), mode);
            tx.oncomplete = () => {
                resolve();
            };
            tx.onabort = () => {
                console.warn(`aborted ${mode} transaction on stores [${[...this.stores]}]`);
                reject(Error("transaction aborted"));
            };
            tx.onerror = (e) => {
                console.warn(`error in transaction`, e.target.error);
            };
            for (const w of this.work) {
                w(tx);
            }
        });
        return this.kickoffPromise;
    }
    /**
     * Delete an object by from the given object store.
     */
    delete(store, key) {
        this.checkFinished();
        const doDelete = (tx) => {
            tx.objectStore(store.name).delete(key);
        };
        this.scheduleFinish();
        this.addWork(doDelete, store.name, true);
        return this;
    }
    /**
     * Low-level function to add a task to the internal work queue.
     */
    addWork(workFn, storeName, isWrite) {
        this.work.push(workFn);
        if (storeName) {
            this.addStoreAccess(storeName, isWrite);
        }
    }
    addStoreAccess(storeName, isWrite) {
        if (storeName) {
            this.stores.add(storeName);
        }
        if (isWrite) {
            this.hasWrite = true;
        }
    }
}
exports.QueryRoot = QueryRoot;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Decorators for validating JSON objects and converting them to a typed
 * object.
 *
 * The decorators are put onto classes, and the validation is done
 * via a static method that is filled in by the annotation.
 *
 * Example:
 * ```
 *  @Checkable.Class
 *  class Person {
 *    @Checkable.String
 *    name: string;
 *    @Checkable.Number
 *    age: number;
 *
 *    // Method will be implemented automatically
 *    static checked(obj: any): Person;
 *  }
 * ```
 */
var Checkable;
(function (Checkable) {
    // tslint:disable-next-line:no-shadowed-variable
    Checkable.SchemaError = (function SchemaError(message) {
        const that = this;
        that.name = "SchemaError";
        that.message = message;
        that.stack = new Error().stack;
    });
    Checkable.SchemaError.prototype = new Error();
    /**
     * Classes that are checkable are annotated with this
     * checkable info symbol, which contains the information necessary
     * to check if they're valid.
     */
    const checkableInfoSym = Symbol("checkableInfo");
    /**
     * Get the current property list for a checkable type.
     */
    function getCheckableInfo(target) {
        let chk = target[checkableInfoSym];
        if (!chk) {
            chk = { props: [] };
            target[checkableInfoSym] = chk;
        }
        return chk;
    }
    function checkNumber(target, prop, path) {
        if ((typeof target) !== "number") {
            throw new Checkable.SchemaError(`expected number for ${path}`);
        }
        return target;
    }
    function checkString(target, prop, path) {
        if (typeof target !== "string") {
            throw new Checkable.SchemaError(`expected string for ${path}, got ${typeof target} instead`);
        }
        if (prop.stringChecker && !prop.stringChecker(target)) {
            throw new Checkable.SchemaError(`string property ${path} malformed`);
        }
        return target;
    }
    function checkBoolean(target, prop, path) {
        if (typeof target !== "boolean") {
            throw new Checkable.SchemaError(`expected boolean for ${path}, got ${typeof target} instead`);
        }
        return target;
    }
    function checkAnyObject(target, prop, path) {
        if (typeof target !== "object") {
            throw new Checkable.SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`);
        }
        return target;
    }
    function checkAny(target, prop, path) {
        return target;
    }
    function checkList(target, prop, path) {
        if (!Array.isArray(target)) {
            throw new Checkable.SchemaError(`array expected for ${path}, got ${typeof target} instead`);
        }
        for (let i = 0; i < target.length; i++) {
            const v = target[i];
            prop.elementChecker(v, prop.elementProp, path.concat([i]));
        }
        return target;
    }
    function checkMap(target, prop, path) {
        if (typeof target !== "object") {
            throw new Checkable.SchemaError(`expected  object for ${path}, got ${typeof target} instead`);
        }
        for (const key in target) {
            prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
            const value = target[key];
            prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
        }
    }
    function checkOptional(target, prop, path) {
        console.assert(prop.propertyKey);
        prop.elementChecker(target, prop.elementProp, path.concat([prop.propertyKey]));
        return target;
    }
    function checkValue(target, prop, path) {
        let type;
        if (prop.type) {
            type = prop.type;
        }
        else if (prop.typeThunk) {
            type = prop.typeThunk();
            if (!type) {
                throw Error(`assertion failed: typeThunk returned null (prop is ${JSON.stringify(prop)})`);
            }
        }
        else {
            throw Error(`assertion failed: type/typeThunk missing (prop is ${JSON.stringify(prop)})`);
        }
        const typeName = type.name || "??";
        const v = target;
        if (!v || typeof v !== "object") {
            throw new Checkable.SchemaError(`expected object for ${path.join(".")}, got ${typeof v} instead`);
        }
        const props = type.prototype[checkableInfoSym].props;
        const remainingPropNames = new Set(Object.getOwnPropertyNames(v));
        const obj = new type();
        for (const innerProp of props) {
            if (!remainingPropNames.has(innerProp.propertyKey)) {
                if (innerProp.optional) {
                    continue;
                }
                throw new Checkable.SchemaError(`Property ${innerProp.propertyKey} missing on ${path} of ${typeName}`);
            }
            if (!remainingPropNames.delete(innerProp.propertyKey)) {
                throw new Checkable.SchemaError("assertion failed");
            }
            const propVal = v[innerProp.propertyKey];
            obj[innerProp.propertyKey] = innerProp.checker(propVal, innerProp, path.concat([innerProp.propertyKey]));
        }
        if (!prop.extraAllowed && remainingPropNames.size !== 0) {
            const err = `superfluous properties ${JSON.stringify(Array.from(remainingPropNames.values()))} of ${typeName}`;
            throw new Checkable.SchemaError(err);
        }
        return obj;
    }
    /**
     * Class with checkable annotations on fields.
     * This annotation adds the implementation of the `checked`
     * static method.
     */
    function Class(opts = {}) {
        return (target) => {
            target.checked = (v) => {
                const cv = checkValue(v, {
                    checker: checkValue,
                    extraAllowed: !!opts.extra,
                    propertyKey: "(root)",
                    type: target,
                }, ["(root)"]);
                if (opts.validate) {
                    if (typeof target.validate !== "function") {
                        console.error("target", target);
                        throw Error("invalid Checkable annotion: validate method required");
                    }
                    // May throw exception
                    target.validate(cv);
                }
                return cv;
            };
            return target;
        };
    }
    Checkable.Class = Class;
    /**
     * Target property must be a Checkable object of the given type.
     */
    function Value(typeThunk) {
        function deco(target, propertyKey) {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkValue,
                propertyKey,
                typeThunk,
            });
        }
        return deco;
    }
    Checkable.Value = Value;
    /**
     * List of values that match the given annotation.  For example, `@Checkable.List(Checkable.String)` is
     * an annotation for a list of strings.
     */
    function List(type) {
        const stub = {};
        type(stub, "(list-element)");
        const elementProp = getCheckableInfo(stub).props[0];
        const elementChecker = elementProp.checker;
        if (!elementChecker) {
            throw Error("assertion failed");
        }
        function deco(target, propertyKey) {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkList,
                elementChecker,
                elementProp,
                propertyKey,
            });
        }
        return deco;
    }
    Checkable.List = List;
    /**
     * Map from the key type to value type.  Takes two annotations,
     * one for the key type and one for the value type.
     */
    function Map(keyType, valueType) {
        const keyStub = {};
        keyType(keyStub, "(map-key)");
        const keyProp = getCheckableInfo(keyStub).props[0];
        if (!keyProp) {
            throw Error("assertion failed");
        }
        const valueStub = {};
        valueType(valueStub, "(map-value)");
        const valueProp = getCheckableInfo(valueStub).props[0];
        if (!valueProp) {
            throw Error("assertion failed");
        }
        function deco(target, propertyKey) {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkMap,
                keyProp,
                propertyKey,
                valueProp,
            });
        }
        return deco;
    }
    Checkable.Map = Map;
    /**
     * Makes another annotation optional, for example `@Checkable.Optional(Checkable.Number)`.
     */
    function Optional(type) {
        const stub = {};
        type(stub, "(optional-element)");
        const elementProp = getCheckableInfo(stub).props[0];
        const elementChecker = elementProp.checker;
        if (!elementChecker) {
            throw Error("assertion failed");
        }
        function deco(target, propertyKey) {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkOptional,
                elementChecker,
                elementProp,
                optional: true,
                propertyKey,
            });
        }
        return deco;
    }
    Checkable.Optional = Optional;
    /**
     * Target property must be a number.
     */
    function Number() {
        const deco = (target, propertyKey) => {
            const chk = getCheckableInfo(target);
            chk.props.push({ checker: checkNumber, propertyKey });
        };
        return deco;
    }
    Checkable.Number = Number;
    /**
     * Target property must be an arbitary object.
     */
    function AnyObject() {
        const deco = (target, propertyKey) => {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkAnyObject,
                propertyKey,
            });
        };
        return deco;
    }
    Checkable.AnyObject = AnyObject;
    /**
     * Target property can be anything.
     *
     * Not useful by itself, but in combination with higher-order annotations
     * such as List or Map.
     */
    function Any() {
        const deco = (target, propertyKey) => {
            const chk = getCheckableInfo(target);
            chk.props.push({
                checker: checkAny,
                optional: true,
                propertyKey,
            });
        };
        return deco;
    }
    Checkable.Any = Any;
    /**
     * Target property must be a string.
     */
    function String(stringChecker) {
        const deco = (target, propertyKey) => {
            const chk = getCheckableInfo(target);
            chk.props.push({ checker: checkString, propertyKey, stringChecker });
        };
        return deco;
    }
    Checkable.String = String;
    /**
     * Target property must be a boolean value.
     */
    function Boolean() {
        const deco = (target, propertyKey) => {
            const chk = getCheckableInfo(target);
            chk.props.push({ checker: checkBoolean, propertyKey });
        };
        return deco;
    }
    Checkable.Boolean = Boolean;
})(Checkable = exports.Checkable || (exports.Checkable = {}));


/***/ }),
/* 4 */
/***/ (function(module, exports) {

// shim for using process in browser
var process = module.exports = {};

// cached from whatever global is present so that test runners that stub it
// don't break things.  But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals.  It's inside a
// function because try/catches deoptimize in certain engines.

var cachedSetTimeout;
var cachedClearTimeout;

function defaultSetTimout() {
    throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
    throw new Error('clearTimeout has not been defined');
}
(function () {
    try {
        if (typeof setTimeout === 'function') {
            cachedSetTimeout = setTimeout;
        } else {
            cachedSetTimeout = defaultSetTimout;
        }
    } catch (e) {
        cachedSetTimeout = defaultSetTimout;
    }
    try {
        if (typeof clearTimeout === 'function') {
            cachedClearTimeout = clearTimeout;
        } else {
            cachedClearTimeout = defaultClearTimeout;
        }
    } catch (e) {
        cachedClearTimeout = defaultClearTimeout;
    }
} ())
function runTimeout(fun) {
    if (cachedSetTimeout === setTimeout) {
        //normal enviroments in sane situations
        return setTimeout(fun, 0);
    }
    // if setTimeout wasn't available but was latter defined
    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
        cachedSetTimeout = setTimeout;
        return setTimeout(fun, 0);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedSetTimeout(fun, 0);
    } catch(e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
            return cachedSetTimeout.call(null, fun, 0);
        } catch(e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
            return cachedSetTimeout.call(this, fun, 0);
        }
    }


}
function runClearTimeout(marker) {
    if (cachedClearTimeout === clearTimeout) {
        //normal enviroments in sane situations
        return clearTimeout(marker);
    }
    // if clearTimeout wasn't available but was latter defined
    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
        cachedClearTimeout = clearTimeout;
        return clearTimeout(marker);
    }
    try {
        // when when somebody has screwed with setTimeout but no I.E. maddness
        return cachedClearTimeout(marker);
    } catch (e){
        try {
            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
            return cachedClearTimeout.call(null, marker);
        } catch (e){
            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
            return cachedClearTimeout.call(this, marker);
        }
    }



}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;

function cleanUpNextTick() {
    if (!draining || !currentQueue) {
        return;
    }
    draining = false;
    if (currentQueue.length) {
        queue = currentQueue.concat(queue);
    } else {
        queueIndex = -1;
    }
    if (queue.length) {
        drainQueue();
    }
}

function drainQueue() {
    if (draining) {
        return;
    }
    var timeout = runTimeout(cleanUpNextTick);
    draining = true;

    var len = queue.length;
    while(len) {
        currentQueue = queue;
        queue = [];
        while (++queueIndex < len) {
            if (currentQueue) {
                currentQueue[queueIndex].run();
            }
        }
        queueIndex = -1;
        len = queue.length;
    }
    currentQueue = null;
    draining = false;
    runClearTimeout(timeout);
}

process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
        for (var i = 1; i < arguments.length; i++) {
            args[i - 1] = arguments[i];
        }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
        runTimeout(drainQueue);
    }
};

// v8 likes predictible objects
function Item(fun, array) {
    this.fun = fun;
    this.array = array;
}
Item.prototype.run = function () {
    this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};

function noop() {}

process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;

process.listeners = function (name) { return [] }

process.binding = function (name) {
    throw new Error('process.binding is not supported');
};

process.cwd = function () { return '/' };
process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
 * URI.js - Mutating URLs
 *
 * Version: 1.19.0
 *
 * Author: Rodney Rehm
 * Web: http://medialize.github.io/URI.js/
 *
 * Licensed under
 *   MIT License http://www.opensource.org/licenses/mit-license
 *
 */
(function (root, factory) {
  'use strict';
  // https://github.com/umdjs/umd/blob/master/returnExports.js
  if (typeof module === 'object' && module.exports) {
    // Node
    module.exports = factory(__webpack_require__(11), __webpack_require__(12), __webpack_require__(13));
  } else if (true) {
    // AMD. Register as an anonymous module.
    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(11), __webpack_require__(12), __webpack_require__(13)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
				(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  } else {
    // Browser globals (root is window)
    root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
  }
}(this, function (punycode, IPv6, SLD, root) {
  'use strict';
  /*global location, escape, unescape */
  // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
  /*jshint camelcase: false */

  // save current URI variable, if any
  var _URI = root && root.URI;

  function URI(url, base) {
    var _urlSupplied = arguments.length >= 1;
    var _baseSupplied = arguments.length >= 2;

    // Allow instantiation without the 'new' keyword
    if (!(this instanceof URI)) {
      if (_urlSupplied) {
        if (_baseSupplied) {
          return new URI(url, base);
        }

        return new URI(url);
      }

      return new URI();
    }

    if (url === undefined) {
      if (_urlSupplied) {
        throw new TypeError('undefined is not a valid argument for URI');
      }

      if (typeof location !== 'undefined') {
        url = location.href + '';
      } else {
        url = '';
      }
    }

    if (url === null) {
      if (_urlSupplied) {
        throw new TypeError('null is not a valid argument for URI');
      }
    }

    this.href(url);

    // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
    if (base !== undefined) {
      return this.absoluteTo(base);
    }

    return this;
  }

  function isInteger(value) {
    return /^[0-9]+$/.test(value);
  }

  URI.version = '1.19.0';

  var p = URI.prototype;
  var hasOwn = Object.prototype.hasOwnProperty;

  function escapeRegEx(string) {
    // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
    return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
  }

  function getType(value) {
    // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
    if (value === undefined) {
      return 'Undefined';
    }

    return String(Object.prototype.toString.call(value)).slice(8, -1);
  }

  function isArray(obj) {
    return getType(obj) === 'Array';
  }

  function filterArrayValues(data, value) {
    var lookup = {};
    var i, length;

    if (getType(value) === 'RegExp') {
      lookup = null;
    } else if (isArray(value)) {
      for (i = 0, length = value.length; i < length; i++) {
        lookup[value[i]] = true;
      }
    } else {
      lookup[value] = true;
    }

    for (i = 0, length = data.length; i < length; i++) {
      /*jshint laxbreak: true */
      var _match = lookup && lookup[data[i]] !== undefined
        || !lookup && value.test(data[i]);
      /*jshint laxbreak: false */
      if (_match) {
        data.splice(i, 1);
        length--;
        i--;
      }
    }

    return data;
  }

  function arrayContains(list, value) {
    var i, length;

    // value may be string, number, array, regexp
    if (isArray(value)) {
      // Note: this can be optimized to O(n) (instead of current O(m * n))
      for (i = 0, length = value.length; i < length; i++) {
        if (!arrayContains(list, value[i])) {
          return false;
        }
      }

      return true;
    }

    var _type = getType(value);
    for (i = 0, length = list.length; i < length; i++) {
      if (_type === 'RegExp') {
        if (typeof list[i] === 'string' && list[i].match(value)) {
          return true;
        }
      } else if (list[i] === value) {
        return true;
      }
    }

    return false;
  }

  function arraysEqual(one, two) {
    if (!isArray(one) || !isArray(two)) {
      return false;
    }

    // arrays can't be equal if they have different amount of content
    if (one.length !== two.length) {
      return false;
    }

    one.sort();
    two.sort();

    for (var i = 0, l = one.length; i < l; i++) {
      if (one[i] !== two[i]) {
        return false;
      }
    }

    return true;
  }

  function trimSlashes(text) {
    var trim_expression = /^\/+|\/+$/g;
    return text.replace(trim_expression, '');
  }

  URI._parts = function() {
    return {
      protocol: null,
      username: null,
      password: null,
      hostname: null,
      urn: null,
      port: null,
      path: null,
      query: null,
      fragment: null,
      // state
      preventInvalidHostname: URI.preventInvalidHostname,
      duplicateQueryParameters: URI.duplicateQueryParameters,
      escapeQuerySpace: URI.escapeQuerySpace
    };
  };
  // state: throw on invalid hostname
  // see https://github.com/medialize/URI.js/pull/345
  // and https://github.com/medialize/URI.js/issues/354
  URI.preventInvalidHostname = false;
  // state: allow duplicate query parameters (a=1&a=1)
  URI.duplicateQueryParameters = false;
  // state: replaces + with %20 (space in query strings)
  URI.escapeQuerySpace = true;
  // static properties
  URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
  URI.idn_expression = /[^a-z0-9\._-]/i;
  URI.punycode_expression = /(xn--)/i;
  // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
  URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
  // credits to Rich Brown
  // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
  // specification: http://www.ietf.org/rfc/rfc4291.txt
  URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
  // expression used is "gruber revised" (@gruber v2) determined to be the
  // best solution in a regex-golf we did a couple of ages ago at
  // * http://mathiasbynens.be/demo/url-regex
  // * http://rodneyrehm.de/t/url-regex.html
  URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
  URI.findUri = {
    // valid "scheme://" or "www."
    start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
    // everything up to the next whitespace
    end: /[\s\r\n]|$/,
    // trim trailing punctuation captured by end RegExp
    trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/,
    // balanced parens inclusion (), [], {}, <>
    parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g,
  };
  // http://www.iana.org/assignments/uri-schemes.html
  // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
  URI.defaultPorts = {
    http: '80',
    https: '443',
    ftp: '21',
    gopher: '70',
    ws: '80',
    wss: '443'
  };
  // list of protocols which always require a hostname
  URI.hostProtocols = [
    'http',
    'https'
  ];

  // allowed hostname characters according to RFC 3986
  // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
  // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _
  URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/;
  // map DOM Elements to their URI attribute
  URI.domAttributes = {
    'a': 'href',
    'blockquote': 'cite',
    'link': 'href',
    'base': 'href',
    'script': 'src',
    'form': 'action',
    'img': 'src',
    'area': 'href',
    'iframe': 'src',
    'embed': 'src',
    'source': 'src',
    'track': 'src',
    'input': 'src', // but only if type="image"
    'audio': 'src',
    'video': 'src'
  };
  URI.getDomAttribute = function(node) {
    if (!node || !node.nodeName) {
      return undefined;
    }

    var nodeName = node.nodeName.toLowerCase();
    // <input> should only expose src for type="image"
    if (nodeName === 'input' && node.type !== 'image') {
      return undefined;
    }

    return URI.domAttributes[nodeName];
  };

  function escapeForDumbFirefox36(value) {
    // https://github.com/medialize/URI.js/issues/91
    return escape(value);
  }

  // encoding / decoding according to RFC3986
  function strictEncodeURIComponent(string) {
    // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
    return encodeURIComponent(string)
      .replace(/[!'()*]/g, escapeForDumbFirefox36)
      .replace(/\*/g, '%2A');
  }
  URI.encode = strictEncodeURIComponent;
  URI.decode = decodeURIComponent;
  URI.iso8859 = function() {
    URI.encode = escape;
    URI.decode = unescape;
  };
  URI.unicode = function() {
    URI.encode = strictEncodeURIComponent;
    URI.decode = decodeURIComponent;
  };
  URI.characters = {
    pathname: {
      encode: {
        // RFC3986 2.1: For consistency, URI producers and normalizers should
        // use uppercase hexadecimal digits for all percent-encodings.
        expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
        map: {
          // -._~!'()*
          '%24': '$',
          '%26': '&',
          '%2B': '+',
          '%2C': ',',
          '%3B': ';',
          '%3D': '=',
          '%3A': ':',
          '%40': '@'
        }
      },
      decode: {
        expression: /[\/\?#]/g,
        map: {
          '/': '%2F',
          '?': '%3F',
          '#': '%23'
        }
      }
    },
    reserved: {
      encode: {
        // RFC3986 2.1: For consistency, URI producers and normalizers should
        // use uppercase hexadecimal digits for all percent-encodings.
        expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
        map: {
          // gen-delims
          '%3A': ':',
          '%2F': '/',
          '%3F': '?',
          '%23': '#',
          '%5B': '[',
          '%5D': ']',
          '%40': '@',
          // sub-delims
          '%21': '!',
          '%24': '$',
          '%26': '&',
          '%27': '\'',
          '%28': '(',
          '%29': ')',
          '%2A': '*',
          '%2B': '+',
          '%2C': ',',
          '%3B': ';',
          '%3D': '='
        }
      }
    },
    urnpath: {
      // The characters under `encode` are the characters called out by RFC 2141 as being acceptable
      // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
      // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
      // note that the colon character is not featured in the encoding map; this is because URI.js
      // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
      // should not appear unencoded in a segment itself.
      // See also the note above about RFC3986 and capitalalized hex digits.
      encode: {
        expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
        map: {
          '%21': '!',
          '%24': '$',
          '%27': '\'',
          '%28': '(',
          '%29': ')',
          '%2A': '*',
          '%2B': '+',
          '%2C': ',',
          '%3B': ';',
          '%3D': '=',
          '%40': '@'
        }
      },
      // These characters are the characters called out by RFC2141 as "reserved" characters that
      // should never appear in a URN, plus the colon character (see note above).
      decode: {
        expression: /[\/\?#:]/g,
        map: {
          '/': '%2F',
          '?': '%3F',
          '#': '%23',
          ':': '%3A'
        }
      }
    }
  };
  URI.encodeQuery = function(string, escapeQuerySpace) {
    var escaped = URI.encode(string + '');
    if (escapeQuerySpace === undefined) {
      escapeQuerySpace = URI.escapeQuerySpace;
    }

    return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
  };
  URI.decodeQuery = function(string, escapeQuerySpace) {
    string += '';
    if (escapeQuerySpace === undefined) {
      escapeQuerySpace = URI.escapeQuerySpace;
    }

    try {
      return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
    } catch(e) {
      // we're not going to mess with weird encodings,
      // give up and return the undecoded original string
      // see https://github.com/medialize/URI.js/issues/87
      // see https://github.com/medialize/URI.js/issues/92
      return string;
    }
  };
  // generate encode/decode path functions
  var _parts = {'encode':'encode', 'decode':'decode'};
  var _part;
  var generateAccessor = function(_group, _part) {
    return function(string) {
      try {
        return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
          return URI.characters[_group][_part].map[c];
        });
      } catch (e) {
        // we're not going to mess with weird encodings,
        // give up and return the undecoded original string
        // see https://github.com/medialize/URI.js/issues/87
        // see https://github.com/medialize/URI.js/issues/92
        return string;
      }
    };
  };

  for (_part in _parts) {
    URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
    URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
  }

  var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
    return function(string) {
      // Why pass in names of functions, rather than the function objects themselves? The
      // definitions of some functions (but in particular, URI.decode) will occasionally change due
      // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
      // that the functions we use here are "fresh".
      var actualCodingFunc;
      if (!_innerCodingFuncName) {
        actualCodingFunc = URI[_codingFuncName];
      } else {
        actualCodingFunc = function(string) {
          return URI[_codingFuncName](URI[_innerCodingFuncName](string));
        };
      }

      var segments = (string + '').split(_sep);

      for (var i = 0, length = segments.length; i < length; i++) {
        segments[i] = actualCodingFunc(segments[i]);
      }

      return segments.join(_sep);
    };
  };

  // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
  URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
  URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
  URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
  URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');

  URI.encodeReserved = generateAccessor('reserved', 'encode');

  URI.parse = function(string, parts) {
    var pos;
    if (!parts) {
      parts = {
        preventInvalidHostname: URI.preventInvalidHostname
      };
    }
    // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]

    // extract fragment
    pos = string.indexOf('#');
    if (pos > -1) {
      // escaping?
      parts.fragment = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // extract query
    pos = string.indexOf('?');
    if (pos > -1) {
      // escaping?
      parts.query = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // extract protocol
    if (string.substring(0, 2) === '//') {
      // relative-scheme
      parts.protocol = null;
      string = string.substring(2);
      // extract "user:pass@host:port"
      string = URI.parseAuthority(string, parts);
    } else {
      pos = string.indexOf(':');
      if (pos > -1) {
        parts.protocol = string.substring(0, pos) || null;
        if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
          // : may be within the path
          parts.protocol = undefined;
        } else if (string.substring(pos + 1, pos + 3) === '//') {
          string = string.substring(pos + 3);

          // extract "user:pass@host:port"
          string = URI.parseAuthority(string, parts);
        } else {
          string = string.substring(pos + 1);
          parts.urn = true;
        }
      }
    }

    // what's left must be the path
    parts.path = string;

    // and we're done
    return parts;
  };
  URI.parseHost = function(string, parts) {
    if (!string) {
      string = '';
    }

    // Copy chrome, IE, opera backslash-handling behavior.
    // Back slashes before the query string get converted to forward slashes
    // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124
    // See: https://code.google.com/p/chromium/issues/detail?id=25916
    // https://github.com/medialize/URI.js/pull/233
    string = string.replace(/\\/g, '/');

    // extract host:port
    var pos = string.indexOf('/');
    var bracketPos;
    var t;

    if (pos === -1) {
      pos = string.length;
    }

    if (string.charAt(0) === '[') {
      // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
      // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
      // IPv6+port in the format [2001:db8::1]:80 (for the time being)
      bracketPos = string.indexOf(']');
      parts.hostname = string.substring(1, bracketPos) || null;
      parts.port = string.substring(bracketPos + 2, pos) || null;
      if (parts.port === '/') {
        parts.port = null;
      }
    } else {
      var firstColon = string.indexOf(':');
      var firstSlash = string.indexOf('/');
      var nextColon = string.indexOf(':', firstColon + 1);
      if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
        // IPv6 host contains multiple colons - but no port
        // this notation is actually not allowed by RFC 3986, but we're a liberal parser
        parts.hostname = string.substring(0, pos) || null;
        parts.port = null;
      } else {
        t = string.substring(0, pos).split(':');
        parts.hostname = t[0] || null;
        parts.port = t[1] || null;
      }
    }

    if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
      pos++;
      string = '/' + string;
    }

    if (parts.preventInvalidHostname) {
      URI.ensureValidHostname(parts.hostname, parts.protocol);
    }

    if (parts.port) {
      URI.ensureValidPort(parts.port);
    }

    return string.substring(pos) || '/';
  };
  URI.parseAuthority = function(string, parts) {
    string = URI.parseUserinfo(string, parts);
    return URI.parseHost(string, parts);
  };
  URI.parseUserinfo = function(string, parts) {
    // extract username:password
    var firstSlash = string.indexOf('/');
    var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
    var t;

    // authority@ must come before /path
    if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
      t = string.substring(0, pos).split(':');
      parts.username = t[0] ? URI.decode(t[0]) : null;
      t.shift();
      parts.password = t[0] ? URI.decode(t.join(':')) : null;
      string = string.substring(pos + 1);
    } else {
      parts.username = null;
      parts.password = null;
    }

    return string;
  };
  URI.parseQuery = function(string, escapeQuerySpace) {
    if (!string) {
      return {};
    }

    // throw out the funky business - "?"[name"="value"&"]+
    string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');

    if (!string) {
      return {};
    }

    var items = {};
    var splits = string.split('&');
    var length = splits.length;
    var v, name, value;

    for (var i = 0; i < length; i++) {
      v = splits[i].split('=');
      name = URI.decodeQuery(v.shift(), escapeQuerySpace);
      // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
      value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;

      if (hasOwn.call(items, name)) {
        if (typeof items[name] === 'string' || items[name] === null) {
          items[name] = [items[name]];
        }

        items[name].push(value);
      } else {
        items[name] = value;
      }
    }

    return items;
  };

  URI.build = function(parts) {
    var t = '';

    if (parts.protocol) {
      t += parts.protocol + ':';
    }

    if (!parts.urn && (t || parts.hostname)) {
      t += '//';
    }

    t += (URI.buildAuthority(parts) || '');

    if (typeof parts.path === 'string') {
      if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') {
        t += '/';
      }

      t += parts.path;
    }

    if (typeof parts.query === 'string' && parts.query) {
      t += '?' + parts.query;
    }

    if (typeof parts.fragment === 'string' && parts.fragment) {
      t += '#' + parts.fragment;
    }
    return t;
  };
  URI.buildHost = function(parts) {
    var t = '';

    if (!parts.hostname) {
      return '';
    } else if (URI.ip6_expression.test(parts.hostname)) {
      t += '[' + parts.hostname + ']';
    } else {
      t += parts.hostname;
    }

    if (parts.port) {
      t += ':' + parts.port;
    }

    return t;
  };
  URI.buildAuthority = function(parts) {
    return URI.buildUserinfo(parts) + URI.buildHost(parts);
  };
  URI.buildUserinfo = function(parts) {
    var t = '';

    if (parts.username) {
      t += URI.encode(parts.username);
    }

    if (parts.password) {
      t += ':' + URI.encode(parts.password);
    }

    if (t) {
      t += '@';
    }

    return t;
  };
  URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
    // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
    // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
    // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
    // URI.js treats the query string as being application/x-www-form-urlencoded
    // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type

    var t = '';
    var unique, key, i, length;
    for (key in data) {
      if (hasOwn.call(data, key) && key) {
        if (isArray(data[key])) {
          unique = {};
          for (i = 0, length = data[key].length; i < length; i++) {
            if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
              t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
              if (duplicateQueryParameters !== true) {
                unique[data[key][i] + ''] = true;
              }
            }
          }
        } else if (data[key] !== undefined) {
          t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
        }
      }
    }

    return t.substring(1);
  };
  URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
    // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
    // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
    return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
  };

  URI.addQuery = function(data, name, value) {
    if (typeof name === 'object') {
      for (var key in name) {
        if (hasOwn.call(name, key)) {
          URI.addQuery(data, key, name[key]);
        }
      }
    } else if (typeof name === 'string') {
      if (data[name] === undefined) {
        data[name] = value;
        return;
      } else if (typeof data[name] === 'string') {
        data[name] = [data[name]];
      }

      if (!isArray(value)) {
        value = [value];
      }

      data[name] = (data[name] || []).concat(value);
    } else {
      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
    }
  };

  URI.setQuery = function(data, name, value) {
    if (typeof name === 'object') {
      for (var key in name) {
        if (hasOwn.call(name, key)) {
          URI.setQuery(data, key, name[key]);
        }
      }
    } else if (typeof name === 'string') {
      data[name] = value === undefined ? null : value;
    } else {
      throw new TypeError('URI.setQuery() accepts an object, string as the name parameter');
    }
  };

  URI.removeQuery = function(data, name, value) {
    var i, length, key;

    if (isArray(name)) {
      for (i = 0, length = name.length; i < length; i++) {
        data[name[i]] = undefined;
      }
    } else if (getType(name) === 'RegExp') {
      for (key in data) {
        if (name.test(key)) {
          data[key] = undefined;
        }
      }
    } else if (typeof name === 'object') {
      for (key in name) {
        if (hasOwn.call(name, key)) {
          URI.removeQuery(data, key, name[key]);
        }
      }
    } else if (typeof name === 'string') {
      if (value !== undefined) {
        if (getType(value) === 'RegExp') {
          if (!isArray(data[name]) && value.test(data[name])) {
            data[name] = undefined;
          } else {
            data[name] = filterArrayValues(data[name], value);
          }
        } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) {
          data[name] = undefined;
        } else if (isArray(data[name])) {
          data[name] = filterArrayValues(data[name], value);
        }
      } else {
        data[name] = undefined;
      }
    } else {
      throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter');
    }
  };
  URI.hasQuery = function(data, name, value, withinArray) {
    switch (getType(name)) {
      case 'String':
        // Nothing to do here
        break;

      case 'RegExp':
        for (var key in data) {
          if (hasOwn.call(data, key)) {
            if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) {
              return true;
            }
          }
        }

        return false;

      case 'Object':
        for (var _key in name) {
          if (hasOwn.call(name, _key)) {
            if (!URI.hasQuery(data, _key, name[_key])) {
              return false;
            }
          }
        }

        return true;

      default:
        throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter');
    }

    switch (getType(value)) {
      case 'Undefined':
        // true if exists (but may be empty)
        return name in data; // data[name] !== undefined;

      case 'Boolean':
        // true if exists and non-empty
        var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
        return value === _booly;

      case 'Function':
        // allow complex comparison
        return !!value(data[name], name, data);

      case 'Array':
        if (!isArray(data[name])) {
          return false;
        }

        var op = withinArray ? arrayContains : arraysEqual;
        return op(data[name], value);

      case 'RegExp':
        if (!isArray(data[name])) {
          return Boolean(data[name] && data[name].match(value));
        }

        if (!withinArray) {
          return false;
        }

        return arrayContains(data[name], value);

      case 'Number':
        value = String(value);
        /* falls through */
      case 'String':
        if (!isArray(data[name])) {
          return data[name] === value;
        }

        if (!withinArray) {
          return false;
        }

        return arrayContains(data[name], value);

      default:
        throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
    }
  };


  URI.joinPaths = function() {
    var input = [];
    var segments = [];
    var nonEmptySegments = 0;

    for (var i = 0; i < arguments.length; i++) {
      var url = new URI(arguments[i]);
      input.push(url);
      var _segments = url.segment();
      for (var s = 0; s < _segments.length; s++) {
        if (typeof _segments[s] === 'string') {
          segments.push(_segments[s]);
        }

        if (_segments[s]) {
          nonEmptySegments++;
        }
      }
    }

    if (!segments.length || !nonEmptySegments) {
      return new URI('');
    }

    var uri = new URI('').segment(segments);

    if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') {
      uri.path('/' + uri.path());
    }

    return uri.normalize();
  };

  URI.commonPath = function(one, two) {
    var length = Math.min(one.length, two.length);
    var pos;

    // find first non-matching character
    for (pos = 0; pos < length; pos++) {
      if (one.charAt(pos) !== two.charAt(pos)) {
        pos--;
        break;
      }
    }

    if (pos < 1) {
      return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
    }

    // revert to last /
    if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
      pos = one.substring(0, pos).lastIndexOf('/');
    }

    return one.substring(0, pos + 1);
  };

  URI.withinString = function(string, callback, options) {
    options || (options = {});
    var _start = options.start || URI.findUri.start;
    var _end = options.end || URI.findUri.end;
    var _trim = options.trim || URI.findUri.trim;
    var _parens = options.parens || URI.findUri.parens;
    var _attributeOpen = /[a-z0-9-]=["']?$/i;

    _start.lastIndex = 0;
    while (true) {
      var match = _start.exec(string);
      if (!match) {
        break;
      }

      var start = match.index;
      if (options.ignoreHtml) {
        // attribut(e=["']?$)
        var attributeOpen = string.slice(Math.max(start - 3, 0), start);
        if (attributeOpen && _attributeOpen.test(attributeOpen)) {
          continue;
        }
      }

      var end = start + string.slice(start).search(_end);
      var slice = string.slice(start, end);
      // make sure we include well balanced parens
      var parensEnd = -1;
      while (true) {
        var parensMatch = _parens.exec(slice);
        if (!parensMatch) {
          break;
        }

        var parensMatchEnd = parensMatch.index + parensMatch[0].length;
        parensEnd = Math.max(parensEnd, parensMatchEnd);
      }

      if (parensEnd > -1) {
        slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, '');
      } else {
        slice = slice.replace(_trim, '');
      }

      if (slice.length <= match[0].length) {
        // the extract only contains the starting marker of a URI,
        // e.g. "www" or "http://"
        continue;
      }

      if (options.ignore && options.ignore.test(slice)) {
        continue;
      }

      end = start + slice.length;
      var result = callback(slice, start, end, string);
      if (result === undefined) {
        _start.lastIndex = end;
        continue;
      }

      result = String(result);
      string = string.slice(0, start) + result + string.slice(end);
      _start.lastIndex = start + result.length;
    }

    _start.lastIndex = 0;
    return string;
  };

  URI.ensureValidHostname = function(v, protocol) {
    // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
    // they are not part of DNS and therefore ignored by URI.js

    var hasHostname = !!v; // not null and not an empty string
    var hasProtocol = !!protocol;
    var rejectEmptyHostname = false;

    if (hasProtocol) {
      rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol);
    }

    if (rejectEmptyHostname && !hasHostname) {
      throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol);
    } else if (v && v.match(URI.invalid_hostname_characters)) {
      // test punycode
      if (!punycode) {
        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available');
      }
      if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]');
      }
    }
  };

  URI.ensureValidPort = function (v) {
    if (!v) {
      return;
    }

    var port = Number(v);
    if (isInteger(port) && (port > 0) && (port < 65536)) {
      return;
    }

    throw new TypeError('Port "' + v + '" is not a valid port');
  };

  // noConflict
  URI.noConflict = function(removeAll) {
    if (removeAll) {
      var unconflicted = {
        URI: this.noConflict()
      };

      if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
        unconflicted.URITemplate = root.URITemplate.noConflict();
      }

      if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
        unconflicted.IPv6 = root.IPv6.noConflict();
      }

      if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
        unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
      }

      return unconflicted;
    } else if (root.URI === this) {
      root.URI = _URI;
    }

    return this;
  };

  p.build = function(deferBuild) {
    if (deferBuild === true) {
      this._deferred_build = true;
    } else if (deferBuild === undefined || this._deferred_build) {
      this._string = URI.build(this._parts);
      this._deferred_build = false;
    }

    return this;
  };

  p.clone = function() {
    return new URI(this);
  };

  p.valueOf = p.toString = function() {
    return this.build(false)._string;
  };


  function generateSimpleAccessor(_part){
    return function(v, build) {
      if (v === undefined) {
        return this._parts[_part] || '';
      } else {
        this._parts[_part] = v || null;
        this.build(!build);
        return this;
      }
    };
  }

  function generatePrefixAccessor(_part, _key){
    return function(v, build) {
      if (v === undefined) {
        return this._parts[_part] || '';
      } else {
        if (v !== null) {
          v = v + '';
          if (v.charAt(0) === _key) {
            v = v.substring(1);
          }
        }

        this._parts[_part] = v;
        this.build(!build);
        return this;
      }
    };
  }

  p.protocol = generateSimpleAccessor('protocol');
  p.username = generateSimpleAccessor('username');
  p.password = generateSimpleAccessor('password');
  p.hostname = generateSimpleAccessor('hostname');
  p.port = generateSimpleAccessor('port');
  p.query = generatePrefixAccessor('query', '?');
  p.fragment = generatePrefixAccessor('fragment', '#');

  p.search = function(v, build) {
    var t = this.query(v, build);
    return typeof t === 'string' && t.length ? ('?' + t) : t;
  };
  p.hash = function(v, build) {
    var t = this.fragment(v, build);
    return typeof t === 'string' && t.length ? ('#' + t) : t;
  };

  p.pathname = function(v, build) {
    if (v === undefined || v === true) {
      var res = this._parts.path || (this._parts.hostname ? '/' : '');
      return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
    } else {
      if (this._parts.urn) {
        this._parts.path = v ? URI.recodeUrnPath(v) : '';
      } else {
        this._parts.path = v ? URI.recodePath(v) : '/';
      }
      this.build(!build);
      return this;
    }
  };
  p.path = p.pathname;
  p.href = function(href, build) {
    var key;

    if (href === undefined) {
      return this.toString();
    }

    this._string = '';
    this._parts = URI._parts();

    var _URI = href instanceof URI;
    var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
    if (href.nodeName) {
      var attribute = URI.getDomAttribute(href);
      href = href[attribute] || '';
      _object = false;
    }

    // window.location is reported to be an object, but it's not the sort
    // of object we're looking for:
    // * location.protocol ends with a colon
    // * location.query != object.search
    // * location.hash != object.fragment
    // simply serializing the unknown object should do the trick
    // (for location, not for everything...)
    if (!_URI && _object && href.pathname !== undefined) {
      href = href.toString();
    }

    if (typeof href === 'string' || href instanceof String) {
      this._parts = URI.parse(String(href), this._parts);
    } else if (_URI || _object) {
      var src = _URI ? href._parts : href;
      for (key in src) {
        if (hasOwn.call(this._parts, key)) {
          this._parts[key] = src[key];
        }
      }
    } else {
      throw new TypeError('invalid input');
    }

    this.build(!build);
    return this;
  };

  // identification accessors
  p.is = function(what) {
    var ip = false;
    var ip4 = false;
    var ip6 = false;
    var name = false;
    var sld = false;
    var idn = false;
    var punycode = false;
    var relative = !this._parts.urn;

    if (this._parts.hostname) {
      relative = false;
      ip4 = URI.ip4_expression.test(this._parts.hostname);
      ip6 = URI.ip6_expression.test(this._parts.hostname);
      ip = ip4 || ip6;
      name = !ip;
      sld = name && SLD && SLD.has(this._parts.hostname);
      idn = name && URI.idn_expression.test(this._parts.hostname);
      punycode = name && URI.punycode_expression.test(this._parts.hostname);
    }

    switch (what.toLowerCase()) {
      case 'relative':
        return relative;

      case 'absolute':
        return !relative;

      // hostname identification
      case 'domain':
      case 'name':
        return name;

      case 'sld':
        return sld;

      case 'ip':
        return ip;

      case 'ip4':
      case 'ipv4':
      case 'inet4':
        return ip4;

      case 'ip6':
      case 'ipv6':
      case 'inet6':
        return ip6;

      case 'idn':
        return idn;

      case 'url':
        return !this._parts.urn;

      case 'urn':
        return !!this._parts.urn;

      case 'punycode':
        return punycode;
    }

    return null;
  };

  // component specific input validation
  var _protocol = p.protocol;
  var _port = p.port;
  var _hostname = p.hostname;

  p.protocol = function(v, build) {
    if (v) {
      // accept trailing ://
      v = v.replace(/:(\/\/)?$/, '');

      if (!v.match(URI.protocol_expression)) {
        throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
      }
    }

    return _protocol.call(this, v, build);
  };
  p.scheme = p.protocol;
  p.port = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v !== undefined) {
      if (v === 0) {
        v = null;
      }

      if (v) {
        v += '';
        if (v.charAt(0) === ':') {
          v = v.substring(1);
        }

        URI.ensureValidPort(v);
      }
    }
    return _port.call(this, v, build);
  };
  p.hostname = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v !== undefined) {
      var x = { preventInvalidHostname: this._parts.preventInvalidHostname };
      var res = URI.parseHost(v, x);
      if (res !== '/') {
        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
      }

      v = x.hostname;
      if (this._parts.preventInvalidHostname) {
        URI.ensureValidHostname(v, this._parts.protocol);
      }
    }

    return _hostname.call(this, v, build);
  };

  // compound accessors
  p.origin = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined) {
      var protocol = this.protocol();
      var authority = this.authority();
      if (!authority) {
        return '';
      }

      return (protocol ? protocol + '://' : '') + this.authority();
    } else {
      var origin = URI(v);
      this
        .protocol(origin.protocol())
        .authority(origin.authority())
        .build(!build);
      return this;
    }
  };
  p.host = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined) {
      return this._parts.hostname ? URI.buildHost(this._parts) : '';
    } else {
      var res = URI.parseHost(v, this._parts);
      if (res !== '/') {
        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
      }

      this.build(!build);
      return this;
    }
  };
  p.authority = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined) {
      return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
    } else {
      var res = URI.parseAuthority(v, this._parts);
      if (res !== '/') {
        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
      }

      this.build(!build);
      return this;
    }
  };
  p.userinfo = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined) {
      var t = URI.buildUserinfo(this._parts);
      return t ? t.substring(0, t.length -1) : t;
    } else {
      if (v[v.length-1] !== '@') {
        v += '@';
      }

      URI.parseUserinfo(v, this._parts);
      this.build(!build);
      return this;
    }
  };
  p.resource = function(v, build) {
    var parts;

    if (v === undefined) {
      return this.path() + this.search() + this.hash();
    }

    parts = URI.parse(v);
    this._parts.path = parts.path;
    this._parts.query = parts.query;
    this._parts.fragment = parts.fragment;
    this.build(!build);
    return this;
  };

  // fraction accessors
  p.subdomain = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    // convenience, return "www" from "www.example.org"
    if (v === undefined) {
      if (!this._parts.hostname || this.is('IP')) {
        return '';
      }

      // grab domain and add another segment
      var end = this._parts.hostname.length - this.domain().length - 1;
      return this._parts.hostname.substring(0, end) || '';
    } else {
      var e = this._parts.hostname.length - this.domain().length;
      var sub = this._parts.hostname.substring(0, e);
      var replace = new RegExp('^' + escapeRegEx(sub));

      if (v && v.charAt(v.length - 1) !== '.') {
        v += '.';
      }

      if (v.indexOf(':') !== -1) {
        throw new TypeError('Domains cannot contain colons');
      }

      if (v) {
        URI.ensureValidHostname(v, this._parts.protocol);
      }

      this._parts.hostname = this._parts.hostname.replace(replace, v);
      this.build(!build);
      return this;
    }
  };
  p.domain = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (typeof v === 'boolean') {
      build = v;
      v = undefined;
    }

    // convenience, return "example.org" from "www.example.org"
    if (v === undefined) {
      if (!this._parts.hostname || this.is('IP')) {
        return '';
      }

      // if hostname consists of 1 or 2 segments, it must be the domain
      var t = this._parts.hostname.match(/\./g);
      if (t && t.length < 2) {
        return this._parts.hostname;
      }

      // grab tld and add another segment
      var end = this._parts.hostname.length - this.tld(build).length - 1;
      end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
      return this._parts.hostname.substring(end) || '';
    } else {
      if (!v) {
        throw new TypeError('cannot set domain empty');
      }

      if (v.indexOf(':') !== -1) {
        throw new TypeError('Domains cannot contain colons');
      }

      URI.ensureValidHostname(v, this._parts.protocol);

      if (!this._parts.hostname || this.is('IP')) {
        this._parts.hostname = v;
      } else {
        var replace = new RegExp(escapeRegEx(this.domain()) + '$');
        this._parts.hostname = this._parts.hostname.replace(replace, v);
      }

      this.build(!build);
      return this;
    }
  };
  p.tld = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (typeof v === 'boolean') {
      build = v;
      v = undefined;
    }

    // return "org" from "www.example.org"
    if (v === undefined) {
      if (!this._parts.hostname || this.is('IP')) {
        return '';
      }

      var pos = this._parts.hostname.lastIndexOf('.');
      var tld = this._parts.hostname.substring(pos + 1);

      if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
        return SLD.get(this._parts.hostname) || tld;
      }

      return tld;
    } else {
      var replace;

      if (!v) {
        throw new TypeError('cannot set TLD empty');
      } else if (v.match(/[^a-zA-Z0-9-]/)) {
        if (SLD && SLD.is(v)) {
          replace = new RegExp(escapeRegEx(this.tld()) + '$');
          this._parts.hostname = this._parts.hostname.replace(replace, v);
        } else {
          throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
        }
      } else if (!this._parts.hostname || this.is('IP')) {
        throw new ReferenceError('cannot set TLD on non-domain host');
      } else {
        replace = new RegExp(escapeRegEx(this.tld()) + '$');
        this._parts.hostname = this._parts.hostname.replace(replace, v);
      }

      this.build(!build);
      return this;
    }
  };
  p.directory = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined || v === true) {
      if (!this._parts.path && !this._parts.hostname) {
        return '';
      }

      if (this._parts.path === '/') {
        return '/';
      }

      var end = this._parts.path.length - this.filename().length - 1;
      var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');

      return v ? URI.decodePath(res) : res;

    } else {
      var e = this._parts.path.length - this.filename().length;
      var directory = this._parts.path.substring(0, e);
      var replace = new RegExp('^' + escapeRegEx(directory));

      // fully qualifier directories begin with a slash
      if (!this.is('relative')) {
        if (!v) {
          v = '/';
        }

        if (v.charAt(0) !== '/') {
          v = '/' + v;
        }
      }

      // directories always end with a slash
      if (v && v.charAt(v.length - 1) !== '/') {
        v += '/';
      }

      v = URI.recodePath(v);
      this._parts.path = this._parts.path.replace(replace, v);
      this.build(!build);
      return this;
    }
  };
  p.filename = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (typeof v !== 'string') {
      if (!this._parts.path || this._parts.path === '/') {
        return '';
      }

      var pos = this._parts.path.lastIndexOf('/');
      var res = this._parts.path.substring(pos+1);

      return v ? URI.decodePathSegment(res) : res;
    } else {
      var mutatedDirectory = false;

      if (v.charAt(0) === '/') {
        v = v.substring(1);
      }

      if (v.match(/\.?\//)) {
        mutatedDirectory = true;
      }

      var replace = new RegExp(escapeRegEx(this.filename()) + '$');
      v = URI.recodePath(v);
      this._parts.path = this._parts.path.replace(replace, v);

      if (mutatedDirectory) {
        this.normalizePath(build);
      } else {
        this.build(!build);
      }

      return this;
    }
  };
  p.suffix = function(v, build) {
    if (this._parts.urn) {
      return v === undefined ? '' : this;
    }

    if (v === undefined || v === true) {
      if (!this._parts.path || this._parts.path === '/') {
        return '';
      }

      var filename = this.filename();
      var pos = filename.lastIndexOf('.');
      var s, res;

      if (pos === -1) {
        return '';
      }

      // suffix may only contain alnum characters (yup, I made this up.)
      s = filename.substring(pos+1);
      res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
      return v ? URI.decodePathSegment(res) : res;
    } else {
      if (v.charAt(0) === '.') {
        v = v.substring(1);
      }

      var suffix = this.suffix();
      var replace;

      if (!suffix) {
        if (!v) {
          return this;
        }

        this._parts.path += '.' + URI.recodePath(v);
      } else if (!v) {
        replace = new RegExp(escapeRegEx('.' + suffix) + '$');
      } else {
        replace = new RegExp(escapeRegEx(suffix) + '$');
      }

      if (replace) {
        v = URI.recodePath(v);
        this._parts.path = this._parts.path.replace(replace, v);
      }

      this.build(!build);
      return this;
    }
  };
  p.segment = function(segment, v, build) {
    var separator = this._parts.urn ? ':' : '/';
    var path = this.path();
    var absolute = path.substring(0, 1) === '/';
    var segments = path.split(separator);

    if (segment !== undefined && typeof segment !== 'number') {
      build = v;
      v = segment;
      segment = undefined;
    }

    if (segment !== undefined && typeof segment !== 'number') {
      throw new Error('Bad segment "' + segment + '", must be 0-based integer');
    }

    if (absolute) {
      segments.shift();
    }

    if (segment < 0) {
      // allow negative indexes to address from the end
      segment = Math.max(segments.length + segment, 0);
    }

    if (v === undefined) {
      /*jshint laxbreak: true */
      return segment === undefined
        ? segments
        : segments[segment];
      /*jshint laxbreak: false */
    } else if (segment === null || segments[segment] === undefined) {
      if (isArray(v)) {
        segments = [];
        // collapse empty elements within array
        for (var i=0, l=v.length; i < l; i++) {
          if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
            continue;
          }

          if (segments.length && !segments[segments.length -1].length) {
            segments.pop();
          }

          segments.push(trimSlashes(v[i]));
        }
      } else if (v || typeof v === 'string') {
        v = trimSlashes(v);
        if (segments[segments.length -1] === '') {
          // empty trailing elements have to be overwritten
          // to prevent results such as /foo//bar
          segments[segments.length -1] = v;
        } else {
          segments.push(v);
        }
      }
    } else {
      if (v) {
        segments[segment] = trimSlashes(v);
      } else {
        segments.splice(segment, 1);
      }
    }

    if (absolute) {
      segments.unshift('');
    }

    return this.path(segments.join(separator), build);
  };
  p.segmentCoded = function(segment, v, build) {
    var segments, i, l;

    if (typeof segment !== 'number') {
      build = v;
      v = segment;
      segment = undefined;
    }

    if (v === undefined) {
      segments = this.segment(segment, v, build);
      if (!isArray(segments)) {
        segments = segments !== undefined ? URI.decode(segments) : undefined;
      } else {
        for (i = 0, l = segments.length; i < l; i++) {
          segments[i] = URI.decode(segments[i]);
        }
      }

      return segments;
    }

    if (!isArray(v)) {
      v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
    } else {
      for (i = 0, l = v.length; i < l; i++) {
        v[i] = URI.encode(v[i]);
      }
    }

    return this.segment(segment, v, build);
  };

  // mutating query string
  var q = p.query;
  p.query = function(v, build) {
    if (v === true) {
      return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
    } else if (typeof v === 'function') {
      var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
      var result = v.call(this, data);
      this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
      this.build(!build);
      return this;
    } else if (v !== undefined && typeof v !== 'string') {
      this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
      this.build(!build);
      return this;
    } else {
      return q.call(this, v, build);
    }
  };
  p.setQuery = function(name, value, build) {
    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);

    if (typeof name === 'string' || name instanceof String) {
      data[name] = value !== undefined ? value : null;
    } else if (typeof name === 'object') {
      for (var key in name) {
        if (hasOwn.call(name, key)) {
          data[key] = name[key];
        }
      }
    } else {
      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
    }

    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
    if (typeof name !== 'string') {
      build = value;
    }

    this.build(!build);
    return this;
  };
  p.addQuery = function(name, value, build) {
    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
    URI.addQuery(data, name, value === undefined ? null : value);
    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
    if (typeof name !== 'string') {
      build = value;
    }

    this.build(!build);
    return this;
  };
  p.removeQuery = function(name, value, build) {
    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
    URI.removeQuery(data, name, value);
    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
    if (typeof name !== 'string') {
      build = value;
    }

    this.build(!build);
    return this;
  };
  p.hasQuery = function(name, value, withinArray) {
    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
    return URI.hasQuery(data, name, value, withinArray);
  };
  p.setSearch = p.setQuery;
  p.addSearch = p.addQuery;
  p.removeSearch = p.removeQuery;
  p.hasSearch = p.hasQuery;

  // sanitizing URLs
  p.normalize = function() {
    if (this._parts.urn) {
      return this
        .normalizeProtocol(false)
        .normalizePath(false)
        .normalizeQuery(false)
        .normalizeFragment(false)
        .build();
    }

    return this
      .normalizeProtocol(false)
      .normalizeHostname(false)
      .normalizePort(false)
      .normalizePath(false)
      .normalizeQuery(false)
      .normalizeFragment(false)
      .build();
  };
  p.normalizeProtocol = function(build) {
    if (typeof this._parts.protocol === 'string') {
      this._parts.protocol = this._parts.protocol.toLowerCase();
      this.build(!build);
    }

    return this;
  };
  p.normalizeHostname = function(build) {
    if (this._parts.hostname) {
      if (this.is('IDN') && punycode) {
        this._parts.hostname = punycode.toASCII(this._parts.hostname);
      } else if (this.is('IPv6') && IPv6) {
        this._parts.hostname = IPv6.best(this._parts.hostname);
      }

      this._parts.hostname = this._parts.hostname.toLowerCase();
      this.build(!build);
    }

    return this;
  };
  p.normalizePort = function(build) {
    // remove port of it's the protocol's default
    if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
      this._parts.port = null;
      this.build(!build);
    }

    return this;
  };
  p.normalizePath = function(build) {
    var _path = this._parts.path;
    if (!_path) {
      return this;
    }

    if (this._parts.urn) {
      this._parts.path = URI.recodeUrnPath(this._parts.path);
      this.build(!build);
      return this;
    }

    if (this._parts.path === '/') {
      return this;
    }

    _path = URI.recodePath(_path);

    var _was_relative;
    var _leadingParents = '';
    var _parent, _pos;

    // handle relative paths
    if (_path.charAt(0) !== '/') {
      _was_relative = true;
      _path = '/' + _path;
    }

    // handle relative files (as opposed to directories)
    if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') {
      _path += '/';
    }

    // resolve simples
    _path = _path
      .replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
      .replace(/\/{2,}/g, '/');

    // remember leading parents
    if (_was_relative) {
      _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
      if (_leadingParents) {
        _leadingParents = _leadingParents[0];
      }
    }

    // resolve parents
    while (true) {
      _parent = _path.search(/\/\.\.(\/|$)/);
      if (_parent === -1) {
        // no more ../ to resolve
        break;
      } else if (_parent === 0) {
        // top level cannot be relative, skip it
        _path = _path.substring(3);
        continue;
      }

      _pos = _path.substring(0, _parent).lastIndexOf('/');
      if (_pos === -1) {
        _pos = _parent;
      }
      _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
    }

    // revert to relative
    if (_was_relative && this.is('relative')) {
      _path = _leadingParents + _path.substring(1);
    }

    this._parts.path = _path;
    this.build(!build);
    return this;
  };
  p.normalizePathname = p.normalizePath;
  p.normalizeQuery = function(build) {
    if (typeof this._parts.query === 'string') {
      if (!this._parts.query.length) {
        this._parts.query = null;
      } else {
        this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
      }

      this.build(!build);
    }

    return this;
  };
  p.normalizeFragment = function(build) {
    if (!this._parts.fragment) {
      this._parts.fragment = null;
      this.build(!build);
    }

    return this;
  };
  p.normalizeSearch = p.normalizeQuery;
  p.normalizeHash = p.normalizeFragment;

  p.iso8859 = function() {
    // expect unicode input, iso8859 output
    var e = URI.encode;
    var d = URI.decode;

    URI.encode = escape;
    URI.decode = decodeURIComponent;
    try {
      this.normalize();
    } finally {
      URI.encode = e;
      URI.decode = d;
    }
    return this;
  };

  p.unicode = function() {
    // expect iso8859 input, unicode output
    var e = URI.encode;
    var d = URI.decode;

    URI.encode = strictEncodeURIComponent;
    URI.decode = unescape;
    try {
      this.normalize();
    } finally {
      URI.encode = e;
      URI.decode = d;
    }
    return this;
  };

  p.readable = function() {
    var uri = this.clone();
    // removing username, password, because they shouldn't be displayed according to RFC 3986
    uri.username('').password('').normalize();
    var t = '';
    if (uri._parts.protocol) {
      t += uri._parts.protocol + '://';
    }

    if (uri._parts.hostname) {
      if (uri.is('punycode') && punycode) {
        t += punycode.toUnicode(uri._parts.hostname);
        if (uri._parts.port) {
          t += ':' + uri._parts.port;
        }
      } else {
        t += uri.host();
      }
    }

    if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
      t += '/';
    }

    t += uri.path(true);
    if (uri._parts.query) {
      var q = '';
      for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
        var kv = (qp[i] || '').split('=');
        q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
          .replace(/&/g, '%26');

        if (kv[1] !== undefined) {
          q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
            .replace(/&/g, '%26');
        }
      }
      t += '?' + q.substring(1);
    }

    t += URI.decodeQuery(uri.hash(), true);
    return t;
  };

  // resolving relative and absolute URLs
  p.absoluteTo = function(base) {
    var resolved = this.clone();
    var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
    var basedir, i, p;

    if (this._parts.urn) {
      throw new Error('URNs do not have any generally defined hierarchical components');
    }

    if (!(base instanceof URI)) {
      base = new URI(base);
    }

    if (resolved._parts.protocol) {
      // Directly returns even if this._parts.hostname is empty.
      return resolved;
    } else {
      resolved._parts.protocol = base._parts.protocol;
    }

    if (this._parts.hostname) {
      return resolved;
    }

    for (i = 0; (p = properties[i]); i++) {
      resolved._parts[p] = base._parts[p];
    }

    if (!resolved._parts.path) {
      resolved._parts.path = base._parts.path;
      if (!resolved._parts.query) {
        resolved._parts.query = base._parts.query;
      }
    } else {
      if (resolved._parts.path.substring(-2) === '..') {
        resolved._parts.path += '/';
      }

      if (resolved.path().charAt(0) !== '/') {
        basedir = base.directory();
        basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : '';
        resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
        resolved.normalizePath();
      }
    }

    resolved.build();
    return resolved;
  };
  p.relativeTo = function(base) {
    var relative = this.clone().normalize();
    var relativeParts, baseParts, common, relativePath, basePath;

    if (relative._parts.urn) {
      throw new Error('URNs do not have any generally defined hierarchical components');
    }

    base = new URI(base).normalize();
    relativeParts = relative._parts;
    baseParts = base._parts;
    relativePath = relative.path();
    basePath = base.path();

    if (relativePath.charAt(0) !== '/') {
      throw new Error('URI is already relative');
    }

    if (basePath.charAt(0) !== '/') {
      throw new Error('Cannot calculate a URI relative to another relative URI');
    }

    if (relativeParts.protocol === baseParts.protocol) {
      relativeParts.protocol = null;
    }

    if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
      return relative.build();
    }

    if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
      return relative.build();
    }

    if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
      relativeParts.hostname = null;
      relativeParts.port = null;
    } else {
      return relative.build();
    }

    if (relativePath === basePath) {
      relativeParts.path = '';
      return relative.build();
    }

    // determine common sub path
    common = URI.commonPath(relativePath, basePath);

    // If the paths have nothing in common, return a relative URL with the absolute path.
    if (!common) {
      return relative.build();
    }

    var parents = baseParts.path
      .substring(common.length)
      .replace(/[^\/]*$/, '')
      .replace(/.*?\//g, '../');

    relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './';

    return relative.build();
  };

  // comparing URIs
  p.equals = function(uri) {
    var one = this.clone();
    var two = new URI(uri);
    var one_map = {};
    var two_map = {};
    var checked = {};
    var one_query, two_query, key;

    one.normalize();
    two.normalize();

    // exact match
    if (one.toString() === two.toString()) {
      return true;
    }

    // extract query string
    one_query = one.query();
    two_query = two.query();
    one.query('');
    two.query('');

    // definitely not equal if not even non-query parts match
    if (one.toString() !== two.toString()) {
      return false;
    }

    // query parameters have the same length, even if they're permuted
    if (one_query.length !== two_query.length) {
      return false;
    }

    one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
    two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);

    for (key in one_map) {
      if (hasOwn.call(one_map, key)) {
        if (!isArray(one_map[key])) {
          if (one_map[key] !== two_map[key]) {
            return false;
          }
        } else if (!arraysEqual(one_map[key], two_map[key])) {
          return false;
        }

        checked[key] = true;
      }
    }

    for (key in two_map) {
      if (hasOwn.call(two_map, key)) {
        if (!checked[key]) {
          // two contains a parameter not present in one
          return false;
        }
      }
    }

    return true;
  };

  // state
  p.preventInvalidHostname = function(v) {
    this._parts.preventInvalidHostname = !!v;
    return this;
  };

  p.duplicateQueryParameters = function(v) {
    this._parts.duplicateQueryParameters = !!v;
    return this;
  };

  p.escapeQuerySpace = function(v) {
    this._parts.escapeQuerySpace = !!v;
    return this;
  };

  return URI;
}));


/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
/* WEBPACK VAR INJECTION */(function(process) {

var utils = __webpack_require__(0);
var normalizeHeaderName = __webpack_require__(38);

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = __webpack_require__(15);
  } else if (typeof process !== 'undefined') {
    // For node use HTTP adapter
    adapter = __webpack_require__(15);
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

  timeout: 0,

  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,

  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

defaults.headers = {
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))

/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2018 GNUnet e.V. and INRIA

 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/>
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Type and schema definitions for the base taler protocol.
 *
 * All types here should be "@Checkable".
 *
 * Even though the rest of the wallet uses camelCase for fields, use snake_case
 * here, since that's the convention for the Taler JSON+HTTP API.
 */
/**
 * Imports.
 */
const checkable_1 = __webpack_require__(3);
const Amounts = __webpack_require__(1);
const helpers_1 = __webpack_require__(10);
/**
 * Denomination as found in the /keys response from the exchange.
 */
let Denomination = class Denomination {
};
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], Denomination.prototype, "value", void 0);
__decorate([
    checkable_1.Checkable.String()
], Denomination.prototype, "denom_pub", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], Denomination.prototype, "fee_withdraw", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], Denomination.prototype, "fee_deposit", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], Denomination.prototype, "fee_refresh", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], Denomination.prototype, "fee_refund", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], Denomination.prototype, "stamp_start", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], Denomination.prototype, "stamp_expire_withdraw", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], Denomination.prototype, "stamp_expire_legal", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], Denomination.prototype, "stamp_expire_deposit", void 0);
__decorate([
    checkable_1.Checkable.String()
], Denomination.prototype, "master_sig", void 0);
Denomination = __decorate([
    checkable_1.Checkable.Class()
], Denomination);
exports.Denomination = Denomination;
/**
 * Signature by the auditor that a particular denomination key is audited.
 */
let AuditorDenomSig = class AuditorDenomSig {
};
__decorate([
    checkable_1.Checkable.String()
], AuditorDenomSig.prototype, "denom_pub_h", void 0);
__decorate([
    checkable_1.Checkable.String()
], AuditorDenomSig.prototype, "auditor_sig", void 0);
AuditorDenomSig = __decorate([
    checkable_1.Checkable.Class()
], AuditorDenomSig);
exports.AuditorDenomSig = AuditorDenomSig;
/**
 * Auditor information as given by the exchange in /keys.
 */
let Auditor = class Auditor {
};
__decorate([
    checkable_1.Checkable.String()
], Auditor.prototype, "auditor_pub", void 0);
__decorate([
    checkable_1.Checkable.String()
], Auditor.prototype, "auditor_url", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => AuditorDenomSig))
], Auditor.prototype, "denomination_keys", void 0);
Auditor = __decorate([
    checkable_1.Checkable.Class()
], Auditor);
exports.Auditor = Auditor;
/**
 * Response that we get from the exchange for a payback request.
 */
let PaybackConfirmation = class PaybackConfirmation {
};
__decorate([
    checkable_1.Checkable.String()
], PaybackConfirmation.prototype, "reserve_pub", void 0);
__decorate([
    checkable_1.Checkable.String()
], PaybackConfirmation.prototype, "amount", void 0);
__decorate([
    checkable_1.Checkable.String()
], PaybackConfirmation.prototype, "timestamp", void 0);
__decorate([
    checkable_1.Checkable.String()
], PaybackConfirmation.prototype, "exchange_sig", void 0);
__decorate([
    checkable_1.Checkable.String()
], PaybackConfirmation.prototype, "exchange_pub", void 0);
PaybackConfirmation = __decorate([
    checkable_1.Checkable.Class()
], PaybackConfirmation);
exports.PaybackConfirmation = PaybackConfirmation;
/**
 * Information about an exchange as stored inside a
 * merchant's contract terms.
 */
let ExchangeHandle = class ExchangeHandle {
};
__decorate([
    checkable_1.Checkable.String()
], ExchangeHandle.prototype, "master_pub", void 0);
__decorate([
    checkable_1.Checkable.String()
], ExchangeHandle.prototype, "url", void 0);
ExchangeHandle = __decorate([
    checkable_1.Checkable.Class()
], ExchangeHandle);
exports.ExchangeHandle = ExchangeHandle;
/**
 * Contract terms from a merchant.
 */
let ContractTerms = class ContractTerms {
    static validate(x) {
        if (x.exchanges.length === 0) {
            throw Error("no exchanges in contract terms");
        }
    }
};
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "H_wire", void 0);
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "wire_method", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.String())
], ContractTerms.prototype, "summary", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.String())
], ContractTerms.prototype, "nonce", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], ContractTerms.prototype, "amount", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.AnyObject())
], ContractTerms.prototype, "auditors", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.String())
], ContractTerms.prototype, "pay_deadline", void 0);
__decorate([
    checkable_1.Checkable.Any()
], ContractTerms.prototype, "locations", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], ContractTerms.prototype, "max_fee", void 0);
__decorate([
    checkable_1.Checkable.Any()
], ContractTerms.prototype, "merchant", void 0);
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "merchant_pub", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => ExchangeHandle))
], ContractTerms.prototype, "exchanges", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.AnyObject())
], ContractTerms.prototype, "products", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], ContractTerms.prototype, "refund_deadline", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], ContractTerms.prototype, "timestamp", void 0);
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "order_id", void 0);
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "pay_url", void 0);
__decorate([
    checkable_1.Checkable.String()
], ContractTerms.prototype, "fulfillment_url", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.Number())
], ContractTerms.prototype, "wire_fee_amortization", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.String())
], ContractTerms.prototype, "max_wire_fee", void 0);
__decorate([
    checkable_1.Checkable.Any()
], ContractTerms.prototype, "extra", void 0);
ContractTerms = __decorate([
    checkable_1.Checkable.Class({ validate: true })
], ContractTerms);
exports.ContractTerms = ContractTerms;
/**
 * Refund permission in the format that the merchant gives it to us.
 */
let MerchantRefundPermission = class MerchantRefundPermission {
};
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], MerchantRefundPermission.prototype, "refund_amount", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], MerchantRefundPermission.prototype, "refund_fee", void 0);
__decorate([
    checkable_1.Checkable.String()
], MerchantRefundPermission.prototype, "coin_pub", void 0);
__decorate([
    checkable_1.Checkable.Number()
], MerchantRefundPermission.prototype, "rtransaction_id", void 0);
__decorate([
    checkable_1.Checkable.String()
], MerchantRefundPermission.prototype, "merchant_sig", void 0);
MerchantRefundPermission = __decorate([
    checkable_1.Checkable.Class()
], MerchantRefundPermission);
exports.MerchantRefundPermission = MerchantRefundPermission;
/**
 * Response for a refund pickup or a /pay in abort mode.
 */
let MerchantRefundResponse = class MerchantRefundResponse {
};
__decorate([
    checkable_1.Checkable.String()
], MerchantRefundResponse.prototype, "merchant_pub", void 0);
__decorate([
    checkable_1.Checkable.String()
], MerchantRefundResponse.prototype, "h_contract_terms", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => MerchantRefundPermission))
], MerchantRefundResponse.prototype, "refund_permissions", void 0);
MerchantRefundResponse = __decorate([
    checkable_1.Checkable.Class()
], MerchantRefundResponse);
exports.MerchantRefundResponse = MerchantRefundResponse;
/**
 * Reserve signature, defined as separate class to facilitate
 * schema validation with "@Checkable".
 */
let ReserveSigSingleton = class ReserveSigSingleton {
};
__decorate([
    checkable_1.Checkable.String()
], ReserveSigSingleton.prototype, "reserve_sig", void 0);
ReserveSigSingleton = __decorate([
    checkable_1.Checkable.Class()
], ReserveSigSingleton);
exports.ReserveSigSingleton = ReserveSigSingleton;
/**
 * Response to /reserve/status
 */
let ReserveStatus = class ReserveStatus {
};
__decorate([
    checkable_1.Checkable.String()
], ReserveStatus.prototype, "balance", void 0);
__decorate([
    checkable_1.Checkable.Any()
], ReserveStatus.prototype, "history", void 0);
ReserveStatus = __decorate([
    checkable_1.Checkable.Class()
], ReserveStatus);
exports.ReserveStatus = ReserveStatus;
/**
 * Response of the merchant
 * to the TipPickupRequest.
 */
let TipResponse = class TipResponse {
};
__decorate([
    checkable_1.Checkable.String()
], TipResponse.prototype, "reserve_pub", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => ReserveSigSingleton))
], TipResponse.prototype, "reserve_sigs", void 0);
TipResponse = __decorate([
    checkable_1.Checkable.Class()
], TipResponse);
exports.TipResponse = TipResponse;
/**
 * Token containing all the information for the wallet
 * to process a tip.  Given by the merchant to the wallet.
 */
let TipToken = class TipToken {
};
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], TipToken.prototype, "expiration", void 0);
__decorate([
    checkable_1.Checkable.String()
], TipToken.prototype, "exchange_url", void 0);
__decorate([
    checkable_1.Checkable.String()
], TipToken.prototype, "pickup_url", void 0);
__decorate([
    checkable_1.Checkable.String()
], TipToken.prototype, "tip_id", void 0);
__decorate([
    checkable_1.Checkable.String()
], TipToken.prototype, "amount", void 0);
__decorate([
    checkable_1.Checkable.String()
], TipToken.prototype, "next_url", void 0);
TipToken = __decorate([
    checkable_1.Checkable.Class()
], TipToken);
exports.TipToken = TipToken;
/**
 * Element of the payback list that the
 * exchange gives us in /keys.
 */
let Payback = class Payback {
};
__decorate([
    checkable_1.Checkable.String()
], Payback.prototype, "h_denom_pub", void 0);
Payback = __decorate([
    checkable_1.Checkable.Class()
], Payback);
exports.Payback = Payback;
/**
 * Structure that the exchange gives us in /keys.
 */
let KeysJson = class KeysJson {
};
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => Denomination))
], KeysJson.prototype, "denoms", void 0);
__decorate([
    checkable_1.Checkable.String()
], KeysJson.prototype, "master_public_key", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => Auditor))
], KeysJson.prototype, "auditors", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], KeysJson.prototype, "list_issue_date", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.List(checkable_1.Checkable.Value(() => Payback)))
], KeysJson.prototype, "payback", void 0);
__decorate([
    checkable_1.Checkable.Any()
], KeysJson.prototype, "signkeys", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.String())
], KeysJson.prototype, "version", void 0);
KeysJson = __decorate([
    checkable_1.Checkable.Class({ extra: true })
], KeysJson);
exports.KeysJson = KeysJson;
/**
 * Wire fees as anounced by the exchange.
 */
let WireFeesJson = class WireFeesJson {
};
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], WireFeesJson.prototype, "wire_fee", void 0);
__decorate([
    checkable_1.Checkable.String(Amounts.check)
], WireFeesJson.prototype, "closing_fee", void 0);
__decorate([
    checkable_1.Checkable.String()
], WireFeesJson.prototype, "sig", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], WireFeesJson.prototype, "start_date", void 0);
__decorate([
    checkable_1.Checkable.String(helpers_1.timestampCheck)
], WireFeesJson.prototype, "end_date", void 0);
WireFeesJson = __decorate([
    checkable_1.Checkable.Class()
], WireFeesJson);
exports.WireFeesJson = WireFeesJson;
/**
 * Information about wire transfer methods supported
 * by the exchange.
 */
let WireDetailJson = class WireDetailJson {
};
__decorate([
    checkable_1.Checkable.String()
], WireDetailJson.prototype, "type", void 0);
__decorate([
    checkable_1.Checkable.List(checkable_1.Checkable.Value(() => WireFeesJson))
], WireDetailJson.prototype, "fees", void 0);
WireDetailJson = __decorate([
    checkable_1.Checkable.Class({ extra: true })
], WireDetailJson);
exports.WireDetailJson = WireDetailJson;
/**
 * Type guard for wire details.
 */
function isWireDetail(x) {
    return x && typeof x === "object" && typeof x.type === "string";
}
exports.isWireDetail = isWireDetail;
/**
 * Proposal returned from the contract URL.
 */
let Proposal = class Proposal {
};
__decorate([
    checkable_1.Checkable.Value(() => ContractTerms)
], Proposal.prototype, "contract_terms", void 0);
__decorate([
    checkable_1.Checkable.String()
], Proposal.prototype, "sig", void 0);
Proposal = __decorate([
    checkable_1.Checkable.Class({ extra: true })
], Proposal);
exports.Proposal = Proposal;


/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * An implementation of the [[HttpRequestLibrary]] using the
 * browser's XMLHttpRequest.
 */
class BrowserHttpLib {
    req(method, url, options) {
        return new Promise((resolve, reject) => {
            const myRequest = new XMLHttpRequest();
            myRequest.open(method, url);
            if (options && options.req) {
                myRequest.send(options.req);
            }
            else {
                myRequest.send();
            }
            myRequest.addEventListener("readystatechange", (e) => {
                if (myRequest.readyState === XMLHttpRequest.DONE) {
                    const resp = {
                        responseText: myRequest.responseText,
                        status: myRequest.status,
                    };
                    resolve(resp);
                }
            });
        });
    }
    get(url) {
        return this.req("get", url);
    }
    postJson(url, body) {
        return this.req("post", url, { req: JSON.stringify(body) });
    }
    postForm(url, form) {
        return this.req("post", url, { req: form });
    }
}
exports.BrowserHttpLib = BrowserHttpLib;
/**
 * Exception thrown on request errors.
 */
class RequestException {
    constructor(detail) {
        this.detail = detail;
    }
}
exports.RequestException = RequestException;


/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
/* WEBPACK VAR INJECTION */(function(process) {
/*
 This file is part of TALER
 (C) 2017 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/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
class IntervalHandle {
    constructor(h) {
        this.h = h;
    }
    clear() {
        clearTimeout(this.h);
    }
}
class TimeoutHandle {
    constructor(h) {
        this.h = h;
    }
    clear() {
        clearTimeout(this.h);
    }
}
/**
 * Get a performance counter in milliseconds.
 */
exports.performanceNow = (() => {
    if (typeof process !== "undefined" && process.hrtime) {
        return () => {
            const t = process.hrtime();
            return t[0] * 1e9 + t[1];
        };
    }
    else if (true) {
        return () => performance.now();
    }
    else {
        return () => 0;
    }
})();
/**
 * Call a function every time the delay given in milliseconds passes.
 */
function every(delayMs, callback) {
    return new IntervalHandle(setInterval(callback, delayMs));
}
exports.every = every;
/**
 * Call a function after the delay given in milliseconds passes.
 */
function after(delayMs, callback) {
    return new TimeoutHandle(setTimeout(callback, delayMs));
}
exports.after = after;
const nullTimerHandle = {
    clear() {
        // do nothing
        return;
    },
};
/**
 * Group of timers that can be destroyed at once.
 */
class TimerGroup {
    constructor() {
        this.stopped = false;
        this.timerMap = {};
        this.idGen = 1;
    }
    stopCurrentAndFutureTimers() {
        this.stopped = true;
        for (const x in this.timerMap) {
            if (!this.timerMap.hasOwnProperty(x)) {
                continue;
            }
            this.timerMap[x].clear();
            delete this.timerMap[x];
        }
    }
    after(delayMs, callback) {
        if (this.stopped) {
            console.warn("dropping timer since timer group is stopped");
            return nullTimerHandle;
        }
        const h = after(delayMs, callback);
        const myId = this.idGen++;
        this.timerMap[myId] = h;
        const tm = this.timerMap;
        return {
            clear() {
                h.clear();
                delete tm[myId];
            },
        };
    }
    every(delayMs, callback) {
        if (this.stopped) {
            console.warn("dropping timer since timer group is stopped");
            return nullTimerHandle;
        }
        const h = every(delayMs, callback);
        const myId = this.idGen++;
        this.timerMap[myId] = h;
        const tm = this.timerMap;
        return {
            clear() {
                h.clear();
                delete tm[myId];
            },
        };
    }
}
exports.TimerGroup = TimerGroup;

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))

/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
const Amounts = __webpack_require__(1);
const URI = __webpack_require__(5);
/**
 * Show an amount in a form suitable for the user.
 * FIXME:  In the future, this should consider currency-specific
 * settings such as significant digits or currency symbols.
 */
function amountToPretty(amount) {
    const x = amount.value + amount.fraction / Amounts.fractionalBase;
    return `${x} ${amount.currency}`;
}
exports.amountToPretty = amountToPretty;
/**
 * Canonicalize a base url, typically for the exchange.
 *
 * See http://api.taler.net/wallet.html#general
 */
function canonicalizeBaseUrl(url) {
    const x = new URI(url);
    if (!x.protocol()) {
        x.protocol("https");
    }
    x.path(x.path() + "/").normalizePath();
    x.fragment("");
    x.query();
    return x.href();
}
exports.canonicalizeBaseUrl = canonicalizeBaseUrl;
/**
 * Convert object to JSON with canonical ordering of keys
 * and whitespace omitted.
 */
function canonicalJson(obj) {
    // Check for cycles, etc.
    JSON.stringify(obj);
    if (typeof obj === "string" || typeof obj === "number" || obj === null) {
        return JSON.stringify(obj);
    }
    if (Array.isArray(obj)) {
        const objs = obj.map((e) => canonicalJson(e));
        return `[${objs.join(",")}]`;
    }
    const keys = [];
    for (const key in obj) {
        keys.push(key);
    }
    keys.sort();
    let s = "{";
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
        if (i !== keys.length - 1) {
            s += ",";
        }
    }
    return s + "}";
}
exports.canonicalJson = canonicalJson;
/**
 * Check for deep equality of two objects.
 * Only arrays, objects and primitives are supported.
 */
function deepEquals(x, y) {
    if (x === y) {
        return true;
    }
    if (Array.isArray(x) && x.length !== y.length) {
        return false;
    }
    const p = Object.keys(x);
    return Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
        p.every((i) => deepEquals(x[i], y[i]));
}
exports.deepEquals = deepEquals;
/**
 * Map from a collection to a list or results and then
 * concatenate the results.
 */
function flatMap(xs, f) {
    return xs.reduce((acc, next) => [...f(next), ...acc], []);
}
exports.flatMap = flatMap;
/**
 * Extract a numeric timstamp (in seconds) from the Taler date format
 * ("/Date([n])/").  Returns null if input is not in the right format.
 */
function getTalerStampSec(stamp) {
    const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
    if (!m || !m[1]) {
        return null;
    }
    return parseInt(m[1], 10);
}
exports.getTalerStampSec = getTalerStampSec;
/**
 * Check if a timestamp is in the right format.
 */
function timestampCheck(stamp) {
    return getTalerStampSec(stamp) !== null;
}
exports.timestampCheck = timestampCheck;
/**
 * Get a JavaScript Date object from a Taler date string.
 * Returns null if input is not in the right format.
 */
function getTalerStampDate(stamp) {
    const sec = getTalerStampSec(stamp);
    if (sec == null) {
        return null;
    }
    return new Date(sec * 1000);
}
exports.getTalerStampDate = getTalerStampDate;
/**
 * Compute the hash function of a JSON object.
 */
function hash(val) {
    const str = canonicalJson(val);
    // https://github.com/darkskyapp/string-hash
    let h = 5381;
    let i = str.length;
    while (i) {
        h = (h * 33) ^ str.charCodeAt(--i);
    }
    /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
    * integers. Since we want the results to be always positive, convert the
    * signed int to an unsigned by doing an unsigned bitshift. */
    return h >>> 0;
}
exports.hash = hash;
/**
 * Lexically compare two strings.
 */
function strcmp(s1, s2) {
    if (s1 < s2) {
        return -1;
    }
    if (s1 > s2) {
        return 1;
    }
    return 0;
}
exports.strcmp = strcmp;


/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(module, global) {var __WEBPACK_AMD_DEFINE_RESULT__;/*! https://mths.be/punycode v1.4.0 by @mathias */
;(function(root) {

	/** Detect free variables */
	var freeExports = typeof exports == 'object' && exports &&
		!exports.nodeType && exports;
	var freeModule = typeof module == 'object' && module &&
		!module.nodeType && module;
	var freeGlobal = typeof global == 'object' && global;
	if (
		freeGlobal.global === freeGlobal ||
		freeGlobal.window === freeGlobal ||
		freeGlobal.self === freeGlobal
	) {
		root = freeGlobal;
	}

	/**
	 * The `punycode` object.
	 * @name punycode
	 * @type Object
	 */
	var punycode,

	/** Highest positive signed 32-bit float value */
	maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1

	/** Bootstring parameters */
	base = 36,
	tMin = 1,
	tMax = 26,
	skew = 38,
	damp = 700,
	initialBias = 72,
	initialN = 128, // 0x80
	delimiter = '-', // '\x2D'

	/** Regular expressions */
	regexPunycode = /^xn--/,
	regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
	regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators

	/** Error messages */
	errors = {
		'overflow': 'Overflow: input needs wider integers to process',
		'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
		'invalid-input': 'Invalid input'
	},

	/** Convenience shortcuts */
	baseMinusTMin = base - tMin,
	floor = Math.floor,
	stringFromCharCode = String.fromCharCode,

	/** Temporary variable */
	key;

	/*--------------------------------------------------------------------------*/

	/**
	 * A generic error utility function.
	 * @private
	 * @param {String} type The error type.
	 * @returns {Error} Throws a `RangeError` with the applicable error message.
	 */
	function error(type) {
		throw new RangeError(errors[type]);
	}

	/**
	 * A generic `Array#map` utility function.
	 * @private
	 * @param {Array} array The array to iterate over.
	 * @param {Function} callback The function that gets called for every array
	 * item.
	 * @returns {Array} A new array of values returned by the callback function.
	 */
	function map(array, fn) {
		var length = array.length;
		var result = [];
		while (length--) {
			result[length] = fn(array[length]);
		}
		return result;
	}

	/**
	 * A simple `Array#map`-like wrapper to work with domain name strings or email
	 * addresses.
	 * @private
	 * @param {String} domain The domain name or email address.
	 * @param {Function} callback The function that gets called for every
	 * character.
	 * @returns {Array} A new string of characters returned by the callback
	 * function.
	 */
	function mapDomain(string, fn) {
		var parts = string.split('@');
		var result = '';
		if (parts.length > 1) {
			// In email addresses, only the domain name should be punycoded. Leave
			// the local part (i.e. everything up to `@`) intact.
			result = parts[0] + '@';
			string = parts[1];
		}
		// Avoid `split(regex)` for IE8 compatibility. See #17.
		string = string.replace(regexSeparators, '\x2E');
		var labels = string.split('.');
		var encoded = map(labels, fn).join('.');
		return result + encoded;
	}

	/**
	 * Creates an array containing the numeric code points of each Unicode
	 * character in the string. While JavaScript uses UCS-2 internally,
	 * this function will convert a pair of surrogate halves (each of which
	 * UCS-2 exposes as separate characters) into a single code point,
	 * matching UTF-16.
	 * @see `punycode.ucs2.encode`
	 * @see <https://mathiasbynens.be/notes/javascript-encoding>
	 * @memberOf punycode.ucs2
	 * @name decode
	 * @param {String} string The Unicode input string (UCS-2).
	 * @returns {Array} The new array of code points.
	 */
	function ucs2decode(string) {
		var output = [],
		    counter = 0,
		    length = string.length,
		    value,
		    extra;
		while (counter < length) {
			value = string.charCodeAt(counter++);
			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
				// high surrogate, and there is a next character
				extra = string.charCodeAt(counter++);
				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
				} else {
					// unmatched surrogate; only append this code unit, in case the next
					// code unit is the high surrogate of a surrogate pair
					output.push(value);
					counter--;
				}
			} else {
				output.push(value);
			}
		}
		return output;
	}

	/**
	 * Creates a string based on an array of numeric code points.
	 * @see `punycode.ucs2.decode`
	 * @memberOf punycode.ucs2
	 * @name encode
	 * @param {Array} codePoints The array of numeric code points.
	 * @returns {String} The new Unicode string (UCS-2).
	 */
	function ucs2encode(array) {
		return map(array, function(value) {
			var output = '';
			if (value > 0xFFFF) {
				value -= 0x10000;
				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
				value = 0xDC00 | value & 0x3FF;
			}
			output += stringFromCharCode(value);
			return output;
		}).join('');
	}

	/**
	 * Converts a basic code point into a digit/integer.
	 * @see `digitToBasic()`
	 * @private
	 * @param {Number} codePoint The basic numeric code point value.
	 * @returns {Number} The numeric value of a basic code point (for use in
	 * representing integers) in the range `0` to `base - 1`, or `base` if
	 * the code point does not represent a value.
	 */
	function basicToDigit(codePoint) {
		if (codePoint - 48 < 10) {
			return codePoint - 22;
		}
		if (codePoint - 65 < 26) {
			return codePoint - 65;
		}
		if (codePoint - 97 < 26) {
			return codePoint - 97;
		}
		return base;
	}

	/**
	 * Converts a digit/integer into a basic code point.
	 * @see `basicToDigit()`
	 * @private
	 * @param {Number} digit The numeric value of a basic code point.
	 * @returns {Number} The basic code point whose value (when used for
	 * representing integers) is `digit`, which needs to be in the range
	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
	 * used; else, the lowercase form is used. The behavior is undefined
	 * if `flag` is non-zero and `digit` has no uppercase form.
	 */
	function digitToBasic(digit, flag) {
		//  0..25 map to ASCII a..z or A..Z
		// 26..35 map to ASCII 0..9
		return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
	}

	/**
	 * Bias adaptation function as per section 3.4 of RFC 3492.
	 * https://tools.ietf.org/html/rfc3492#section-3.4
	 * @private
	 */
	function adapt(delta, numPoints, firstTime) {
		var k = 0;
		delta = firstTime ? floor(delta / damp) : delta >> 1;
		delta += floor(delta / numPoints);
		for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
			delta = floor(delta / baseMinusTMin);
		}
		return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
	}

	/**
	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
	 * symbols.
	 * @memberOf punycode
	 * @param {String} input The Punycode string of ASCII-only symbols.
	 * @returns {String} The resulting string of Unicode symbols.
	 */
	function decode(input) {
		// Don't use UCS-2
		var output = [],
		    inputLength = input.length,
		    out,
		    i = 0,
		    n = initialN,
		    bias = initialBias,
		    basic,
		    j,
		    index,
		    oldi,
		    w,
		    k,
		    digit,
		    t,
		    /** Cached calculation results */
		    baseMinusT;

		// Handle the basic code points: let `basic` be the number of input code
		// points before the last delimiter, or `0` if there is none, then copy
		// the first basic code points to the output.

		basic = input.lastIndexOf(delimiter);
		if (basic < 0) {
			basic = 0;
		}

		for (j = 0; j < basic; ++j) {
			// if it's not a basic code point
			if (input.charCodeAt(j) >= 0x80) {
				error('not-basic');
			}
			output.push(input.charCodeAt(j));
		}

		// Main decoding loop: start just after the last delimiter if any basic code
		// points were copied; start at the beginning otherwise.

		for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {

			// `index` is the index of the next character to be consumed.
			// Decode a generalized variable-length integer into `delta`,
			// which gets added to `i`. The overflow checking is easier
			// if we increase `i` as we go, then subtract off its starting
			// value at the end to obtain `delta`.
			for (oldi = i, w = 1, k = base; /* no condition */; k += base) {

				if (index >= inputLength) {
					error('invalid-input');
				}

				digit = basicToDigit(input.charCodeAt(index++));

				if (digit >= base || digit > floor((maxInt - i) / w)) {
					error('overflow');
				}

				i += digit * w;
				t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);

				if (digit < t) {
					break;
				}

				baseMinusT = base - t;
				if (w > floor(maxInt / baseMinusT)) {
					error('overflow');
				}

				w *= baseMinusT;

			}

			out = output.length + 1;
			bias = adapt(i - oldi, out, oldi == 0);

			// `i` was supposed to wrap around from `out` to `0`,
			// incrementing `n` each time, so we'll fix that now:
			if (floor(i / out) > maxInt - n) {
				error('overflow');
			}

			n += floor(i / out);
			i %= out;

			// Insert `n` at position `i` of the output
			output.splice(i++, 0, n);

		}

		return ucs2encode(output);
	}

	/**
	 * Converts a string of Unicode symbols (e.g. a domain name label) to a
	 * Punycode string of ASCII-only symbols.
	 * @memberOf punycode
	 * @param {String} input The string of Unicode symbols.
	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
	 */
	function encode(input) {
		var n,
		    delta,
		    handledCPCount,
		    basicLength,
		    bias,
		    j,
		    m,
		    q,
		    k,
		    t,
		    currentValue,
		    output = [],
		    /** `inputLength` will hold the number of code points in `input`. */
		    inputLength,
		    /** Cached calculation results */
		    handledCPCountPlusOne,
		    baseMinusT,
		    qMinusT;

		// Convert the input in UCS-2 to Unicode
		input = ucs2decode(input);

		// Cache the length
		inputLength = input.length;

		// Initialize the state
		n = initialN;
		delta = 0;
		bias = initialBias;

		// Handle the basic code points
		for (j = 0; j < inputLength; ++j) {
			currentValue = input[j];
			if (currentValue < 0x80) {
				output.push(stringFromCharCode(currentValue));
			}
		}

		handledCPCount = basicLength = output.length;

		// `handledCPCount` is the number of code points that have been handled;
		// `basicLength` is the number of basic code points.

		// Finish the basic string - if it is not empty - with a delimiter
		if (basicLength) {
			output.push(delimiter);
		}

		// Main encoding loop:
		while (handledCPCount < inputLength) {

			// All non-basic code points < n have been handled already. Find the next
			// larger one:
			for (m = maxInt, j = 0; j < inputLength; ++j) {
				currentValue = input[j];
				if (currentValue >= n && currentValue < m) {
					m = currentValue;
				}
			}

			// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
			// but guard against overflow
			handledCPCountPlusOne = handledCPCount + 1;
			if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
				error('overflow');
			}

			delta += (m - n) * handledCPCountPlusOne;
			n = m;

			for (j = 0; j < inputLength; ++j) {
				currentValue = input[j];

				if (currentValue < n && ++delta > maxInt) {
					error('overflow');
				}

				if (currentValue == n) {
					// Represent delta as a generalized variable-length integer
					for (q = delta, k = base; /* no condition */; k += base) {
						t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
						if (q < t) {
							break;
						}
						qMinusT = q - t;
						baseMinusT = base - t;
						output.push(
							stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
						);
						q = floor(qMinusT / baseMinusT);
					}

					output.push(stringFromCharCode(digitToBasic(q, 0)));
					bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
					delta = 0;
					++handledCPCount;
				}
			}

			++delta;
			++n;

		}
		return output.join('');
	}

	/**
	 * Converts a Punycode string representing a domain name or an email address
	 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
	 * it doesn't matter if you call it on a string that has already been
	 * converted to Unicode.
	 * @memberOf punycode
	 * @param {String} input The Punycoded domain name or email address to
	 * convert to Unicode.
	 * @returns {String} The Unicode representation of the given Punycode
	 * string.
	 */
	function toUnicode(input) {
		return mapDomain(input, function(string) {
			return regexPunycode.test(string)
				? decode(string.slice(4).toLowerCase())
				: string;
		});
	}

	/**
	 * Converts a Unicode string representing a domain name or an email address to
	 * Punycode. Only the non-ASCII parts of the domain name will be converted,
	 * i.e. it doesn't matter if you call it with a domain that's already in
	 * ASCII.
	 * @memberOf punycode
	 * @param {String} input The domain name or email address to convert, as a
	 * Unicode string.
	 * @returns {String} The Punycode representation of the given domain name or
	 * email address.
	 */
	function toASCII(input) {
		return mapDomain(input, function(string) {
			return regexNonASCII.test(string)
				? 'xn--' + encode(string)
				: string;
		});
	}

	/*--------------------------------------------------------------------------*/

	/** Define the public API */
	punycode = {
		/**
		 * A string representing the current Punycode.js version number.
		 * @memberOf punycode
		 * @type String
		 */
		'version': '1.3.2',
		/**
		 * An object of methods to convert from JavaScript's internal character
		 * representation (UCS-2) to Unicode code points, and back.
		 * @see <https://mathiasbynens.be/notes/javascript-encoding>
		 * @memberOf punycode
		 * @type Object
		 */
		'ucs2': {
			'decode': ucs2decode,
			'encode': ucs2encode
		},
		'decode': decode,
		'encode': encode,
		'toASCII': toASCII,
		'toUnicode': toUnicode
	};

	/** Expose `punycode` */
	// Some AMD build optimizers, like r.js, check for specific condition patterns
	// like the following:
	if (
		true
	) {
		!(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
			return punycode;
		}).call(exports, __webpack_require__, exports, module),
				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	} else if (freeExports && freeModule) {
		if (module.exports == freeExports) {
			// in Node.js, io.js, or RingoJS v0.8.0+
			freeModule.exports = punycode;
		} else {
			// in Narwhal or RingoJS v0.7.0-
			for (key in punycode) {
				punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
			}
		}
	} else {
		// in Rhino or a web browser
		root.punycode = punycode;
	}

}(this));

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(31)(module), __webpack_require__(32)))

/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
 * URI.js - Mutating URLs
 * IPv6 Support
 *
 * Version: 1.19.0
 *
 * Author: Rodney Rehm
 * Web: http://medialize.github.io/URI.js/
 *
 * Licensed under
 *   MIT License http://www.opensource.org/licenses/mit-license
 *
 */

(function (root, factory) {
  'use strict';
  // https://github.com/umdjs/umd/blob/master/returnExports.js
  if (typeof module === 'object' && module.exports) {
    // Node
    module.exports = factory();
  } else if (true) {
    // AMD. Register as an anonymous module.
    !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
				(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
				__WEBPACK_AMD_DEFINE_FACTORY__),
				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  } else {
    // Browser globals (root is window)
    root.IPv6 = factory(root);
  }
}(this, function (root) {
  'use strict';

  /*
  var _in = "fe80:0000:0000:0000:0204:61ff:fe9d:f156";
  var _out = IPv6.best(_in);
  var _expected = "fe80::204:61ff:fe9d:f156";

  console.log(_in, _out, _expected, _out === _expected);
  */

  // save current IPv6 variable, if any
  var _IPv6 = root && root.IPv6;

  function bestPresentation(address) {
    // based on:
    // Javascript to test an IPv6 address for proper format, and to
    // present the "best text representation" according to IETF Draft RFC at
    // http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04
    // 8 Feb 2010 Rich Brown, Dartware, LLC
    // Please feel free to use this code as long as you provide a link to
    // http://www.intermapper.com
    // http://intermapper.com/support/tools/IPV6-Validator.aspx
    // http://download.dartware.com/thirdparty/ipv6validator.js

    var _address = address.toLowerCase();
    var segments = _address.split(':');
    var length = segments.length;
    var total = 8;

    // trim colons (:: or ::a:b:c… or …a:b:c::)
    if (segments[0] === '' && segments[1] === '' && segments[2] === '') {
      // must have been ::
      // remove first two items
      segments.shift();
      segments.shift();
    } else if (segments[0] === '' && segments[1] === '') {
      // must have been ::xxxx
      // remove the first item
      segments.shift();
    } else if (segments[length - 1] === '' && segments[length - 2] === '') {
      // must have been xxxx::
      segments.pop();
    }

    length = segments.length;

    // adjust total segments for IPv4 trailer
    if (segments[length - 1].indexOf('.') !== -1) {
      // found a "." which means IPv4
      total = 7;
    }

    // fill empty segments them with "0000"
    var pos;
    for (pos = 0; pos < length; pos++) {
      if (segments[pos] === '') {
        break;
      }
    }

    if (pos < total) {
      segments.splice(pos, 1, '0000');
      while (segments.length < total) {
        segments.splice(pos, 0, '0000');
      }
    }

    // strip leading zeros
    var _segments;
    for (var i = 0; i < total; i++) {
      _segments = segments[i].split('');
      for (var j = 0; j < 3 ; j++) {
        if (_segments[0] === '0' && _segments.length > 1) {
          _segments.splice(0,1);
        } else {
          break;
        }
      }

      segments[i] = _segments.join('');
    }

    // find longest sequence of zeroes and coalesce them into one segment
    var best = -1;
    var _best = 0;
    var _current = 0;
    var current = -1;
    var inzeroes = false;
    // i; already declared

    for (i = 0; i < total; i++) {
      if (inzeroes) {
        if (segments[i] === '0') {
          _current += 1;
        } else {
          inzeroes = false;
          if (_current > _best) {
            best = current;
            _best = _current;
          }
        }
      } else {
        if (segments[i] === '0') {
          inzeroes = true;
          current = i;
          _current = 1;
        }
      }
    }

    if (_current > _best) {
      best = current;
      _best = _current;
    }

    if (_best > 1) {
      segments.splice(best, _best, '');
    }

    length = segments.length;

    // assemble remaining segments
    var result = '';
    if (segments[0] === '')  {
      result = ':';
    }

    for (i = 0; i < length; i++) {
      result += segments[i];
      if (i === length - 1) {
        break;
      }

      result += ':';
    }

    if (segments[length - 1] === '') {
      result += ':';
    }

    return result;
  }

  function noConflict() {
    /*jshint validthis: true */
    if (root.IPv6 === this) {
      root.IPv6 = _IPv6;
    }

    return this;
  }

  return {
    best: bestPresentation,
    noConflict: noConflict
  };
}));


/***/ }),
/* 13 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
 * URI.js - Mutating URLs
 * Second Level Domain (SLD) Support
 *
 * Version: 1.19.0
 *
 * Author: Rodney Rehm
 * Web: http://medialize.github.io/URI.js/
 *
 * Licensed under
 *   MIT License http://www.opensource.org/licenses/mit-license
 *
 */

(function (root, factory) {
  'use strict';
  // https://github.com/umdjs/umd/blob/master/returnExports.js
  if (typeof module === 'object' && module.exports) {
    // Node
    module.exports = factory();
  } else if (true) {
    // AMD. Register as an anonymous module.
    !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
				(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
				__WEBPACK_AMD_DEFINE_FACTORY__),
				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  } else {
    // Browser globals (root is window)
    root.SecondLevelDomains = factory(root);
  }
}(this, function (root) {
  'use strict';

  // save current SecondLevelDomains variable, if any
  var _SecondLevelDomains = root && root.SecondLevelDomains;

  var SLD = {
    // list of known Second Level Domains
    // converted list of SLDs from https://github.com/gavingmiller/second-level-domains
    // ----
    // publicsuffix.org is more current and actually used by a couple of browsers internally.
    // downside is it also contains domains like "dyndns.org" - which is fine for the security
    // issues browser have to deal with (SOP for cookies, etc) - but is way overboard for URI.js
    // ----
    list: {
      'ac':' com gov mil net org ',
      'ae':' ac co gov mil name net org pro sch ',
      'af':' com edu gov net org ',
      'al':' com edu gov mil net org ',
      'ao':' co ed gv it og pb ',
      'ar':' com edu gob gov int mil net org tur ',
      'at':' ac co gv or ',
      'au':' asn com csiro edu gov id net org ',
      'ba':' co com edu gov mil net org rs unbi unmo unsa untz unze ',
      'bb':' biz co com edu gov info net org store tv ',
      'bh':' biz cc com edu gov info net org ',
      'bn':' com edu gov net org ',
      'bo':' com edu gob gov int mil net org tv ',
      'br':' adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ',
      'bs':' com edu gov net org ',
      'bz':' du et om ov rg ',
      'ca':' ab bc mb nb nf nl ns nt nu on pe qc sk yk ',
      'ck':' biz co edu gen gov info net org ',
      'cn':' ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ',
      'co':' com edu gov mil net nom org ',
      'cr':' ac c co ed fi go or sa ',
      'cy':' ac biz com ekloges gov ltd name net org parliament press pro tm ',
      'do':' art com edu gob gov mil net org sld web ',
      'dz':' art asso com edu gov net org pol ',
      'ec':' com edu fin gov info med mil net org pro ',
      'eg':' com edu eun gov mil name net org sci ',
      'er':' com edu gov ind mil net org rochest w ',
      'es':' com edu gob nom org ',
      'et':' biz com edu gov info name net org ',
      'fj':' ac biz com info mil name net org pro ',
      'fk':' ac co gov net nom org ',
      'fr':' asso com f gouv nom prd presse tm ',
      'gg':' co net org ',
      'gh':' com edu gov mil org ',
      'gn':' ac com gov net org ',
      'gr':' com edu gov mil net org ',
      'gt':' com edu gob ind mil net org ',
      'gu':' com edu gov net org ',
      'hk':' com edu gov idv net org ',
      'hu':' 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ',
      'id':' ac co go mil net or sch web ',
      'il':' ac co gov idf k12 muni net org ',
      'in':' ac co edu ernet firm gen gov i ind mil net nic org res ',
      'iq':' com edu gov i mil net org ',
      'ir':' ac co dnssec gov i id net org sch ',
      'it':' edu gov ',
      'je':' co net org ',
      'jo':' com edu gov mil name net org sch ',
      'jp':' ac ad co ed go gr lg ne or ',
      'ke':' ac co go info me mobi ne or sc ',
      'kh':' com edu gov mil net org per ',
      'ki':' biz com de edu gov info mob net org tel ',
      'km':' asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ',
      'kn':' edu gov net org ',
      'kr':' ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ',
      'kw':' com edu gov net org ',
      'ky':' com edu gov net org ',
      'kz':' com edu gov mil net org ',
      'lb':' com edu gov net org ',
      'lk':' assn com edu gov grp hotel int ltd net ngo org sch soc web ',
      'lr':' com edu gov net org ',
      'lv':' asn com conf edu gov id mil net org ',
      'ly':' com edu gov id med net org plc sch ',
      'ma':' ac co gov m net org press ',
      'mc':' asso tm ',
      'me':' ac co edu gov its net org priv ',
      'mg':' com edu gov mil nom org prd tm ',
      'mk':' com edu gov inf name net org pro ',
      'ml':' com edu gov net org presse ',
      'mn':' edu gov org ',
      'mo':' com edu gov net org ',
      'mt':' com edu gov net org ',
      'mv':' aero biz com coop edu gov info int mil museum name net org pro ',
      'mw':' ac co com coop edu gov int museum net org ',
      'mx':' com edu gob net org ',
      'my':' com edu gov mil name net org sch ',
      'nf':' arts com firm info net other per rec store web ',
      'ng':' biz com edu gov mil mobi name net org sch ',
      'ni':' ac co com edu gob mil net nom org ',
      'np':' com edu gov mil net org ',
      'nr':' biz com edu gov info net org ',
      'om':' ac biz co com edu gov med mil museum net org pro sch ',
      'pe':' com edu gob mil net nom org sld ',
      'ph':' com edu gov i mil net ngo org ',
      'pk':' biz com edu fam gob gok gon gop gos gov net org web ',
      'pl':' art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ',
      'pr':' ac biz com edu est gov info isla name net org pro prof ',
      'ps':' com edu gov net org plo sec ',
      'pw':' belau co ed go ne or ',
      'ro':' arts com firm info nom nt org rec store tm www ',
      'rs':' ac co edu gov in org ',
      'sb':' com edu gov net org ',
      'sc':' com edu gov net org ',
      'sh':' co com edu gov net nom org ',
      'sl':' com edu gov net org ',
      'st':' co com consulado edu embaixada gov mil net org principe saotome store ',
      'sv':' com edu gob org red ',
      'sz':' ac co org ',
      'tr':' av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ',
      'tt':' aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ',
      'tw':' club com ebiz edu game gov idv mil net org ',
      'mu':' ac co com gov net or org ',
      'mz':' ac co edu gov org ',
      'na':' co com ',
      'nz':' ac co cri geek gen govt health iwi maori mil net org parliament school ',
      'pa':' abo ac com edu gob ing med net nom org sld ',
      'pt':' com edu gov int net nome org publ ',
      'py':' com edu gov mil net org ',
      'qa':' com edu gov mil net org ',
      're':' asso com nom ',
      'ru':' ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ',
      'rw':' ac co com edu gouv gov int mil net ',
      'sa':' com edu gov med net org pub sch ',
      'sd':' com edu gov info med net org tv ',
      'se':' a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ',
      'sg':' com edu gov idn net org per ',
      'sn':' art com edu gouv org perso univ ',
      'sy':' com edu gov mil net news org ',
      'th':' ac co go in mi net or ',
      'tj':' ac biz co com edu go gov info int mil name net nic org test web ',
      'tn':' agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ',
      'tz':' ac co go ne or ',
      'ua':' biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ',
      'ug':' ac co go ne or org sc ',
      'uk':' ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ',
      'us':' dni fed isa kids nsn ',
      'uy':' com edu gub mil net org ',
      've':' co com edu gob info mil net org web ',
      'vi':' co com k12 net org ',
      'vn':' ac biz com edu gov health info int name net org pro ',
      'ye':' co com gov ltd me net org plc ',
      'yu':' ac co edu gov org ',
      'za':' ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ',
      'zm':' ac co com edu gov net org sch ',
      // https://en.wikipedia.org/wiki/CentralNic#Second-level_domains
      'com': 'ar br cn de eu gb gr hu jpn kr no qc ru sa se uk us uy za ',
      'net': 'gb jp se uk ',
      'org': 'ae',
      'de': 'com '
    },
    // gorhill 2013-10-25: Using indexOf() instead Regexp(). Significant boost
    // in both performance and memory footprint. No initialization required.
    // http://jsperf.com/uri-js-sld-regex-vs-binary-search/4
    // Following methods use lastIndexOf() rather than array.split() in order
    // to avoid any memory allocations.
    has: function(domain) {
      var tldOffset = domain.lastIndexOf('.');
      if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
        return false;
      }
      var sldOffset = domain.lastIndexOf('.', tldOffset-1);
      if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
        return false;
      }
      var sldList = SLD.list[domain.slice(tldOffset+1)];
      if (!sldList) {
        return false;
      }
      return sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') >= 0;
    },
    is: function(domain) {
      var tldOffset = domain.lastIndexOf('.');
      if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
        return false;
      }
      var sldOffset = domain.lastIndexOf('.', tldOffset-1);
      if (sldOffset >= 0) {
        return false;
      }
      var sldList = SLD.list[domain.slice(tldOffset+1)];
      if (!sldList) {
        return false;
      }
      return sldList.indexOf(' ' + domain.slice(0, tldOffset) + ' ') >= 0;
    },
    get: function(domain) {
      var tldOffset = domain.lastIndexOf('.');
      if (tldOffset <= 0 || tldOffset >= (domain.length-1)) {
        return null;
      }
      var sldOffset = domain.lastIndexOf('.', tldOffset-1);
      if (sldOffset <= 0 || sldOffset >= (tldOffset-1)) {
        return null;
      }
      var sldList = SLD.list[domain.slice(tldOffset+1)];
      if (!sldList) {
        return null;
      }
      if (sldList.indexOf(' ' + domain.slice(sldOffset+1, tldOffset) + ' ') < 0) {
        return null;
      }
      return domain.slice(sldOffset+1);
    },
    noConflict: function(){
      if (root.SecondLevelDomains === this) {
        root.SecondLevelDomains = _SecondLevelDomains;
      }
      return this;
    }
  };

  return SLD;
}));


/***/ }),
/* 14 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};


/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);
var settle = __webpack_require__(39);
var buildURL = __webpack_require__(41);
var parseHeaders = __webpack_require__(42);
var isURLSameOrigin = __webpack_require__(43);
var createError = __webpack_require__(16);
var btoa = (typeof window !== 'undefined' && window.btoa && window.btoa.bind(window)) || __webpack_require__(44);

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();
    var loadEvent = 'onreadystatechange';
    var xDomain = false;

    // For IE 8/9 CORS support
    // Only supports POST and GET calls and doesn't returns the response headers.
    // DON'T do this for testing b/c XMLHttpRequest is mocked, not XDomainRequest.
    if ("production" !== 'test' &&
        typeof window !== 'undefined' &&
        window.XDomainRequest && !('withCredentials' in request) &&
        !isURLSameOrigin(config.url)) {
      request = new window.XDomainRequest();
      loadEvent = 'onload';
      xDomain = true;
      request.onprogress = function handleProgress() {};
      request.ontimeout = function handleTimeout() {};
    }

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password || '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    request[loadEvent] = function handleLoad() {
      if (!request || (request.readyState !== 4 && !xDomain)) {
        return;
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        // IE sends 1223 instead of 204 (https://github.com/mzabriskie/axios/issues/201)
        status: request.status === 1223 ? 204 : request.status,
        statusText: request.status === 1223 ? 'No Content' : request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };

    // Handle low level network errors
    request.onerror = function handleError() {
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
      reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };

    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    if (utils.isStandardBrowserEnv()) {
      var cookies = __webpack_require__(45);

      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
          cookies.read(config.xsrfCookieName) :
          undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);
        }
      });
    }

    // Add withCredentials to request if needed
    if (config.withCredentials) {
      request.withCredentials = true;
    }

    // Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (requestData === undefined) {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  });
};


/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var enhanceError = __webpack_require__(40);

/**
 * Create an Error with the specified message, config, error code, request and response.
 *
 * @param {string} message The error message.
 * @param {Object} config The config.
 * @param {string} [code] The error code (for example, 'ECONNABORTED').
 * @param {Object} [request] The request.
 * @param {Object} [response] The response.
 * @returns {Error} The created error.
 */
module.exports = function createError(message, config, code, request, response) {
  var error = new Error(message);
  return enhanceError(error, config, code, request, response);
};


/***/ }),
/* 17 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};


/***/ }),
/* 18 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


/**
 * A `Cancel` is an object that is thrown when an operation is canceled.
 *
 * @class
 * @param {string=} message The message.
 */
function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;


/***/ }),
/* 19 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2018 GNUnet e.V. and INRIA

 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/>
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Types for records stored in the wallet's database.
 *
 * Types for the objects in the database should end in "-Record".
 */
/**
 * Imports.
 */
const amounts_1 = __webpack_require__(1);
const checkable_1 = __webpack_require__(3);
const talerTypes_1 = __webpack_require__(7);
const query_1 = __webpack_require__(2);
/**
 * Current database version, should be incremented
 * each time we do incompatible schema changes on the database.
 * In the future we might consider adding migration functions for
 * each version increment.
 */
exports.WALLET_DB_VERSION = 26;
/**
 * Status of a denomination.
 */
var DenominationStatus;
(function (DenominationStatus) {
    /**
     * Verification was delayed.
     */
    DenominationStatus[DenominationStatus["Unverified"] = 0] = "Unverified";
    /**
     * Verified as valid.
     */
    DenominationStatus[DenominationStatus["VerifiedGood"] = 1] = "VerifiedGood";
    /**
     * Verified as invalid.
     */
    DenominationStatus[DenominationStatus["VerifiedBad"] = 2] = "VerifiedBad";
})(DenominationStatus = exports.DenominationStatus || (exports.DenominationStatus = {}));
/**
 * Denomination record as stored in the wallet's database.
 */
let DenominationRecord = class DenominationRecord {
};
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], DenominationRecord.prototype, "value", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "denomPub", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "denomPubHash", void 0);
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], DenominationRecord.prototype, "feeWithdraw", void 0);
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], DenominationRecord.prototype, "feeDeposit", void 0);
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], DenominationRecord.prototype, "feeRefresh", void 0);
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], DenominationRecord.prototype, "feeRefund", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "stampStart", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "stampExpireWithdraw", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "stampExpireLegal", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "stampExpireDeposit", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "masterSig", void 0);
__decorate([
    checkable_1.Checkable.Number()
], DenominationRecord.prototype, "status", void 0);
__decorate([
    checkable_1.Checkable.Boolean()
], DenominationRecord.prototype, "isOffered", void 0);
__decorate([
    checkable_1.Checkable.String()
], DenominationRecord.prototype, "exchangeBaseUrl", void 0);
DenominationRecord = __decorate([
    checkable_1.Checkable.Class()
], DenominationRecord);
exports.DenominationRecord = DenominationRecord;
/**
 * Status of a coin.
 */
var CoinStatus;
(function (CoinStatus) {
    /**
     * Withdrawn and never shown to anybody.
     */
    CoinStatus[CoinStatus["Fresh"] = 0] = "Fresh";
    /**
     * Currently planned to be sent to a merchant for a purchase.
     */
    CoinStatus[CoinStatus["PurchasePending"] = 1] = "PurchasePending";
    /**
     * Used for a completed transaction and now dirty.
     */
    CoinStatus[CoinStatus["Dirty"] = 2] = "Dirty";
    /**
     * A coin that was refreshed.
     */
    CoinStatus[CoinStatus["Refreshed"] = 3] = "Refreshed";
    /**
     * Coin marked to be paid back, but payback not finished.
     */
    CoinStatus[CoinStatus["PaybackPending"] = 4] = "PaybackPending";
    /**
     * Coin fully paid back.
     */
    CoinStatus[CoinStatus["PaybackDone"] = 5] = "PaybackDone";
    /**
     * Coin was dirty but can't be refreshed.
     */
    CoinStatus[CoinStatus["Useless"] = 6] = "Useless";
    /**
     * The coin was withdrawn for a tip that the user hasn't accepted yet.
     */
    CoinStatus[CoinStatus["TainedByTip"] = 7] = "TainedByTip";
})(CoinStatus = exports.CoinStatus || (exports.CoinStatus = {}));
/**
 * Proposal record, stored in the wallet's database.
 */
let ProposalDownloadRecord = class ProposalDownloadRecord {
};
__decorate([
    checkable_1.Checkable.String()
], ProposalDownloadRecord.prototype, "url", void 0);
__decorate([
    checkable_1.Checkable.Value(() => talerTypes_1.ContractTerms)
], ProposalDownloadRecord.prototype, "contractTerms", void 0);
__decorate([
    checkable_1.Checkable.String()
], ProposalDownloadRecord.prototype, "merchantSig", void 0);
__decorate([
    checkable_1.Checkable.String()
], ProposalDownloadRecord.prototype, "contractTermsHash", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.Number())
], ProposalDownloadRecord.prototype, "id", void 0);
__decorate([
    checkable_1.Checkable.Number()
], ProposalDownloadRecord.prototype, "timestamp", void 0);
__decorate([
    checkable_1.Checkable.String()
], ProposalDownloadRecord.prototype, "noncePriv", void 0);
ProposalDownloadRecord = __decorate([
    checkable_1.Checkable.Class()
], ProposalDownloadRecord);
exports.ProposalDownloadRecord = ProposalDownloadRecord;
/* tslint:disable:completed-docs */
/**
 * The stores and indices for the wallet database.
 */
var Stores;
(function (Stores) {
    class ExchangeStore extends query_1.Store {
        constructor() {
            super("exchanges", { keyPath: "baseUrl" });
            this.pubKeyIndex = new query_1.Index(this, "pubKeyIndex", "masterPublicKey");
        }
    }
    class CoinsStore extends query_1.Store {
        constructor() {
            super("coins", { keyPath: "coinPub" });
            this.exchangeBaseUrlIndex = new query_1.Index(this, "exchangeBaseUrl", "exchangeBaseUrl");
            this.denomPubIndex = new query_1.Index(this, "denomPubIndex", "denomPub");
        }
    }
    class ProposalsStore extends query_1.Store {
        constructor() {
            super("proposals", {
                autoIncrement: true,
                keyPath: "id",
            });
            this.urlIndex = new query_1.Index(this, "urlIndex", "url");
            this.timestampIndex = new query_1.Index(this, "timestampIndex", "timestamp");
        }
    }
    class PurchasesStore extends query_1.Store {
        constructor() {
            super("purchases", { keyPath: "contractTermsHash" });
            this.fulfillmentUrlIndex = new query_1.Index(this, "fulfillmentUrlIndex", "contractTerms.fulfillment_url");
            this.orderIdIndex = new query_1.Index(this, "orderIdIndex", "contractTerms.order_id");
            this.timestampIndex = new query_1.Index(this, "timestampIndex", "timestamp");
        }
    }
    class DenominationsStore extends query_1.Store {
        constructor() {
            // cast needed because of bug in type annotations
            super("denominations", { keyPath: ["exchangeBaseUrl", "denomPub"] });
            this.denomPubHashIndex = new query_1.Index(this, "denomPubHashIndex", "denomPubHash");
            this.exchangeBaseUrlIndex = new query_1.Index(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
            this.denomPubIndex = new query_1.Index(this, "denomPubIndex", "denomPub");
        }
    }
    class CurrenciesStore extends query_1.Store {
        constructor() {
            super("currencies", { keyPath: "name" });
        }
    }
    class ConfigStore extends query_1.Store {
        constructor() {
            super("config", { keyPath: "key" });
        }
    }
    class ExchangeWireFeesStore extends query_1.Store {
        constructor() {
            super("exchangeWireFees", { keyPath: "exchangeBaseUrl" });
        }
    }
    class ReservesStore extends query_1.Store {
        constructor() {
            super("reserves", { keyPath: "reserve_pub" });
            this.timestampCreatedIndex = new query_1.Index(this, "timestampCreatedIndex", "created");
            this.timestampConfirmedIndex = new query_1.Index(this, "timestampConfirmedIndex", "timestamp_confirmed");
            this.timestampDepletedIndex = new query_1.Index(this, "timestampDepletedIndex", "timestamp_depleted");
        }
    }
    class TipsStore extends query_1.Store {
        constructor() {
            super("tips", { keyPath: ["tipId", "merchantDomain"] });
            this.coinPubIndex = new query_1.Index(this, "coinPubIndex", "coinPubs", { multiEntry: true });
        }
    }
    class SenderWiresStore extends query_1.Store {
        constructor() {
            super("senderWires", { keyPath: "id" });
        }
    }
    Stores.coins = new CoinsStore();
    Stores.coinsReturns = new query_1.Store("coinsReturns", { keyPath: "contractTermsHash" });
    Stores.config = new ConfigStore();
    Stores.currencies = new CurrenciesStore();
    Stores.denominations = new DenominationsStore();
    Stores.exchangeWireFees = new ExchangeWireFeesStore();
    Stores.exchanges = new ExchangeStore();
    Stores.precoins = new query_1.Store("precoins", { keyPath: "coinPub" });
    Stores.proposals = new ProposalsStore();
    Stores.refresh = new query_1.Store("refresh", { keyPath: "id", autoIncrement: true });
    Stores.reserves = new ReservesStore();
    Stores.purchases = new PurchasesStore();
    Stores.tips = new TipsStore();
    Stores.senderWires = new SenderWiresStore();
})(Stores = exports.Stores || (exports.Stores = {}));
/* tslint:enable:completed-docs */


/***/ }),
/* 20 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2017 INRIA

 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/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Compatibility helpers needed for browsers that don't implement
* WebExtension APIs consistently.
*/
function isFirefox() {
    const rt = chrome.runtime;
    if (typeof rt.getBrowserInfo === "function") {
        return true;
    }
    return false;
}
exports.isFirefox = isFirefox;


/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Entry point for the background page.
 *
 * @author Florian Dold
 */
/**
 * Imports.
 */
const wxBackend_1 = __webpack_require__(22);
window.addEventListener("load", () => {
    wxBackend_1.wxMain();
});


/***/ }),
/* 22 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Messaging for the WebExtensions wallet.  Should contain
 * parts that are specific for WebExtensions, but as little business
 * logic as possible.
 */
/**
 * Imports.
 */
const http_1 = __webpack_require__(8);
const logging = __webpack_require__(23);
const query_1 = __webpack_require__(2);
const amounts_1 = __webpack_require__(1);
const walletTypes_1 = __webpack_require__(24);
const wallet_1 = __webpack_require__(25);
const compat_1 = __webpack_require__(20);
const dbTypes_1 = __webpack_require__(19);
const chromeBadge_1 = __webpack_require__(53);
const URI = __webpack_require__(5);
const talerTypes_1 = __webpack_require__(7);
const DB_NAME = "taler";
const NeedsWallet = Symbol("NeedsWallet");
function handleMessage(sender, type, detail) {
    function assertNotFound(t) {
        console.error(`Request type ${t} unknown`);
        console.error(`Request detail was ${detail}`);
        return { error: "request unknown", requestType: type };
    }
    function needsWallet() {
        if (!currentWallet) {
            throw NeedsWallet;
        }
        return currentWallet;
    }
    switch (type) {
        case "balances": {
            return needsWallet().getBalances();
        }
        case "dump-db": {
            const db = needsWallet().db;
            return exportDb(db);
        }
        case "import-db": {
            const db = needsWallet().db;
            return importDb(db, detail.dump);
        }
        case "ping": {
            return Promise.resolve();
        }
        case "reset-db": {
            if (currentWallet) {
                const db = currentWallet.db;
                const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
                // tslint:disable-next-line:prefer-for-of
                for (let i = 0; i < db.objectStoreNames.length; i++) {
                    tx.objectStore(db.objectStoreNames[i]).clear();
                }
            }
            deleteDb();
            setBadgeText({ text: "" });
            console.log("reset done");
            if (!currentWallet) {
                reinitWallet();
            }
            return Promise.resolve({});
        }
        case "create-reserve": {
            const d = {
                amount: detail.amount,
                exchange: detail.exchange,
                senderWire: detail.senderWire,
            };
            const req = walletTypes_1.CreateReserveRequest.checked(d);
            return needsWallet().createReserve(req);
        }
        case "confirm-reserve": {
            const d = {
                reservePub: detail.reservePub,
            };
            const req = walletTypes_1.ConfirmReserveRequest.checked(d);
            return needsWallet().confirmReserve(req);
        }
        case "confirm-pay": {
            if (typeof detail.proposalId !== "number") {
                throw Error("proposalId must be number");
            }
            return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
        }
        case "submit-pay": {
            if (typeof detail.contractTermsHash !== "string") {
                throw Error("contractTermsHash must be a string");
            }
            return needsWallet().submitPay(detail.contractTermsHash, detail.sessionId);
        }
        case "check-pay": {
            if (typeof detail.proposalId !== "number") {
                throw Error("proposalId must be number");
            }
            return needsWallet().checkPay(detail.proposalId);
        }
        case "query-payment": {
            if (sender.tab && sender.tab.id) {
                rateLimitCache[sender.tab.id]++;
                if (rateLimitCache[sender.tab.id] > 10) {
                    console.warn("rate limit for query-payment exceeded");
                    const msg = {
                        error: "rate limit exceeded for query-payment",
                        hint: "Check for redirect loops",
                        rateLimitExceeded: true,
                    };
                    return Promise.resolve(msg);
                }
            }
            return needsWallet().queryPaymentByFulfillmentUrl(detail.url);
        }
        case "exchange-info": {
            if (!detail.baseUrl) {
                return Promise.resolve({ error: "bad url" });
            }
            return needsWallet().updateExchangeFromUrl(detail.baseUrl);
        }
        case "currency-info": {
            if (!detail.name) {
                return Promise.resolve({ error: "name missing" });
            }
            return needsWallet().getCurrencyRecord(detail.name);
        }
        case "hash-contract": {
            if (!detail.contract) {
                return Promise.resolve({ error: "contract missing" });
            }
            return needsWallet().hashContract(detail.contract).then((hash) => {
                return hash;
            });
        }
        case "reserve-creation-info": {
            if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
                return Promise.resolve({ error: "bad url" });
            }
            const amount = amounts_1.AmountJson.checked(detail.amount);
            return needsWallet().getReserveCreationInfo(detail.baseUrl, amount);
        }
        case "get-history": {
            // TODO: limit history length
            return needsWallet().getHistory();
        }
        case "get-proposal": {
            return needsWallet().getProposal(detail.proposalId);
        }
        case "get-exchanges": {
            return needsWallet().getExchanges();
        }
        case "get-currencies": {
            return needsWallet().getCurrencies();
        }
        case "update-currency": {
            return needsWallet().updateCurrency(detail.currencyRecord);
        }
        case "get-reserves": {
            if (typeof detail.exchangeBaseUrl !== "string") {
                return Promise.reject(Error("exchangeBaseUrl missing"));
            }
            return needsWallet().getReserves(detail.exchangeBaseUrl);
        }
        case "get-payback-reserves": {
            return needsWallet().getPaybackReserves();
        }
        case "withdraw-payback-reserve": {
            if (typeof detail.reservePub !== "string") {
                return Promise.reject(Error("reservePub missing"));
            }
            return needsWallet().withdrawPaybackReserve(detail.reservePub);
        }
        case "get-coins": {
            if (typeof detail.exchangeBaseUrl !== "string") {
                return Promise.reject(Error("exchangBaseUrl missing"));
            }
            return needsWallet().getCoins(detail.exchangeBaseUrl);
        }
        case "get-precoins": {
            if (typeof detail.exchangeBaseUrl !== "string") {
                return Promise.reject(Error("exchangBaseUrl missing"));
            }
            return needsWallet().getPreCoins(detail.exchangeBaseUrl);
        }
        case "get-denoms": {
            if (typeof detail.exchangeBaseUrl !== "string") {
                return Promise.reject(Error("exchangBaseUrl missing"));
            }
            return needsWallet().getDenoms(detail.exchangeBaseUrl);
        }
        case "refresh-coin": {
            if (typeof detail.coinPub !== "string") {
                return Promise.reject(Error("coinPub missing"));
            }
            return needsWallet().refresh(detail.coinPub);
        }
        case "payback-coin": {
            if (typeof detail.coinPub !== "string") {
                return Promise.reject(Error("coinPub missing"));
            }
            return needsWallet().payback(detail.coinPub);
        }
        case "get-sender-wire-infos": {
            return needsWallet().getSenderWireInfos();
        }
        case "return-coins": {
            const d = {
                amount: detail.amount,
                exchange: detail.exchange,
                senderWire: detail.senderWire,
            };
            const req = walletTypes_1.ReturnCoinsRequest.checked(d);
            return needsWallet().returnCoins(req);
        }
        case "check-upgrade": {
            let dbResetRequired = false;
            if (!currentWallet) {
                dbResetRequired = true;
            }
            const resp = {
                currentDbVersion: dbTypes_1.WALLET_DB_VERSION.toString(),
                dbResetRequired,
                oldDbVersion: (oldDbVersion || "unknown").toString(),
            };
            return resp;
        }
        case "log-and-display-error":
            logging.storeReport(detail).then((reportUid) => {
                const url = chrome.extension.getURL(`/src/webex/pages/error.html?reportUid=${reportUid}`);
                if (detail.sameTab && sender && sender.tab && sender.tab.id) {
                    chrome.tabs.update(detail.tabId, { url });
                }
                else {
                    chrome.tabs.create({ url });
                }
            });
            return;
        case "get-report":
            return logging.getReport(detail.reportUid);
        case "get-purchase": {
            const contractTermsHash = detail.contractTermsHash;
            if (!contractTermsHash) {
                throw Error("contractTermsHash missing");
            }
            return needsWallet().getPurchase(contractTermsHash);
        }
        case "get-full-refund-fees":
            return needsWallet().getFullRefundFees(detail.refundPermissions);
        case "accept-refund":
            return needsWallet().acceptRefund(detail.refundUrl);
        case "get-tip-status": {
            const tipToken = talerTypes_1.TipToken.checked(detail.tipToken);
            return needsWallet().getTipStatus(tipToken);
        }
        case "accept-tip": {
            const tipToken = talerTypes_1.TipToken.checked(detail.tipToken);
            return needsWallet().acceptTip(tipToken);
        }
        case "clear-notification": {
            return needsWallet().clearNotification();
        }
        case "download-proposal": {
            return needsWallet().downloadProposal(detail.url);
        }
        case "abort-failed-payment": {
            if (!detail.contractTermsHash) {
                throw Error("contracTermsHash not given");
            }
            return needsWallet().abortFailedPayment(detail.contractTermsHash);
        }
        case "taler-pay": {
            const senderUrl = sender.url;
            if (!senderUrl) {
                console.log("can't trigger payment, no sender URL");
                return;
            }
            const tab = sender.tab;
            if (!tab) {
                console.log("can't trigger payment, no sender tab");
                return;
            }
            const tabId = tab.id;
            if (typeof tabId !== "string") {
                console.log("can't trigger payment, no sender tab id");
                return;
            }
            talerPay(detail, senderUrl, tabId);
            return;
        }
        default:
            // Exhaustiveness check.
            // See https://www.typescriptlang.org/docs/handbook/advanced-types.html
            return assertNotFound(type);
    }
}
function dispatch(req, sender, sendResponse) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const p = handleMessage(sender, req.type, req.detail);
            const r = yield p;
            try {
                sendResponse(r);
            }
            catch (e) {
                // might fail if tab disconnected
            }
        }
        catch (e) {
            console.log(`exception during wallet handler for '${req.type}'`);
            console.log("request", req);
            console.error(e);
            let stack;
            try {
                stack = e.stack.toString();
            }
            catch (e) {
                // might fail
            }
            try {
                sendResponse({
                    error: "exception",
                    message: e.message,
                    stack,
                });
            }
            catch (e) {
                console.log(e);
                // might fail if tab disconnected
            }
        }
    });
}
class ChromeNotifier {
    constructor() {
        this.ports = [];
        chrome.runtime.onConnect.addListener((port) => {
            console.log("got connect!");
            this.ports.push(port);
            port.onDisconnect.addListener(() => {
                const i = this.ports.indexOf(port);
                if (i >= 0) {
                    this.ports.splice(i, 1);
                }
                else {
                    console.error("port already removed");
                }
            });
        });
    }
    notify() {
        for (const p of this.ports) {
            p.postMessage({ notify: true });
        }
    }
}
function talerPay(fields, url, tabId) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!currentWallet) {
            console.log("can't handle payment, no wallet");
            return undefined;
        }
        const w = currentWallet;
        const goToPayment = (p) => {
            const nextUrl = new URI(p.contractTerms.fulfillment_url);
            nextUrl.addSearch("order_id", p.contractTerms.order_id);
            if (p.lastSessionSig) {
                nextUrl.addSearch("session_sig", p.lastSessionSig);
            }
            return nextUrl.href();
        };
        if (fields.resource_url) {
            const p = yield w.queryPaymentByFulfillmentUrl(fields.resource_url);
            console.log("query for resource url", fields.resource_url, "result", p);
            if (p && (fields.session_id === undefined || fields.session_id === p.lastSessionId)) {
                return goToPayment(p);
            }
        }
        if (fields.contract_url) {
            const proposalId = yield w.downloadProposal(fields.contract_url);
            const uri = new URI(chrome.extension.getURL("/src/webex/pages/confirm-contract.html"));
            if (fields.session_id) {
                uri.addSearch("sessionId", fields.session_id);
            }
            uri.addSearch("proposalId", proposalId);
            const redirectUrl = uri.href();
            return redirectUrl;
        }
        if (fields.offer_url) {
            return fields.offer_url;
        }
        if (fields.refund_url) {
            console.log("processing refund");
            const uri = new URI(chrome.extension.getURL("/src/webex/pages/refund.html"));
            return uri.query({ refundUrl: fields.refund_url }).href();
        }
        if (fields.tip) {
            const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
            return uri.query({ tip_token: fields.tip }).href();
        }
        return undefined;
    });
}
function getTab(tabId) {
    return new Promise((resolve, reject) => {
        chrome.tabs.get(tabId, (tab) => resolve(tab));
    });
}
function setBadgeText(options) {
    // not supported by all browsers ...
    if (chrome && chrome.browserAction && chrome.browserAction.setBadgeText) {
        chrome.browserAction.setBadgeText(options);
    }
    else {
        console.warn("can't set badge text, not supported", options);
    }
}
function waitMs(timeoutMs) {
    return new Promise((resolve, reject) => {
        chrome.extension.getBackgroundPage().setTimeout(() => resolve(), timeoutMs);
    });
}
function makeSyncWalletRedirect(url, tabId, oldUrl, params) {
    const innerUrl = new URI(chrome.extension.getURL("/src/webex/pages/" + url));
    if (params) {
        for (const key in params) {
            if (params[key]) {
                innerUrl.addSearch(key, params[key]);
            }
        }
    }
    const outerUrl = new URI(chrome.extension.getURL("/src/webex/pages/redirect.html"));
    outerUrl.addSearch("url", innerUrl);
    if (compat_1.isFirefox()) {
        // Some platforms don't support the sync redirect (yet), so fall back to
        // async redirect after a timeout.
        const doit = () => __awaiter(this, void 0, void 0, function* () {
            yield waitMs(150);
            const tab = yield getTab(tabId);
            if (tab.url === oldUrl) {
                chrome.tabs.update(tabId, { url: outerUrl.href() });
            }
        });
        doit();
    }
    return { redirectUrl: outerUrl.href() };
}
/**
 * Handle a HTTP response that has the "402 Payment Required" status.
 * In this callback we don't have access to the body, and must communicate via
 * shared state with the content script that will later be run later
 * in this tab.
 */
function handleHttpPayment(headerList, url, tabId) {
    if (!currentWallet) {
        console.log("can't handle payment, no wallet");
        return;
    }
    const headers = {};
    for (const kv of headerList) {
        if (kv.value) {
            headers[kv.name.toLowerCase()] = kv.value;
        }
    }
    const fields = {
        contract_url: headers["x-taler-contract-url"],
        offer_url: headers["x-taler-offer-url"],
        refund_url: headers["x-taler-refund-url"],
        resource_url: headers["x-taler-resource-url"],
        session_id: headers["x-taler-session-id"],
        tip: headers["x-taler-tip"],
    };
    const talerHeaderFound = Object.keys(fields).filter((x) => fields[x]).length !== 0;
    if (!talerHeaderFound) {
        // 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");
        return;
    }
    console.log("got pay detail", fields);
    // Synchronous fast path for existing payment
    if (fields.resource_url) {
        const result = currentWallet.getNextUrlFromResourceUrl(fields.resource_url);
        if (result && (fields.session_id === undefined || fields.session_id === result.lastSessionId)) {
            return { redirectUrl: result.nextUrl };
        }
    }
    // Synchronous fast path for new contract
    if (fields.contract_url) {
        return makeSyncWalletRedirect("confirm-contract.html", tabId, url, {
            contractUrl: fields.contract_url,
            sessionId: fields.session_id,
            resourceUrl: fields.resource_url,
        });
    }
    // Synchronous fast path for tip
    if (fields.tip) {
        return makeSyncWalletRedirect("tip.html", tabId, url, { tip_token: fields.tip });
    }
    // Synchronous fast path for refund
    if (fields.refund_url) {
        console.log("processing refund");
        return makeSyncWalletRedirect("refund.html", tabId, url, { refundUrl: fields.refund_url });
    }
    // We need to do some asynchronous operation, we can't directly redirect
    talerPay(fields, url, tabId).then((nextUrl) => {
        if (nextUrl) {
            // We use chrome.tabs.executeScript instead of chrome.tabs.update
            // because the latter is buggy when it does not execute in the same
            // (micro-?)task as the header callback.
            chrome.tabs.executeScript({
                code: `document.location.href = decodeURIComponent("${encodeURI(nextUrl)}");`,
                runAt: "document_start",
            });
        }
    });
    return;
}
function handleBankRequest(wallet, headerList, url, tabId) {
    const headers = {};
    for (const kv of headerList) {
        if (kv.value) {
            headers[kv.name.toLowerCase()] = kv.value;
        }
    }
    const operation = headers["x-taler-operation"];
    if (!operation) {
        // Not a taler related request.
        return;
    }
    if (operation === "confirm-reserve") {
        const reservePub = headers["x-taler-reserve-pub"];
        if (reservePub !== undefined) {
            console.log(`confirming reserve ${reservePub} via 201`);
            wallet.confirmReserve({ reservePub });
        }
        else {
            console.warn("got 'X-Taler-Operation: confirm-reserve' without 'X-Taler-Reserve-Pub'");
        }
        return;
    }
    if (operation === "create-reserve") {
        const amount = headers["x-taler-amount"];
        if (!amount) {
            console.log("202 not understood (X-Taler-Amount missing)");
            return;
        }
        const callbackUrl = headers["x-taler-callback-url"];
        if (!callbackUrl) {
            console.log("202 not understood (X-Taler-Callback-Url missing)");
            return;
        }
        try {
            JSON.parse(amount);
        }
        catch (e) {
            const errUri = new URI(chrome.extension.getURL("/src/webex/pages/error.html"));
            const p = {
                message: `Can't parse amount ("${amount}"): ${e.message}`,
            };
            const errRedirectUrl = errUri.query(p).href();
            // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
            chrome.tabs.update(tabId, { url: errRedirectUrl });
            return;
        }
        const wtTypes = headers["x-taler-wt-types"];
        if (!wtTypes) {
            console.log("202 not understood (X-Taler-Wt-Types missing)");
            return;
        }
        const params = {
            amount,
            bank_url: url,
            callback_url: new URI(callbackUrl).absoluteTo(url),
            sender_wire: headers["x-taler-sender-wire"],
            suggested_exchange_url: headers["x-taler-suggested-exchange"],
            wt_types: wtTypes,
        };
        const uri = new URI(chrome.extension.getURL("/src/webex/pages/confirm-create-reserve.html"));
        const redirectUrl = uri.query(params).href();
        console.log("redirecting to", redirectUrl);
        // FIXME: use direct redirect when https://bugzilla.mozilla.org/show_bug.cgi?id=707624 is fixed
        chrome.tabs.update(tabId, { url: redirectUrl });
        return;
    }
    console.log("Ignoring unknown X-Taler-Operation:", operation);
}
// Rate limit cache for executePayment operations, to break redirect loops
let rateLimitCache = {};
function clearRateLimitCache() {
    rateLimitCache = {};
}
/**
 * Currently active wallet instance.  Might be unloaded and
 * re-instantiated when the database is reset.
 */
let currentWallet;
/**
 * Last version if an outdated DB, if applicable.
 */
let oldDbVersion;
function reinitWallet() {
    return __awaiter(this, void 0, void 0, function* () {
        if (currentWallet) {
            currentWallet.stop();
            currentWallet = undefined;
        }
        setBadgeText({ text: "" });
        const badge = new chromeBadge_1.ChromeBadge();
        let db;
        try {
            db = yield openTalerDb();
        }
        catch (e) {
            console.error("could not open database", e);
            return;
        }
        const http = new http_1.BrowserHttpLib();
        const notifier = new ChromeNotifier();
        console.log("setting wallet");
        const wallet = new wallet_1.Wallet(db, http, badge, notifier);
        // Useful for debugging in the background page.
        window.talerWallet = wallet;
        currentWallet = wallet;
    });
}
/**
 * Inject a script into a tab.  Gracefully logs errors
 * and works around a bug where the tab's URL does not match the internal URL,
 * making the injection fail in a confusing way.
 */
function injectScript(tabId, details, actualUrl) {
    chrome.tabs.executeScript(tabId, details, () => {
        // Required to squelch chrome's "unchecked lastError" warning.
        // Sometimes chrome reports the URL of a tab as http/https but
        // injection fails.  This can happen when a page is unloaded or
        // shows a "no internet" page etc.
        if (chrome.runtime.lastError) {
            console.warn("injection failed on page", actualUrl, chrome.runtime.lastError.message);
        }
    });
}
/**
 * Main function to run for the WebExtension backend.
 *
 * Sets up all event handlers and other machinery.
 */
function wxMain() {
    return __awaiter(this, void 0, void 0, function* () {
        // Explicitly unload the extension page as soon as an update is available,
        // so the update gets installed as soon as possible.
        chrome.runtime.onUpdateAvailable.addListener((details) => {
            console.log("update available:", details);
            chrome.runtime.reload();
        });
        window.onerror = (m, source, lineno, colno, error) => {
            logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
        };
        chrome.tabs.query({}, (tabs) => {
            console.log("got tabs", tabs);
            for (const tab of tabs) {
                if (!tab.url || !tab.id) {
                    continue;
                }
                const uri = new URI(tab.url);
                if (uri.protocol() !== "http" && uri.protocol() !== "https") {
                    continue;
                }
                console.log("injecting into existing tab", tab.id, "with url", uri.href(), "protocol", uri.protocol());
                injectScript(tab.id, { file: "/dist/contentScript-bundle.js", runAt: "document_start" }, uri.href());
                const code = `
        if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
          document.dispatchEvent(new Event("taler-probe-result"));
        }
      `;
                injectScript(tab.id, { code, runAt: "document_start" }, uri.href());
            }
        });
        const tabTimers = {};
        chrome.tabs.onRemoved.addListener((tabId, changeInfo) => {
            const tt = tabTimers[tabId] || [];
            for (const t of tt) {
                chrome.extension.getBackgroundPage().clearTimeout(t);
            }
        });
        chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
            if (changeInfo.status !== "complete") {
                return;
            }
            const timers = [];
            const addRun = (dt) => {
                const id = chrome.extension.getBackgroundPage().setTimeout(run, dt);
                timers.push(id);
            };
            const run = () => {
                timers.shift();
                chrome.tabs.get(tabId, (tab) => {
                    if (chrome.runtime.lastError) {
                        return;
                    }
                    if (!tab.url || !tab.id) {
                        return;
                    }
                    const uri = new URI(tab.url);
                    if (!(uri.protocol() === "http" || uri.protocol() === "https")) {
                        return;
                    }
                    const code = `
          if (("taler" in window) || document.documentElement.getAttribute("data-taler-nojs")) {
            document.dispatchEvent(new Event("taler-probe-result"));
          }
        `;
                    injectScript(tab.id, { code, runAt: "document_start" }, uri.href());
                });
            };
            addRun(0);
            addRun(50);
            addRun(300);
            addRun(1000);
            addRun(2000);
            addRun(4000);
            addRun(8000);
            addRun(16000);
            tabTimers[tabId] = timers;
        });
        chrome.extension.getBackgroundPage().setInterval(clearRateLimitCache, 5000);
        reinitWallet();
        // Handlers for messages coming directly from the content
        // script on the page
        chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
            dispatch(req, sender, sendResponse);
            return true;
        });
        // Clear notifications both when the popop opens,
        // as well when it closes.
        chrome.runtime.onConnect.addListener((port) => {
            if (port.name === "popup") {
                if (currentWallet) {
                    currentWallet.clearNotification();
                }
                port.onDisconnect.addListener(() => {
                    if (currentWallet) {
                        currentWallet.clearNotification();
                    }
                });
            }
        });
        // Handlers for catching HTTP requests
        chrome.webRequest.onHeadersReceived.addListener((details) => {
            const wallet = currentWallet;
            if (!wallet) {
                console.warn("wallet not available while handling header");
            }
            if (details.statusCode === 402) {
                console.log(`got 402 from ${details.url}`);
                return handleHttpPayment(details.responseHeaders || [], details.url, details.tabId);
            }
            else if (details.statusCode === 202) {
                return handleBankRequest(wallet, details.responseHeaders || [], details.url, details.tabId);
            }
        }, { urls: ["<all_urls>"] }, ["responseHeaders", "blocking"]);
    });
}
exports.wxMain = wxMain;
/**
 * Return a promise that resolves
 * to the taler wallet db.
 */
function openTalerDb() {
    return new Promise((resolve, reject) => {
        const req = indexedDB.open(DB_NAME, dbTypes_1.WALLET_DB_VERSION);
        req.onerror = (e) => {
            console.log("taler database error", e);
            reject(e);
        };
        req.onsuccess = (e) => {
            req.result.onversionchange = (evt) => {
                console.log(`handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`);
                req.result.close();
                reinitWallet();
            };
            resolve(req.result);
        };
        req.onupgradeneeded = (e) => {
            const db = req.result;
            console.log(`DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${e.newVersion}`);
            switch (e.oldVersion) {
                case 0:// DB does not exist yet
                    for (const n in dbTypes_1.Stores) {
                        if (dbTypes_1.Stores[n] instanceof query_1.Store) {
                            const si = dbTypes_1.Stores[n];
                            const s = db.createObjectStore(si.name, si.storeParams);
                            for (const indexName in si) {
                                if (si[indexName] instanceof query_1.Index) {
                                    const ii = si[indexName];
                                    s.createIndex(ii.indexName, ii.keyPath, ii.options);
                                }
                            }
                        }
                    }
                    break;
                default:
                    if (e.oldVersion !== dbTypes_1.WALLET_DB_VERSION) {
                        oldDbVersion = e.oldVersion;
                        chrome.tabs.create({
                            url: chrome.extension.getURL("/src/webex/pages/reset-required.html"),
                        });
                        setBadgeText({ text: "err" });
                        chrome.browserAction.setBadgeBackgroundColor({ color: "#F00" });
                        throw Error("incompatible DB");
                    }
                    break;
            }
        };
    });
}
function exportDb(db) {
    const dump = {
        name: db.name,
        stores: {},
        version: db.version,
    };
    return new Promise((resolve, reject) => {
        const tx = db.transaction(Array.from(db.objectStoreNames));
        tx.addEventListener("complete", () => {
            resolve(dump);
        });
        // tslint:disable-next-line:prefer-for-of
        for (let i = 0; i < db.objectStoreNames.length; i++) {
            const name = db.objectStoreNames[i];
            const storeDump = {};
            dump.stores[name] = storeDump;
            tx.objectStore(name)
                .openCursor()
                .addEventListener("success", (e) => {
                const cursor = e.target.result;
                if (cursor) {
                    storeDump[cursor.key] = cursor.value;
                    cursor.continue();
                }
            });
        }
    });
}
function importDb(db, dump) {
    console.log("importing db", dump);
    return new Promise((resolve, reject) => {
        const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
        if (dump.stores) {
            for (const storeName in dump.stores) {
                const objects = [];
                const dumpStore = dump.stores[storeName];
                for (const key in dumpStore) {
                    objects.push(dumpStore[key]);
                }
                console.log(`importing ${objects.length} records into ${storeName}`);
                const store = tx.objectStore(storeName);
                for (const obj of objects) {
                    store.put(obj);
                }
            }
        }
        tx.addEventListener("complete", () => {
            resolve();
        });
    });
}
function deleteDb() {
    indexedDB.deleteDatabase(DB_NAME);
}


/***/ }),
/* 23 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2016 Inria

 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/>
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Configurable logging.  Allows to log persistently to a database.
 */
const query_1 = __webpack_require__(2);
// Right now, our debug/info/warn/debug loggers just use the console based
// loggers.  This might change in the future.
function makeInfo() {
    return console.info.bind(console, "%o");
}
function makeWarn() {
    return console.warn.bind(console, "%o");
}
function makeError() {
    return console.error.bind(console, "%o");
}
function makeDebug() {
    return console.log.bind(console, "%o");
}
/**
 * Log a message using the configurable logger.
 */
function log(msg, level = "info") {
    return __awaiter(this, void 0, void 0, function* () {
        const ci = getCallInfo(2);
        return record(level, msg, undefined, ci.file, ci.line, ci.column);
    });
}
exports.log = log;
function getCallInfo(level) {
    // see https://github.com/v8/v8/wiki/Stack-Trace-API
    const stack = Error().stack;
    if (!stack) {
        return unknownFrame;
    }
    const lines = stack.split("\n");
    return parseStackLine(lines[level + 1]);
}
const unknownFrame = {
    column: 0,
    file: "(unknown)",
    line: 0,
    method: "(unknown)",
};
/**
 * Adapted from https://github.com/errwischt/stacktrace-parser.
 */
function parseStackLine(stackLine) {
    // tslint:disable-next-line:max-line-length
    const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
    const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
    const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
    let parts;
    parts = gecko.exec(stackLine);
    if (parts) {
        const f = {
            column: parts[5] ? +parts[5] : undefined,
            file: parts[3],
            line: +parts[4],
            method: parts[1] || "(unknown)",
        };
        return f;
    }
    parts = chrome.exec(stackLine);
    if (parts) {
        const f = {
            column: parts[4] ? +parts[4] : undefined,
            file: parts[2],
            line: +parts[3],
            method: parts[1] || "(unknown)",
        };
        return f;
    }
    parts = node.exec(stackLine);
    if (parts) {
        const f = {
            column: parts[4] ? +parts[4] : undefined,
            file: parts[2],
            line: +parts[3],
            method: parts[1] || "(unknown)",
        };
        return f;
    }
    return unknownFrame;
}
let db;
/**
 * Get all logs.  Only use for debugging, since this returns all logs ever made
 * at once without pagination.
 */
function getLogs() {
    return __awaiter(this, void 0, void 0, function* () {
        if (!db) {
            db = yield openLoggingDb();
        }
        return yield new query_1.QueryRoot(db).iter(logsStore).toArray();
    });
}
exports.getLogs = getLogs;
/**
 * The barrier ensures that only one DB write is scheduled against the log db
 * at the same time, so that the DB can stay responsive.  This is a bit of a
 * design problem with IndexedDB, it doesn't guarantee fairness.
 */
let barrier;
/**
 * Record an exeption in the log.
 */
function recordException(msg, e) {
    return __awaiter(this, void 0, void 0, function* () {
        let stack;
        let frame;
        try {
            stack = e.stack;
            if (stack) {
                const lines = stack.split("\n");
                frame = parseStackLine(lines[1]);
            }
        }
        catch (e) {
            // ignore
        }
        if (!frame) {
            frame = unknownFrame;
        }
        return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
    });
}
exports.recordException = recordException;
/**
 * Cache for reports.  Also used when something is so broken that we can't even
 * access the database.
 */
const reportCache = {};
/**
 * Get a UUID that does not use cryptographically secure randomness.
 * Formatted as RFC4122 version 4 UUID.
 */
function getInsecureUuid() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
        const r = Math.random() * 16 | 0;
        const v = c === "x" ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}
/**
 * Store a report and return a unique identifier to retrieve it later.
 */
function storeReport(report) {
    return __awaiter(this, void 0, void 0, function* () {
        const uid = getInsecureUuid();
        reportCache[uid] = report;
        return uid;
    });
}
exports.storeReport = storeReport;
/**
 * Retrieve a report by its unique identifier.
 */
function getReport(reportUid) {
    return __awaiter(this, void 0, void 0, function* () {
        return reportCache[reportUid];
    });
}
exports.getReport = getReport;
/**
 * Record a log entry in the database.
 */
function record(level, msg, detail, source, line, col) {
    return __awaiter(this, void 0, void 0, function* () {
        if (typeof indexedDB === "undefined") {
            console.log("can't access DB for logging in this context");
            console.log("log was", { level, msg, detail, source, line, col });
            return;
        }
        let myBarrier;
        if (barrier) {
            const p = barrier.promise;
            myBarrier = barrier = query_1.openPromise();
            yield p;
        }
        else {
            myBarrier = barrier = query_1.openPromise();
        }
        try {
            if (!db) {
                db = yield openLoggingDb();
            }
            const count = yield new query_1.QueryRoot(db).count(logsStore);
            if (count > 1000) {
                yield new query_1.QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200));
            }
            const entry = {
                col,
                detail,
                level,
                line,
                msg,
                source,
                timestamp: new Date().getTime(),
            };
            yield new query_1.QueryRoot(db).put(logsStore, entry);
        }
        finally {
            yield Promise.resolve().then(() => myBarrier.resolve());
        }
    });
}
exports.record = record;
const loggingDbVersion = 2;
const logsStore = new query_1.Store("logs");
/**
 * Get a handle to the IndexedDB used to store
 * logs.
 */
function openLoggingDb() {
    return new Promise((resolve, reject) => {
        const req = indexedDB.open("taler-logging", loggingDbVersion);
        req.onerror = (e) => {
            reject(e);
        };
        req.onsuccess = (e) => {
            resolve(req.result);
        };
        req.onupgradeneeded = (e) => {
            const resDb = req.result;
            if (e.oldVersion !== 0) {
                try {
                    resDb.deleteObjectStore("logs");
                }
                catch (e) {
                    console.error(e);
                }
            }
            resDb.createObjectStore("logs", { keyPath: "id", autoIncrement: true });
            resDb.createObjectStore("reports", { keyPath: "uid", autoIncrement: false });
        };
    });
}
exports.openLoggingDb = openLoggingDb;
/**
 * Log a message at severity info.
 */
exports.info = makeInfo();
/**
 * Log a message at severity debug.
 */
exports.debug = makeDebug();
/**
 * Log a message at severity warn.
 */
exports.warn = makeWarn();
/**
 * Log a message at severity error.
 */
exports.error = makeError();


/***/ }),
/* 24 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2015-2017 GNUnet e.V. and INRIA

 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/>
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Types used by clients of the wallet.
 *
 * These types are defined in a separate file make tree shaking easier, since
 * some components use these types (via RPC) but do not depend on the wallet
 * code directly.
 */
/**
 * Imports.
 */
const checkable_1 = __webpack_require__(3);
const amounts_1 = __webpack_require__(1);
/**
 * Response for the create reserve request to the wallet.
 */
let CreateReserveResponse = class CreateReserveResponse {
};
__decorate([
    checkable_1.Checkable.String()
], CreateReserveResponse.prototype, "exchange", void 0);
__decorate([
    checkable_1.Checkable.String()
], CreateReserveResponse.prototype, "reservePub", void 0);
CreateReserveResponse = __decorate([
    checkable_1.Checkable.Class()
], CreateReserveResponse);
exports.CreateReserveResponse = CreateReserveResponse;
/**
 * For terseness.
 */
function mkAmount(value, fraction, currency) {
    return { value, fraction, currency };
}
exports.mkAmount = mkAmount;
/**
 * Request to mark a reserve as confirmed.
 */
let CreateReserveRequest = class CreateReserveRequest {
};
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], CreateReserveRequest.prototype, "amount", void 0);
__decorate([
    checkable_1.Checkable.String()
], CreateReserveRequest.prototype, "exchange", void 0);
__decorate([
    checkable_1.Checkable.Optional(checkable_1.Checkable.Any())
], CreateReserveRequest.prototype, "senderWire", void 0);
CreateReserveRequest = __decorate([
    checkable_1.Checkable.Class()
], CreateReserveRequest);
exports.CreateReserveRequest = CreateReserveRequest;
/**
 * Request to mark a reserve as confirmed.
 */
let ConfirmReserveRequest = class ConfirmReserveRequest {
};
__decorate([
    checkable_1.Checkable.String()
], ConfirmReserveRequest.prototype, "reservePub", void 0);
ConfirmReserveRequest = __decorate([
    checkable_1.Checkable.Class()
], ConfirmReserveRequest);
exports.ConfirmReserveRequest = ConfirmReserveRequest;
/**
 * Wire coins to the user's own bank account.
 */
let ReturnCoinsRequest = class ReturnCoinsRequest {
};
__decorate([
    checkable_1.Checkable.Value(() => amounts_1.AmountJson)
], ReturnCoinsRequest.prototype, "amount", void 0);
__decorate([
    checkable_1.Checkable.String()
], ReturnCoinsRequest.prototype, "exchange", void 0);
__decorate([
    checkable_1.Checkable.Any()
], ReturnCoinsRequest.prototype, "senderWire", void 0);
ReturnCoinsRequest = __decorate([
    checkable_1.Checkable.Class()
], ReturnCoinsRequest);
exports.ReturnCoinsRequest = ReturnCoinsRequest;


/***/ }),
/* 25 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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/>
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * High-level wallet operations that should be indepentent from the underlying
 * browser extension interface.
 */
/**
 * Imports.
 */
const cryptoApi_1 = __webpack_require__(26);
const helpers_1 = __webpack_require__(10);
const http_1 = __webpack_require__(8);
const LibtoolVersion = __webpack_require__(33);
const query_1 = __webpack_require__(2);
const timer_1 = __webpack_require__(9);
const Amounts = __webpack_require__(1);
const URI = __webpack_require__(5);
const axios_1 = __webpack_require__(34);
const dbTypes_1 = __webpack_require__(19);
const talerTypes_1 = __webpack_require__(7);
/**
 * Wallet protocol version spoken with the exchange
 * and merchant.
 *
 * Uses libtool's current:revision:age versioning.
 */
exports.WALLET_PROTOCOL_VERSION = "2:0:0";
const builtinCurrencies = [
    {
        auditors: [
            {
                auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
                baseUrl: "https://auditor.demo.taler.net/",
                expirationStamp: (new Date(2027, 1)).getTime(),
            },
        ],
        exchanges: [],
        fractionalDigits: 2,
        name: "KUDOS",
    },
];
function isWithdrawableDenom(d) {
    const nowSec = (new Date()).getTime() / 1000;
    const stampWithdrawSec = helpers_1.getTalerStampSec(d.stampExpireWithdraw);
    if (stampWithdrawSec === null) {
        return false;
    }
    const stampStartSec = helpers_1.getTalerStampSec(d.stampStart);
    if (stampStartSec === null) {
        return false;
    }
    // Withdraw if still possible to withdraw within a minute
    if ((stampWithdrawSec + 60 > nowSec) && (nowSec >= stampStartSec)) {
        return true;
    }
    return false;
}
/**
 * Get the amount that we lose when refreshing a coin of the given denomination
 * with a certain amount left.
 *
 * If the amount left is zero, then the refresh cost
 * is also considered to be zero.  If a refresh isn't possible (e.g. due to lack of
 * the right denominations), then the cost is the full amount left.
 *
 * Considers refresh fees, withdrawal fees after refresh and amounts too small
 * to refresh.
 */
function getTotalRefreshCost(denoms, refreshedDenom, amountLeft) {
    const withdrawAmount = Amounts.sub(amountLeft, refreshedDenom.feeRefresh).amount;
    const withdrawDenoms = getWithdrawDenomList(withdrawAmount, denoms);
    const resultingAmount = Amounts.add(Amounts.getZero(withdrawAmount.currency), ...withdrawDenoms.map((d) => d.value)).amount;
    const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
    console.log("total refresh cost for", helpers_1.amountToPretty(amountLeft), "is", helpers_1.amountToPretty(totalCost));
    return totalCost;
}
exports.getTotalRefreshCost = getTotalRefreshCost;
/**
 * Select coins for a payment under the merchant's constraints.
 *
 * @param denoms all available denoms, used to compute refresh fees
 */
function selectPayCoins(denoms, cds, paymentAmount, depositFeeLimit) {
    if (cds.length === 0) {
        return undefined;
    }
    // Sort by ascending deposit fee and denomPub if deposit fee is the same
    // (to guarantee deterministic results)
    cds.sort((o1, o2) => Amounts.cmp(o1.denom.feeDeposit, o2.denom.feeDeposit) ||
        helpers_1.strcmp(o1.denom.denomPub, o2.denom.denomPub));
    const currency = cds[0].denom.value.currency;
    const cdsResult = [];
    let accDepositFee = Amounts.getZero(currency);
    let accAmount = Amounts.getZero(currency);
    for (const { coin, denom } of cds) {
        if (coin.suspended) {
            continue;
        }
        if (coin.status !== dbTypes_1.CoinStatus.Fresh) {
            continue;
        }
        if (Amounts.cmp(denom.feeDeposit, coin.currentAmount) >= 0) {
            continue;
        }
        cdsResult.push({ coin, denom });
        accDepositFee = Amounts.add(denom.feeDeposit, accDepositFee).amount;
        let leftAmount = Amounts.sub(coin.currentAmount, Amounts.sub(paymentAmount, accAmount).amount).amount;
        accAmount = Amounts.add(coin.currentAmount, accAmount).amount;
        const coversAmount = Amounts.cmp(accAmount, paymentAmount) >= 0;
        const coversAmountWithFee = Amounts.cmp(accAmount, Amounts.add(paymentAmount, denom.feeDeposit).amount) >= 0;
        const isBelowFee = Amounts.cmp(accDepositFee, depositFeeLimit) <= 0;
        console.log("coin selection", { coversAmount, isBelowFee, accDepositFee, accAmount, paymentAmount });
        if ((coversAmount && isBelowFee) || coversAmountWithFee) {
            const depositFeeToCover = Amounts.sub(accDepositFee, depositFeeLimit).amount;
            leftAmount = Amounts.sub(leftAmount, depositFeeToCover).amount;
            console.log("deposit fee to cover", helpers_1.amountToPretty(depositFeeToCover));
            let totalFees = Amounts.getZero(currency);
            if (coversAmountWithFee && !isBelowFee) {
                // these are the fees the customer has to pay
                // because the merchant doesn't cover them
                totalFees = Amounts.sub(depositFeeLimit, accDepositFee).amount;
            }
            totalFees = Amounts.add(totalFees, getTotalRefreshCost(denoms, denom, leftAmount)).amount;
            return { cds: cdsResult, totalFees };
        }
    }
    return undefined;
}
exports.selectPayCoins = selectPayCoins;
/**
 * Get a list of denominations (with repetitions possible)
 * whose total value is as close as possible to the available
 * amount, but never larger.
 */
function getWithdrawDenomList(amountAvailable, denoms) {
    let remaining = Amounts.copy(amountAvailable);
    const ds = [];
    denoms = denoms.filter(isWithdrawableDenom);
    denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
    // This is an arbitrary number of coins
    // we can withdraw in one go.  It's not clear if this limit
    // is useful ...
    for (let i = 0; i < 1000; i++) {
        let found = false;
        for (const d of denoms) {
            const cost = Amounts.add(d.value, d.feeWithdraw).amount;
            if (Amounts.cmp(remaining, cost) < 0) {
                continue;
            }
            found = true;
            remaining = Amounts.sub(remaining, cost).amount;
            ds.push(d);
            break;
        }
        if (!found) {
            break;
        }
    }
    return ds;
}
/**
 * The platform-independent wallet implementation.
 */
class Wallet {
    constructor(db, http, badge, notifier) {
        this.processPreCoinConcurrent = 0;
        this.processPreCoinThrottle = {};
        this.cachedNextUrl = {};
        this.activeTipOperations = {};
        /**
         * Set of identifiers for running operations.
         */
        this.runningOperations = new Set();
        this.db = db;
        this.http = http;
        this.badge = badge;
        this.notifier = notifier;
        this.cryptoApi = new cryptoApi_1.CryptoApi();
        this.timerGroup = new timer_1.TimerGroup();
        const init = () => __awaiter(this, void 0, void 0, function* () {
            yield this.fillDefaults().catch((e) => console.log(e));
            yield this.collectGarbage().catch((e) => console.log(e));
            this.updateExchanges();
            this.resumePendingFromDb();
            this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges());
        });
        init();
    }
    q() {
        return new query_1.QueryRoot(this.db);
    }
    fillDefaults() {
        return __awaiter(this, void 0, void 0, function* () {
            const onTrue = (r) => {
                console.log("defaults already applied");
            };
            const onFalse = (r) => {
                console.log("applying defaults");
                r.put(dbTypes_1.Stores.config, { key: "currencyDefaultsApplied", value: true })
                    .putAll(dbTypes_1.Stores.currencies, builtinCurrencies)
                    .finish();
            };
            yield (this.q()
                .iter(dbTypes_1.Stores.config)
                .filter((x) => x.key === "currencyDefaultsApplied")
                .first()
                .cond((x) => x && x.value, onTrue, onFalse));
        });
    }
    startOperation(operationId) {
        this.runningOperations.add(operationId);
        this.badge.startBusy();
    }
    stopOperation(operationId) {
        this.runningOperations.delete(operationId);
        if (this.runningOperations.size === 0) {
            this.badge.stopBusy();
        }
    }
    updateExchanges() {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("updating exchanges");
            const exchangesUrls = yield this.q()
                .iter(dbTypes_1.Stores.exchanges)
                .map((e) => e.baseUrl)
                .toArray();
            for (const url of exchangesUrls) {
                this.updateExchangeFromUrl(url)
                    .catch((e) => {
                    console.error("updating exchange failed", e);
                });
            }
        });
    }
    /**
     * Resume various pending operations that are pending
     * by looking at the database.
     */
    resumePendingFromDb() {
        console.log("resuming pending operations from db");
        this.q()
            .iter(dbTypes_1.Stores.reserves)
            .forEach((reserve) => {
            console.log("resuming reserve", reserve.reserve_pub);
            this.processReserve(reserve);
        });
        this.q()
            .iter(dbTypes_1.Stores.precoins)
            .forEach((preCoin) => {
            console.log("resuming precoin");
            this.processPreCoin(preCoin);
        });
        this.q()
            .iter(dbTypes_1.Stores.refresh)
            .forEach((r) => {
            this.continueRefreshSession(r);
        });
        this.q()
            .iter(dbTypes_1.Stores.coinsReturns)
            .forEach((r) => {
            this.depositReturnedCoins(r);
        });
        // FIXME: optimize via index
        this.q()
            .iter(dbTypes_1.Stores.coins)
            .forEach((c) => {
            if (c.status === dbTypes_1.CoinStatus.Dirty) {
                console.log("resuming pending refresh for coin", c);
                this.refresh(c.coinPub);
            }
        });
    }
    getCoinsForReturn(exchangeBaseUrl, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, exchangeBaseUrl);
            if (!exchange) {
                throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
            }
            const coins = yield (this.q()
                .iterIndex(dbTypes_1.Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
                .toArray());
            if (!coins || !coins.length) {
                return [];
            }
            const denoms = yield this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl).toArray();
            // Denomination of the first coin, we assume that all other
            // coins have the same currency
            const firstDenom = yield this.q().get(dbTypes_1.Stores.denominations, [
                exchange.baseUrl,
                coins[0].denomPub,
            ]);
            if (!firstDenom) {
                throw Error("db inconsistent");
            }
            const currency = firstDenom.value.currency;
            const cds = [];
            for (const coin of coins) {
                const denom = yield this.q().get(dbTypes_1.Stores.denominations, [exchange.baseUrl, coin.denomPub]);
                if (!denom) {
                    throw Error("db inconsistent");
                }
                if (denom.value.currency !== currency) {
                    console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
                    continue;
                }
                if (coin.suspended) {
                    continue;
                }
                if (coin.status !== dbTypes_1.CoinStatus.Fresh) {
                    continue;
                }
                cds.push({ coin, denom });
            }
            console.log("coin return:  selecting from possible coins", { cds, amount });
            const res = selectPayCoins(denoms, cds, amount, amount);
            if (res) {
                return res.cds;
            }
            return undefined;
        });
    }
    /**
     * Get exchanges and associated coins that are still spendable,
     * but only if the sum the coins' remaining value exceeds the payment amount.
     */
    getCoinsForPayment(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const { allowedAuditors, allowedExchanges, depositFeeLimit, paymentAmount, wireFeeAmortization, wireFeeLimit, wireFeeTime, wireMethod, } = args;
            let remainingAmount = paymentAmount;
            const exchanges = yield this.q().iter(dbTypes_1.Stores.exchanges).toArray();
            for (const exchange of exchanges) {
                let isOkay = false;
                // is the exchange explicitly allowed?
                for (const allowedExchange of allowedExchanges) {
                    if (allowedExchange.master_pub === exchange.masterPublicKey) {
                        isOkay = true;
                        break;
                    }
                }
                // is the exchange allowed because of one of its auditors?
                if (!isOkay) {
                    for (const allowedAuditor of allowedAuditors) {
                        for (const auditor of exchange.auditors) {
                            if (auditor.auditor_pub === allowedAuditor.auditor_pub) {
                                isOkay = true;
                                break;
                            }
                        }
                        if (isOkay) {
                            break;
                        }
                    }
                }
                if (!isOkay) {
                    continue;
                }
                const coins = yield this.q()
                    .iterIndex(dbTypes_1.Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
                    .toArray();
                const denoms = yield this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl).toArray();
                if (!coins || coins.length === 0) {
                    continue;
                }
                // Denomination of the first coin, we assume that all other
                // coins have the same currency
                const firstDenom = yield this.q().get(dbTypes_1.Stores.denominations, [
                    exchange.baseUrl,
                    coins[0].denomPub,
                ]);
                if (!firstDenom) {
                    throw Error("db inconsistent");
                }
                const currency = firstDenom.value.currency;
                const cds = [];
                for (const coin of coins) {
                    const denom = yield this.q().get(dbTypes_1.Stores.denominations, [exchange.baseUrl, coin.denomPub]);
                    if (!denom) {
                        throw Error("db inconsistent");
                    }
                    if (denom.value.currency !== currency) {
                        console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
                        continue;
                    }
                    if (coin.suspended) {
                        continue;
                    }
                    if (coin.status !== dbTypes_1.CoinStatus.Fresh) {
                        continue;
                    }
                    cds.push({ coin, denom });
                }
                const fees = yield this.q().get(dbTypes_1.Stores.exchangeWireFees, exchange.baseUrl);
                if (!fees) {
                    console.error("no fees found for exchange", exchange);
                    continue;
                }
                console.log("payment coins: wireFeeLimit", wireFeeLimit);
                console.log("payment coins: wireFeeAmortization", wireFeeAmortization);
                console.log("payment coins: fees", fees);
                console.log("payment coins: wireMethod", wireMethod);
                console.log("payment coins: wireFeeTime", wireFeeTime);
                let totalFees = Amounts.getZero(currency);
                let wireFee;
                for (const fee of (fees.feesForType[wireMethod] || [])) {
                    if (fee.startStamp <= wireFeeTime && fee.endStamp >= wireFeeTime) {
                        wireFee = fee.wireFee;
                        break;
                    }
                }
                console.log("payment coins: current wire fees", wireFee);
                if (wireFee) {
                    const amortizedWireFee = Amounts.divide(wireFee, wireFeeAmortization);
                    if (Amounts.cmp(wireFeeLimit, amortizedWireFee) < 0) {
                        totalFees = Amounts.add(amortizedWireFee, totalFees).amount;
                        remainingAmount = Amounts.add(amortizedWireFee, remainingAmount).amount;
                    }
                }
                const res = selectPayCoins(denoms, cds, remainingAmount, depositFeeLimit);
                if (res) {
                    totalFees = Amounts.add(totalFees, res.totalFees).amount;
                    return {
                        cds: res.cds,
                        exchangeUrl: exchange.baseUrl,
                        totalAmount: remainingAmount,
                        totalFees,
                    };
                }
            }
            return undefined;
        });
    }
    /**
     * Record all information that is necessary to
     * pay for a proposal in the wallet's database.
     */
    recordConfirmPay(proposal, payCoinInfo, chosenExchange) {
        return __awaiter(this, void 0, void 0, function* () {
            const payReq = {
                coins: payCoinInfo.sigs,
                merchant_pub: proposal.contractTerms.merchant_pub,
                mode: "pay",
                order_id: proposal.contractTerms.order_id,
            };
            const t = {
                abortDone: false,
                abortRequested: false,
                contractTerms: proposal.contractTerms,
                contractTermsHash: proposal.contractTermsHash,
                finished: false,
                lastSessionId: undefined,
                lastSessionSig: undefined,
                merchantSig: proposal.merchantSig,
                payReq,
                refundsDone: {},
                refundsPending: {},
                timestamp: (new Date()).getTime(),
                timestamp_refund: 0,
            };
            yield this.q()
                .put(dbTypes_1.Stores.purchases, t)
                .putAll(dbTypes_1.Stores.coins, payCoinInfo.updatedCoins)
                .finish();
            this.badge.showNotification();
            this.notifier.notify();
            return t;
        });
    }
    /**
     * Download a proposal and store it in the database.
     * Returns an id for it to retrieve it later.
     */
    downloadProposal(url) {
        return __awaiter(this, void 0, void 0, function* () {
            const oldProposal = yield this.q().getIndexed(dbTypes_1.Stores.proposals.urlIndex, url);
            if (oldProposal) {
                return oldProposal.id;
            }
            const { priv, pub } = yield this.cryptoApi.createEddsaKeypair();
            const parsed_url = new URI(url);
            const urlWithNonce = parsed_url.setQuery({ nonce: pub }).href();
            console.log("downloading contract from '" + urlWithNonce + "'");
            let resp;
            try {
                resp = yield axios_1.default.get(urlWithNonce, { validateStatus: (s) => s === 200 });
            }
            catch (e) {
                console.log("contract download failed", e);
                throw e;
            }
            console.log("got response", resp);
            const proposal = talerTypes_1.Proposal.checked(resp.data);
            const contractTermsHash = yield this.hashContract(proposal.contract_terms);
            const proposalRecord = {
                contractTerms: proposal.contract_terms,
                contractTermsHash,
                merchantSig: proposal.sig,
                noncePriv: priv,
                timestamp: (new Date()).getTime(),
                url,
            };
            const id = yield this.q().putWithResult(dbTypes_1.Stores.proposals, proposalRecord);
            this.notifier.notify();
            if (typeof id !== "number") {
                throw Error("db schema wrong");
            }
            return id;
        });
    }
    refundFailedPay(proposalId) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log(`refunding failed payment with proposal id ${proposalId}`);
            const proposal = yield this.q().get(dbTypes_1.Stores.proposals, proposalId);
            if (!proposal) {
                throw Error(`proposal with id ${proposalId} not found`);
            }
            const purchase = yield this.q().get(dbTypes_1.Stores.purchases, proposal.contractTermsHash);
            if (!purchase) {
                throw Error("purchase not found for proposal");
            }
            if (purchase.finished) {
                throw Error("can't auto-refund finished purchase");
            }
        });
    }
    submitPay(contractTermsHash, sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const purchase = yield this.q().get(dbTypes_1.Stores.purchases, contractTermsHash);
            if (!purchase) {
                throw Error("Purchase not found: " + contractTermsHash);
            }
            if (purchase.abortRequested) {
                throw Error("not submitting payment for aborted purchase");
            }
            let resp;
            const payReq = Object.assign({}, purchase.payReq, { session_id: sessionId });
            try {
                const config = {
                    headers: { "Content-Type": "application/json;charset=UTF-8" },
                    timeout: 5000,
                    validateStatus: (s) => s === 200,
                };
                resp = yield axios_1.default.post(purchase.contractTerms.pay_url, payReq, config);
            }
            catch (e) {
                // Gives the user the option to retry / abort and refresh
                console.log("payment failed", e);
                throw e;
            }
            const merchantResp = resp.data;
            console.log("got success from pay_url");
            const merchantPub = purchase.contractTerms.merchant_pub;
            const valid = yield (this.cryptoApi.isValidPaymentSignature(merchantResp.sig, contractTermsHash, merchantPub));
            if (!valid) {
                console.error("merchant payment signature invalid");
                // FIXME: properly display error
                throw Error("merchant payment signature invalid");
            }
            purchase.finished = true;
            const modifiedCoins = [];
            for (const pc of purchase.payReq.coins) {
                const c = yield this.q().get(dbTypes_1.Stores.coins, pc.coin_pub);
                if (!c) {
                    console.error("coin not found");
                    throw Error("coin used in payment not found");
                }
                c.status = dbTypes_1.CoinStatus.Dirty;
                modifiedCoins.push(c);
            }
            const fu = new URI(purchase.contractTerms.fulfillment_url);
            fu.addSearch("order_id", purchase.contractTerms.order_id);
            if (merchantResp.session_sig) {
                purchase.lastSessionSig = merchantResp.session_sig;
                purchase.lastSessionId = sessionId;
                fu.addSearch("session_sig", merchantResp.session_sig);
            }
            yield this.q()
                .putAll(dbTypes_1.Stores.coins, modifiedCoins)
                .put(dbTypes_1.Stores.purchases, purchase)
                .finish();
            for (const c of purchase.payReq.coins) {
                this.refresh(c.coin_pub);
            }
            const nextUrl = fu.href();
            this.cachedNextUrl[purchase.contractTerms.fulfillment_url] = { nextUrl, lastSessionId: sessionId };
            return { nextUrl };
        });
    }
    /**
     * Add a contract to the wallet and sign coins, and send them.
     */
    confirmPay(proposalId, sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log(`executing confirmPay with proposalId ${proposalId} and sessionId ${sessionId}`);
            const proposal = yield this.q().get(dbTypes_1.Stores.proposals, proposalId);
            if (!proposal) {
                throw Error(`proposal with id ${proposalId} not found`);
            }
            let purchase = yield this.q().get(dbTypes_1.Stores.purchases, proposal.contractTermsHash);
            if (purchase) {
                return this.submitPay(purchase.contractTermsHash, sessionId);
            }
            const contractAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
            let wireFeeLimit;
            if (!proposal.contractTerms.max_wire_fee) {
                wireFeeLimit = Amounts.getZero(contractAmount.currency);
            }
            else {
                wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
            }
            const res = yield this.getCoinsForPayment({
                allowedAuditors: proposal.contractTerms.auditors,
                allowedExchanges: proposal.contractTerms.exchanges,
                depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
                paymentAmount: Amounts.parseOrThrow(proposal.contractTerms.amount),
                wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
                wireFeeLimit,
                wireFeeTime: helpers_1.getTalerStampSec(proposal.contractTerms.timestamp) || 0,
                wireMethod: proposal.contractTerms.wire_method,
            });
            console.log("max_fee", proposal.contractTerms.max_fee);
            console.log("coin selection result", res);
            if (!res) {
                // Should not happen, since checkPay should be called first
                console.log("not confirming payment, insufficient coins");
                throw Error("insufficient balance");
            }
            const sd = yield this.getSpeculativePayData(proposalId);
            if (!sd) {
                const { exchangeUrl, cds, totalAmount } = res;
                const payCoinInfo = yield this.cryptoApi.signDeposit(proposal.contractTerms, cds, totalAmount);
                purchase = yield this.recordConfirmPay(proposal, payCoinInfo, exchangeUrl);
            }
            else {
                purchase = yield this.recordConfirmPay(sd.proposal, sd.payCoinInfo, sd.exchangeUrl);
            }
            return this.submitPay(purchase.contractTermsHash, sessionId);
        });
    }
    /**
     * Get the speculative pay data, but only if coins have not changed in between.
     */
    getSpeculativePayData(proposalId) {
        return __awaiter(this, void 0, void 0, function* () {
            const sp = this.speculativePayData;
            if (!sp) {
                return;
            }
            if (sp.proposalId !== proposalId) {
                return;
            }
            const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub);
            const coins = yield this.q().getMany(dbTypes_1.Stores.coins, coinKeys);
            for (let i = 0; i < coins.length; i++) {
                const specCoin = sp.payCoinInfo.originalCoins[i];
                const currentCoin = coins[i];
                // Coin does not exist anymore!
                if (!currentCoin) {
                    return;
                }
                if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) !== 0) {
                    return;
                }
            }
            return sp;
        });
    }
    /**
     * Check if payment for an offer is possible, or if the offer has already
     * been payed for.
     *
     * Also speculatively computes the signature for the payment to make the payment
     * look faster to the user.
     */
    checkPay(proposalId) {
        return __awaiter(this, void 0, void 0, function* () {
            const proposal = yield this.q().get(dbTypes_1.Stores.proposals, proposalId);
            if (!proposal) {
                throw Error(`proposal with id ${proposalId} not found`);
            }
            // First check if we already payed for it.
            const purchase = yield this.q().get(dbTypes_1.Stores.purchases, proposal.contractTermsHash);
            if (purchase) {
                return { status: "paid" };
            }
            const paymentAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
            let wireFeeLimit;
            if (proposal.contractTerms.max_wire_fee) {
                wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
            }
            else {
                wireFeeLimit = Amounts.getZero(paymentAmount.currency);
            }
            // If not already payed, check if we could pay for it.
            const res = yield this.getCoinsForPayment({
                allowedAuditors: proposal.contractTerms.auditors,
                allowedExchanges: proposal.contractTerms.exchanges,
                depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
                paymentAmount,
                wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
                wireFeeLimit,
                wireFeeTime: helpers_1.getTalerStampSec(proposal.contractTerms.timestamp) || 0,
                wireMethod: proposal.contractTerms.wire_method,
            });
            if (!res) {
                console.log("not confirming payment, insufficient coins");
                return { status: "insufficient-balance" };
            }
            // Only create speculative signature if we don't already have one for this proposal
            if ((!this.speculativePayData) || (this.speculativePayData && this.speculativePayData.proposalId !== proposalId)) {
                const { exchangeUrl, cds, totalAmount } = res;
                const payCoinInfo = yield this.cryptoApi.signDeposit(proposal.contractTerms, cds, totalAmount);
                this.speculativePayData = {
                    exchangeUrl,
                    payCoinInfo,
                    proposal,
                    proposalId,
                };
            }
            return { status: "payment-possible", coinSelection: res };
        });
    }
    /**
     * Retrieve information required to pay for a contract, where the
     * contract is identified via the fulfillment url.
     */
    queryPaymentByFulfillmentUrl(url) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("query for payment", url);
            const t = yield this.q().getIndexed(dbTypes_1.Stores.purchases.fulfillmentUrlIndex, url);
            if (!t) {
                console.log("query for payment failed");
                return undefined;
            }
            console.log("query for payment succeeded:", t);
            return t;
        });
    }
    /**
     * First fetch information requred to withdraw from the reserve,
     * then deplete the reserve, withdrawing coins until it is empty.
     */
    processReserve(reserveRecord, retryDelayMs = 250) {
        return __awaiter(this, void 0, void 0, function* () {
            const opId = "reserve-" + reserveRecord.reserve_pub;
            this.startOperation(opId);
            try {
                const reserve = yield this.updateReserve(reserveRecord.reserve_pub);
                yield this.depleteReserve(reserve);
            }
            catch (e) {
                // random, exponential backoff truncated at 3 minutes
                const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
                console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
                this.timerGroup.after(retryDelayMs, () => this.processReserve(reserveRecord, nextDelay));
            }
            finally {
                this.stopOperation(opId);
            }
        });
    }
    /**
     * Given a planchet, withdraw a coin from the exchange.
     */
    processPreCoin(preCoin, retryDelayMs = 200) {
        return __awaiter(this, void 0, void 0, function* () {
            // Throttle concurrent executions of this function, so we don't withdraw too many coins at once.
            if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
                console.log("delaying processPreCoin");
                this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
                return;
            }
            console.log("executing processPreCoin", preCoin);
            this.processPreCoinConcurrent++;
            try {
                const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, preCoin.exchangeBaseUrl);
                if (!exchange) {
                    console.error("db inconsistent: exchange for precoin not found");
                    return;
                }
                const denom = yield this.q().get(dbTypes_1.Stores.denominations, [preCoin.exchangeBaseUrl, preCoin.denomPub]);
                if (!denom) {
                    console.error("db inconsistent: denom for precoin not found");
                    return;
                }
                const coin = yield this.withdrawExecute(preCoin);
                console.log("processPreCoin: got coin", coin);
                const mutateReserve = (r) => {
                    console.log(`before committing coin: current ${helpers_1.amountToPretty(r.current_amount)}, precoin: ${helpers_1.amountToPretty(r.precoin_amount)})}`);
                    const x = Amounts.sub(r.precoin_amount, preCoin.coinValue, denom.feeWithdraw);
                    if (x.saturated) {
                        console.error("database inconsistent");
                        throw query_1.AbortTransaction;
                    }
                    r.precoin_amount = x.amount;
                    return r;
                };
                yield this.q()
                    .mutate(dbTypes_1.Stores.reserves, preCoin.reservePub, mutateReserve)
                    .delete(dbTypes_1.Stores.precoins, coin.coinPub)
                    .add(dbTypes_1.Stores.coins, coin)
                    .finish();
                if (coin.status === dbTypes_1.CoinStatus.TainedByTip) {
                    const tip = yield this.q().getIndexed(dbTypes_1.Stores.tips.coinPubIndex, coin.coinPub);
                    if (!tip) {
                        throw Error(`inconsistent DB: tip for coin pub ${coin.coinPub} not found.`);
                    }
                    if (tip.accepted) {
                        console.log("untainting already accepted tip");
                        // Transactionally set coin to fresh.
                        const mutateCoin = (c) => {
                            if (c.status === dbTypes_1.CoinStatus.TainedByTip) {
                                c.status = dbTypes_1.CoinStatus.Fresh;
                            }
                            return c;
                        };
                        yield this.q().mutate(dbTypes_1.Stores.coins, coin.coinPub, mutateCoin);
                        // Show notifications only for accepted tips
                        this.badge.showNotification();
                    }
                }
                else {
                    this.badge.showNotification();
                }
                this.notifier.notify();
            }
            catch (e) {
                console.error("Failed to withdraw coin from precoin, retrying in", retryDelayMs, "ms", e);
                // exponential backoff truncated at one minute
                const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000);
                this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, nextRetryDelayMs));
                const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
                this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1;
                this.timerGroup.after(retryDelayMs, () => { this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; });
            }
            finally {
                this.processPreCoinConcurrent--;
            }
        });
    }
    /**
     * Update the timestamp of when an exchange was used.
     */
    updateExchangeUsedTime(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            const now = (new Date()).getTime();
            const update = (r) => {
                r.lastUsedTime = now;
                return r;
            };
            yield this.q().mutate(dbTypes_1.Stores.exchanges, exchangeBaseUrl, update).finish();
        });
    }
    /**
     * Create a reserve, but do not flag it as confirmed yet.
     *
     * Adds the corresponding exchange as a trusted exchange if it is neither
     * audited nor trusted already.
     */
    createReserve(req) {
        return __awaiter(this, void 0, void 0, function* () {
            const keypair = yield this.cryptoApi.createEddsaKeypair();
            const now = (new Date()).getTime();
            const canonExchange = helpers_1.canonicalizeBaseUrl(req.exchange);
            const reserveRecord = {
                created: now,
                current_amount: null,
                exchange_base_url: canonExchange,
                hasPayback: false,
                precoin_amount: Amounts.getZero(req.amount.currency),
                requested_amount: req.amount,
                reserve_priv: keypair.priv,
                reserve_pub: keypair.pub,
                senderWire: req.senderWire,
                timestamp_confirmed: 0,
                timestamp_depleted: 0,
            };
            const senderWire = req.senderWire;
            if (talerTypes_1.isWireDetail(senderWire)) {
                const rec = {
                    id: helpers_1.hash(senderWire),
                    senderWire,
                };
                yield this.q().put(dbTypes_1.Stores.senderWires, rec).finish();
            }
            yield this.updateExchangeUsedTime(req.exchange);
            const exchangeInfo = yield this.updateExchangeFromUrl(req.exchange);
            const { isAudited, isTrusted } = yield this.getExchangeTrust(exchangeInfo);
            let currencyRecord = yield this.q().get(dbTypes_1.Stores.currencies, exchangeInfo.currency);
            if (!currencyRecord) {
                currencyRecord = {
                    auditors: [],
                    exchanges: [],
                    fractionalDigits: 2,
                    name: exchangeInfo.currency,
                };
            }
            if (!isAudited && !isTrusted) {
                currencyRecord.exchanges.push({ baseUrl: req.exchange, exchangePub: exchangeInfo.masterPublicKey });
            }
            yield this.q()
                .put(dbTypes_1.Stores.currencies, currencyRecord)
                .put(dbTypes_1.Stores.reserves, reserveRecord)
                .finish();
            const r = {
                exchange: canonExchange,
                reservePub: keypair.pub,
            };
            return r;
        });
    }
    /**
     * Mark an existing reserve as confirmed.  The wallet will start trying
     * to withdraw from that reserve.  This may not immediately succeed,
     * since the exchange might not know about the reserve yet, even though the
     * bank confirmed its creation.
     *
     * A confirmed reserve should be shown to the user in the UI, while
     * an unconfirmed reserve should be hidden.
     */
    confirmReserve(req) {
        return __awaiter(this, void 0, void 0, function* () {
            const now = (new Date()).getTime();
            const reserve = yield (this.q().get(dbTypes_1.Stores.reserves, req.reservePub));
            if (!reserve) {
                console.error("Unable to confirm reserve, not found in DB");
                return;
            }
            console.log("reserve confirmed");
            reserve.timestamp_confirmed = now;
            yield this.q()
                .put(dbTypes_1.Stores.reserves, reserve)
                .finish();
            this.notifier.notify();
            this.processReserve(reserve);
        });
    }
    withdrawExecute(pc) {
        return __awaiter(this, void 0, void 0, function* () {
            const wd = {};
            wd.denom_pub = pc.denomPub;
            wd.reserve_pub = pc.reservePub;
            wd.reserve_sig = pc.withdrawSig;
            wd.coin_ev = pc.coinEv;
            const reqUrl = (new URI("reserve/withdraw")).absoluteTo(pc.exchangeBaseUrl);
            const resp = yield this.http.postJson(reqUrl.href(), wd);
            if (resp.status !== 200) {
                throw new http_1.RequestException({
                    hint: "Withdrawal failed",
                    status: resp.status,
                });
            }
            const r = JSON.parse(resp.responseText);
            const denomSig = yield this.cryptoApi.rsaUnblind(r.ev_sig, pc.blindingKey, pc.denomPub);
            const coin = {
                blindingKey: pc.blindingKey,
                coinPriv: pc.coinPriv,
                coinPub: pc.coinPub,
                currentAmount: pc.coinValue,
                denomPub: pc.denomPub,
                denomSig,
                exchangeBaseUrl: pc.exchangeBaseUrl,
                reservePub: pc.reservePub,
                status: pc.isFromTip ? dbTypes_1.CoinStatus.TainedByTip : dbTypes_1.CoinStatus.Fresh,
            };
            return coin;
        });
    }
    /**
     * Withdraw coins from a reserve until it is empty.
     *
     * When finished, marks the reserve as depleted by setting
     * the depleted timestamp.
     */
    depleteReserve(reserve) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("depleting reserve");
            if (!reserve.current_amount) {
                throw Error("can't withdraw when amount is unknown");
            }
            const withdrawAmount = reserve.current_amount;
            if (!withdrawAmount) {
                throw Error("can't withdraw when amount is unknown");
            }
            const denomsForWithdraw = yield this.getVerifiedWithdrawDenomList(reserve.exchange_base_url, withdrawAmount);
            const smallestAmount = yield this.getVerifiedSmallestWithdrawAmount(reserve.exchange_base_url);
            console.log(`withdrawing ${denomsForWithdraw.length} coins`);
            const ps = denomsForWithdraw.map((denom) => __awaiter(this, void 0, void 0, function* () {
                function mutateReserve(r) {
                    const currentAmount = r.current_amount;
                    if (!currentAmount) {
                        throw Error("can't withdraw when amount is unknown");
                    }
                    r.precoin_amount = Amounts.add(r.precoin_amount, denom.value, denom.feeWithdraw).amount;
                    const result = Amounts.sub(currentAmount, denom.value, denom.feeWithdraw);
                    if (result.saturated) {
                        console.error("can't create precoin, saturated");
                        throw query_1.AbortTransaction;
                    }
                    r.current_amount = result.amount;
                    // Reserve is depleted if the amount left is too small to withdraw
                    if (Amounts.cmp(r.current_amount, smallestAmount) < 0) {
                        r.timestamp_depleted = (new Date()).getTime();
                    }
                    console.log(`after creating precoin: current ${helpers_1.amountToPretty(r.current_amount)}, precoin: ${helpers_1.amountToPretty(r.precoin_amount)})}`);
                    return r;
                }
                const preCoin = yield this.cryptoApi
                    .createPreCoin(denom, reserve);
                yield this.q()
                    .put(dbTypes_1.Stores.precoins, preCoin)
                    .mutate(dbTypes_1.Stores.reserves, reserve.reserve_pub, mutateReserve);
                yield this.processPreCoin(preCoin);
            }));
            yield Promise.all(ps);
        });
    }
    /**
     * Update the information about a reserve that is stored in the wallet
     * by quering the reserve's exchange.
     */
    updateReserve(reservePub) {
        return __awaiter(this, void 0, void 0, function* () {
            const reserve = yield this.q()
                .get(dbTypes_1.Stores.reserves, reservePub);
            if (!reserve) {
                throw Error("reserve not in db");
            }
            const reqUrl = new URI("reserve/status").absoluteTo(reserve.exchange_base_url);
            reqUrl.query({ reserve_pub: reservePub });
            const resp = yield this.http.get(reqUrl.href());
            if (resp.status !== 200) {
                throw Error();
            }
            const reserveInfo = talerTypes_1.ReserveStatus.checked(JSON.parse(resp.responseText));
            if (!reserveInfo) {
                throw Error();
            }
            reserve.current_amount = Amounts.parseOrThrow(reserveInfo.balance);
            yield this.q()
                .put(dbTypes_1.Stores.reserves, reserve)
                .finish();
            this.notifier.notify();
            return reserve;
        });
    }
    /**
     * Get the wire information for the exchange with the given base URL.
     */
    getWireInfo(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            exchangeBaseUrl = helpers_1.canonicalizeBaseUrl(exchangeBaseUrl);
            const reqUrl = new URI("wire").absoluteTo(exchangeBaseUrl);
            const resp = yield this.http.get(reqUrl.href());
            if (resp.status !== 200) {
                throw Error("/wire request failed");
            }
            const wiJson = JSON.parse(resp.responseText);
            if (!wiJson) {
                throw Error("/wire response malformed");
            }
            return wiJson;
        });
    }
    getPossibleDenoms(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            return (this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
                .filter((d) => d.status === dbTypes_1.DenominationStatus.Unverified || d.status === dbTypes_1.DenominationStatus.VerifiedGood)
                .toArray());
        });
    }
    /**
     * Compute the smallest withdrawable amount possible, based on verified denominations.
     *
     * Writes to the DB in order to record the result from verifying
     * denominations.
     */
    getVerifiedSmallestWithdrawAmount(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, exchangeBaseUrl);
            if (!exchange) {
                throw Error(`exchange ${exchangeBaseUrl} not found`);
            }
            const possibleDenoms = yield (this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
                .filter((d) => d.status === dbTypes_1.DenominationStatus.Unverified || d.status === dbTypes_1.DenominationStatus.VerifiedGood)
                .toArray());
            possibleDenoms.sort((d1, d2) => {
                const a1 = Amounts.add(d1.feeWithdraw, d1.value).amount;
                const a2 = Amounts.add(d2.feeWithdraw, d2.value).amount;
                return Amounts.cmp(a1, a2);
            });
            for (const denom of possibleDenoms) {
                if (denom.status === dbTypes_1.DenominationStatus.VerifiedGood) {
                    return Amounts.add(denom.feeWithdraw, denom.value).amount;
                }
                console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`);
                const valid = yield this.cryptoApi.isValidDenom(denom, exchange.masterPublicKey);
                if (!valid) {
                    denom.status = dbTypes_1.DenominationStatus.VerifiedBad;
                }
                else {
                    denom.status = dbTypes_1.DenominationStatus.VerifiedGood;
                }
                yield this.q().put(dbTypes_1.Stores.denominations, denom).finish();
                if (valid) {
                    return Amounts.add(denom.feeWithdraw, denom.value).amount;
                }
            }
            return Amounts.getZero(exchange.currency);
        });
    }
    /**
     * Get a list of denominations to withdraw from the given exchange for the
     * given amount, making sure that all denominations' signatures are verified.
     *
     * Writes to the DB in order to record the result from verifying
     * denominations.
     */
    getVerifiedWithdrawDenomList(exchangeBaseUrl, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, exchangeBaseUrl);
            if (!exchange) {
                throw Error(`exchange ${exchangeBaseUrl} not found`);
            }
            const possibleDenoms = yield (this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
                .filter((d) => d.status === dbTypes_1.DenominationStatus.Unverified || d.status === dbTypes_1.DenominationStatus.VerifiedGood)
                .toArray());
            let allValid = false;
            let selectedDenoms;
            do {
                allValid = true;
                const nextPossibleDenoms = [];
                selectedDenoms = getWithdrawDenomList(amount, possibleDenoms);
                for (const denom of selectedDenoms || []) {
                    if (denom.status === dbTypes_1.DenominationStatus.Unverified) {
                        console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`);
                        const valid = yield this.cryptoApi.isValidDenom(denom, exchange.masterPublicKey);
                        if (!valid) {
                            denom.status = dbTypes_1.DenominationStatus.VerifiedBad;
                            allValid = false;
                        }
                        else {
                            denom.status = dbTypes_1.DenominationStatus.VerifiedGood;
                            nextPossibleDenoms.push(denom);
                        }
                        yield this.q().put(dbTypes_1.Stores.denominations, denom).finish();
                    }
                    else {
                        nextPossibleDenoms.push(denom);
                    }
                }
            } while (selectedDenoms.length > 0 && !allValid);
            return selectedDenoms;
        });
    }
    /**
     * Check if and how an exchange is trusted and/or audited.
     */
    getExchangeTrust(exchangeInfo) {
        return __awaiter(this, void 0, void 0, function* () {
            let isTrusted = false;
            let isAudited = false;
            const currencyRecord = yield this.q().get(dbTypes_1.Stores.currencies, exchangeInfo.currency);
            if (currencyRecord) {
                for (const trustedExchange of currencyRecord.exchanges) {
                    if (trustedExchange.exchangePub === exchangeInfo.masterPublicKey) {
                        isTrusted = true;
                        break;
                    }
                }
                for (const trustedAuditor of currencyRecord.auditors) {
                    for (const exchangeAuditor of exchangeInfo.auditors) {
                        if (trustedAuditor.auditorPub === exchangeAuditor.auditor_pub) {
                            isAudited = true;
                            break;
                        }
                    }
                }
            }
            return { isTrusted, isAudited };
        });
    }
    getReserveCreationInfo(baseUrl, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const exchangeInfo = yield this.updateExchangeFromUrl(baseUrl);
            const selectedDenoms = yield this.getVerifiedWithdrawDenomList(baseUrl, amount);
            let acc = Amounts.getZero(amount.currency);
            for (const d of selectedDenoms) {
                acc = Amounts.add(acc, d.feeWithdraw).amount;
            }
            const actualCoinCost = selectedDenoms
                .map((d) => Amounts.add(d.value, d.feeWithdraw).amount)
                .reduce((a, b) => Amounts.add(a, b).amount);
            const wireInfo = yield this.getWireInfo(baseUrl);
            const wireFees = yield this.q().get(dbTypes_1.Stores.exchangeWireFees, baseUrl);
            if (!wireFees) {
                // should never happen unless DB is inconsistent
                throw Error(`no wire fees found for exchange ${baseUrl}`);
            }
            const { isTrusted, isAudited } = yield this.getExchangeTrust(exchangeInfo);
            let earliestDepositExpiration = Infinity;
            for (const denom of selectedDenoms) {
                const expireDeposit = helpers_1.getTalerStampSec(denom.stampExpireDeposit);
                if (expireDeposit < earliestDepositExpiration) {
                    earliestDepositExpiration = expireDeposit;
                }
            }
            const possibleDenoms = (yield (this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, baseUrl)
                .filter((d) => d.isOffered)
                .toArray())) || [];
            const trustedAuditorPubs = [];
            const currencyRecord = yield this.q().get(dbTypes_1.Stores.currencies, amount.currency);
            if (currencyRecord) {
                trustedAuditorPubs.push(...currencyRecord.auditors.map((a) => a.auditorPub));
            }
            let versionMatch;
            if (exchangeInfo.protocolVersion) {
                versionMatch = LibtoolVersion.compare(exports.WALLET_PROTOCOL_VERSION, exchangeInfo.protocolVersion);
                if (versionMatch && !versionMatch.compatible && versionMatch.currentCmp === -1) {
                    console.log("wallet version might be outdated, checking for updates");
                    chrome.runtime.requestUpdateCheck((status, details) => {
                        console.log("update check status:", status);
                    });
                }
            }
            const ret = {
                earliestDepositExpiration,
                exchangeInfo,
                exchangeVersion: exchangeInfo.protocolVersion || "unknown",
                isAudited,
                isTrusted,
                numOfferedDenoms: possibleDenoms.length,
                overhead: Amounts.sub(amount, actualCoinCost).amount,
                selectedDenoms,
                trustedAuditorPubs,
                versionMatch,
                walletVersion: exports.WALLET_PROTOCOL_VERSION,
                wireFees,
                wireInfo,
                withdrawFee: acc,
            };
            return ret;
        });
    }
    /**
     * Update or add exchange DB entry by fetching the /keys information.
     * Optionally link the reserve entry to the new or existing
     * exchange entry in then DB.
     */
    updateExchangeFromUrl(baseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            baseUrl = helpers_1.canonicalizeBaseUrl(baseUrl);
            const keysUrl = new URI("keys").absoluteTo(baseUrl);
            const wireUrl = new URI("wire").absoluteTo(baseUrl);
            const keysResp = yield this.http.get(keysUrl.href());
            if (keysResp.status !== 200) {
                throw Error("/keys request failed");
            }
            const wireResp = yield this.http.get(wireUrl.href());
            if (wireResp.status !== 200) {
                throw Error("/wire request failed");
            }
            const exchangeKeysJson = talerTypes_1.KeysJson.checked(JSON.parse(keysResp.responseText));
            const wireRespJson = JSON.parse(wireResp.responseText);
            if (typeof wireRespJson !== "object") {
                throw Error("/wire response is not an object");
            }
            console.log("exchange wire", wireRespJson);
            const wireMethodDetails = [];
            for (const methodName in wireRespJson) {
                wireMethodDetails.push(talerTypes_1.WireDetailJson.checked(wireRespJson[methodName]));
            }
            return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, wireMethodDetails);
        });
    }
    suspendCoins(exchangeInfo) {
        return __awaiter(this, void 0, void 0, function* () {
            const resultSuspendedCoins = yield (this.q()
                .iterIndex(dbTypes_1.Stores.coins.exchangeBaseUrlIndex, exchangeInfo.baseUrl)
                .indexJoinLeft(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, (e) => e.exchangeBaseUrl)
                .fold((cd, suspendedCoins) => {
                if ((!cd.right) || (!cd.right.isOffered)) {
                    return Array.prototype.concat(suspendedCoins, [cd.left]);
                }
                return Array.prototype.concat(suspendedCoins);
            }, []));
            const q = this.q();
            resultSuspendedCoins.map((c) => {
                console.log("suspending coin", c);
                c.suspended = true;
                q.put(dbTypes_1.Stores.coins, c);
                this.badge.showNotification();
                this.notifier.notify();
            });
            yield q.finish();
        });
    }
    updateExchangeFromJson(baseUrl, exchangeKeysJson, wireMethodDetails) {
        return __awaiter(this, void 0, void 0, function* () {
            // FIXME: all this should probably be commited atomically
            const updateTimeSec = helpers_1.getTalerStampSec(exchangeKeysJson.list_issue_date);
            if (updateTimeSec === null) {
                throw Error("invalid update time");
            }
            if (exchangeKeysJson.denoms.length === 0) {
                throw Error("exchange doesn't offer any denominations");
            }
            const r = yield this.q().get(dbTypes_1.Stores.exchanges, baseUrl);
            let exchangeInfo;
            if (!r) {
                exchangeInfo = {
                    auditors: exchangeKeysJson.auditors,
                    baseUrl,
                    currency: Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value).currency,
                    lastUpdateTime: updateTimeSec,
                    lastUsedTime: 0,
                    masterPublicKey: exchangeKeysJson.master_public_key,
                };
                console.log("making fresh exchange");
            }
            else {
                if (updateTimeSec < r.lastUpdateTime) {
                    console.log("outdated /keys, not updating");
                    return r;
                }
                exchangeInfo = r;
                exchangeInfo.lastUpdateTime = updateTimeSec;
                console.log("updating old exchange");
            }
            const updatedExchangeInfo = yield this.updateExchangeInfo(exchangeInfo, exchangeKeysJson);
            yield this.suspendCoins(updatedExchangeInfo);
            updatedExchangeInfo.protocolVersion = exchangeKeysJson.version;
            yield this.q()
                .put(dbTypes_1.Stores.exchanges, updatedExchangeInfo)
                .finish();
            let oldWireFees = yield this.q().get(dbTypes_1.Stores.exchangeWireFees, baseUrl);
            if (!oldWireFees) {
                oldWireFees = {
                    exchangeBaseUrl: baseUrl,
                    feesForType: {},
                };
            }
            for (const detail of wireMethodDetails) {
                let latestFeeStamp = 0;
                const fees = oldWireFees.feesForType[detail.type] || [];
                oldWireFees.feesForType[detail.type] = fees;
                for (const oldFee of fees) {
                    if (oldFee.endStamp > latestFeeStamp) {
                        latestFeeStamp = oldFee.endStamp;
                    }
                }
                for (const fee of detail.fees) {
                    const start = helpers_1.getTalerStampSec(fee.start_date);
                    if (start === null) {
                        console.error("invalid start stamp in fee", fee);
                        continue;
                    }
                    if (start < latestFeeStamp) {
                        continue;
                    }
                    const end = helpers_1.getTalerStampSec(fee.end_date);
                    if (end === null) {
                        console.error("invalid end stamp in fee", fee);
                        continue;
                    }
                    const wf = {
                        closingFee: Amounts.parseOrThrow(fee.closing_fee),
                        endStamp: end,
                        sig: fee.sig,
                        startStamp: start,
                        wireFee: Amounts.parseOrThrow(fee.wire_fee),
                    };
                    const valid = yield this.cryptoApi.isValidWireFee(detail.type, wf, exchangeInfo.masterPublicKey);
                    if (!valid) {
                        console.error("fee signature invalid", fee);
                        throw Error("fee signature invalid");
                    }
                    fees.push(wf);
                }
            }
            yield this.q().put(dbTypes_1.Stores.exchangeWireFees, oldWireFees);
            if (exchangeKeysJson.payback) {
                for (const payback of exchangeKeysJson.payback) {
                    const denom = yield this.q().getIndexed(dbTypes_1.Stores.denominations.denomPubHashIndex, payback.h_denom_pub);
                    if (!denom) {
                        continue;
                    }
                    console.log(`cashing back denom`, denom);
                    const coins = yield this.q().iterIndex(dbTypes_1.Stores.coins.denomPubIndex, denom.denomPub).toArray();
                    for (const coin of coins) {
                        this.payback(coin.coinPub);
                    }
                }
            }
            return updatedExchangeInfo;
        });
    }
    updateExchangeInfo(exchangeInfo, newKeys) {
        return __awaiter(this, void 0, void 0, function* () {
            if (exchangeInfo.masterPublicKey !== newKeys.master_public_key) {
                throw Error("public keys do not match");
            }
            const existingDenoms = yield (this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchangeInfo.baseUrl)
                .fold((x, acc) => (acc[x.denomPub] = x, acc), {}));
            const newDenoms = {};
            const newAndUnseenDenoms = {};
            for (const d of newKeys.denoms) {
                const dr = yield this.denominationRecordFromKeys(exchangeInfo.baseUrl, d);
                if (!(d.denom_pub in existingDenoms)) {
                    newAndUnseenDenoms[dr.denomPub] = dr;
                }
                newDenoms[dr.denomPub] = dr;
            }
            for (const oldDenomPub in existingDenoms) {
                if (!(oldDenomPub in newDenoms)) {
                    const d = existingDenoms[oldDenomPub];
                    d.isOffered = false;
                }
            }
            yield this.q()
                .putAll(dbTypes_1.Stores.denominations, Object.keys(newAndUnseenDenoms).map((d) => newAndUnseenDenoms[d]))
                .putAll(dbTypes_1.Stores.denominations, Object.keys(existingDenoms).map((d) => existingDenoms[d]))
                .finish();
            return exchangeInfo;
        });
    }
    /**
     * Get detailed balance information, sliced by exchange and by currency.
     */
    getBalances() {
        return __awaiter(this, void 0, void 0, function* () {
            /**
             * Add amount to a balance field, both for
             * the slicing by exchange and currency.
             */
            function addTo(balance, field, amount, exchange) {
                const z = Amounts.getZero(amount.currency);
                const balanceIdentity = { available: z, paybackAmount: z, pendingIncoming: z, pendingPayment: z };
                let entryCurr = balance.byCurrency[amount.currency];
                if (!entryCurr) {
                    balance.byCurrency[amount.currency] = entryCurr = Object.assign({}, balanceIdentity);
                }
                let entryEx = balance.byExchange[exchange];
                if (!entryEx) {
                    balance.byExchange[exchange] = entryEx = Object.assign({}, balanceIdentity);
                }
                entryCurr[field] = Amounts.add(entryCurr[field], amount).amount;
                entryEx[field] = Amounts.add(entryEx[field], amount).amount;
            }
            function collectBalances(c, balance) {
                if (c.suspended) {
                    return balance;
                }
                if (c.status === dbTypes_1.CoinStatus.Fresh) {
                    addTo(balance, "available", c.currentAmount, c.exchangeBaseUrl);
                    return balance;
                }
                if (c.status === dbTypes_1.CoinStatus.Dirty) {
                    addTo(balance, "pendingIncoming", c.currentAmount, c.exchangeBaseUrl);
                    return balance;
                }
                return balance;
            }
            function collectPendingWithdraw(r, balance) {
                if (!r.timestamp_confirmed) {
                    return balance;
                }
                let amount = r.current_amount;
                if (!amount) {
                    amount = r.requested_amount;
                }
                amount = Amounts.add(amount, r.precoin_amount).amount;
                if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
                    addTo(balance, "pendingIncoming", amount, r.exchange_base_url);
                }
                return balance;
            }
            function collectPaybacks(r, balance) {
                if (!r.hasPayback) {
                    return balance;
                }
                if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount) < 0) {
                    addTo(balance, "paybackAmount", r.current_amount, r.exchange_base_url);
                }
                return balance;
            }
            function collectPendingRefresh(r, balance) {
                // Don't count finished refreshes, since the refresh already resulted
                // in coins being added to the wallet.
                if (r.finished) {
                    return balance;
                }
                addTo(balance, "pendingIncoming", r.valueOutput, r.exchangeBaseUrl);
                return balance;
            }
            function collectPayments(t, balance) {
                if (t.finished) {
                    return balance;
                }
                for (const c of t.payReq.coins) {
                    addTo(balance, "pendingPayment", Amounts.parseOrThrow(c.contribution), c.exchange_url);
                }
                return balance;
            }
            function collectSmallestWithdraw(e, sw) {
                let min = sw[e.left.baseUrl];
                const v = Amounts.add(e.right.value, e.right.feeWithdraw).amount;
                if (!min) {
                    min = v;
                }
                else if (Amounts.cmp(v, min) < 0) {
                    min = v;
                }
                sw[e.left.baseUrl] = min;
                return sw;
            }
            const balanceStore = {
                byCurrency: {},
                byExchange: {},
            };
            // Mapping from exchange pub to smallest
            // possible amount we can withdraw
            let smallestWithdraw = {};
            smallestWithdraw = yield (this.q()
                .iter(dbTypes_1.Stores.exchanges)
                .indexJoin(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, (x) => x.baseUrl)
                .fold(collectSmallestWithdraw, {}));
            const tx = this.q();
            tx.iter(dbTypes_1.Stores.coins)
                .fold(collectBalances, balanceStore);
            tx.iter(dbTypes_1.Stores.refresh)
                .fold(collectPendingRefresh, balanceStore);
            tx.iter(dbTypes_1.Stores.reserves)
                .fold(collectPendingWithdraw, balanceStore);
            tx.iter(dbTypes_1.Stores.reserves)
                .fold(collectPaybacks, balanceStore);
            tx.iter(dbTypes_1.Stores.purchases)
                .fold(collectPayments, balanceStore);
            yield tx.finish();
            return balanceStore;
        });
    }
    createRefreshSession(oldCoinPub) {
        return __awaiter(this, void 0, void 0, function* () {
            const coin = yield this.q().get(dbTypes_1.Stores.coins, oldCoinPub);
            if (!coin) {
                throw Error("coin not found");
            }
            if (coin.currentAmount.value === 0 && coin.currentAmount.fraction === 0) {
                return undefined;
            }
            const exchange = yield this.updateExchangeFromUrl(coin.exchangeBaseUrl);
            if (!exchange) {
                throw Error("db inconsistent");
            }
            const oldDenom = yield this.q().get(dbTypes_1.Stores.denominations, [exchange.baseUrl, coin.denomPub]);
            if (!oldDenom) {
                throw Error("db inconsistent");
            }
            const availableDenoms = yield (this.q()
                .iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
                .toArray());
            const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh).amount;
            const newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms);
            console.log("refreshing coin", coin);
            console.log("refreshing into", newCoinDenoms);
            if (newCoinDenoms.length === 0) {
                console.log(`not refreshing, available amount ${helpers_1.amountToPretty(availableAmount)} too small`);
                coin.status = dbTypes_1.CoinStatus.Useless;
                yield this.q().put(dbTypes_1.Stores.coins, coin);
                this.notifier.notify();
                return undefined;
            }
            const refreshSession = yield (this.cryptoApi.createRefreshSession(exchange.baseUrl, 3, coin, newCoinDenoms, oldDenom.feeRefresh));
            function mutateCoin(c) {
                const r = Amounts.sub(c.currentAmount, refreshSession.valueWithFee);
                if (r.saturated) {
                    // Something else must have written the coin value
                    throw query_1.AbortTransaction;
                }
                c.currentAmount = r.amount;
                c.status = dbTypes_1.CoinStatus.Refreshed;
                return c;
            }
            // Store refresh session and subtract refreshed amount from
            // coin in the same transaction.
            const query = this.q();
            query.put(dbTypes_1.Stores.refresh, refreshSession, "refreshKey")
                .mutate(dbTypes_1.Stores.coins, coin.coinPub, mutateCoin);
            yield query.finish();
            this.notifier.notify();
            const key = query.key("refreshKey");
            if (!key || typeof key !== "number") {
                throw Error("insert failed");
            }
            refreshSession.id = key;
            return refreshSession;
        });
    }
    refresh(oldCoinPub) {
        return __awaiter(this, void 0, void 0, function* () {
            const oldRefreshSessions = yield this.q().iter(dbTypes_1.Stores.refresh).toArray();
            for (const session of oldRefreshSessions) {
                console.log("got old session for", oldCoinPub, session);
                this.continueRefreshSession(session);
            }
            const coin = yield this.q().get(dbTypes_1.Stores.coins, oldCoinPub);
            if (!coin) {
                console.warn("can't refresh, coin not in database");
                return;
            }
            if (coin.status === dbTypes_1.CoinStatus.Useless || coin.status === dbTypes_1.CoinStatus.Fresh) {
                return;
            }
            const refreshSession = yield this.createRefreshSession(oldCoinPub);
            if (!refreshSession) {
                // refreshing not necessary
                console.log("not refreshing", oldCoinPub);
                return;
            }
            this.continueRefreshSession(refreshSession);
        });
    }
    continueRefreshSession(refreshSession) {
        return __awaiter(this, void 0, void 0, function* () {
            if (refreshSession.finished) {
                return;
            }
            if (typeof refreshSession.norevealIndex !== "number") {
                yield this.refreshMelt(refreshSession);
                const r = yield this.q().get(dbTypes_1.Stores.refresh, refreshSession.id);
                if (!r) {
                    throw Error("refresh session does not exist anymore");
                }
                refreshSession = r;
            }
            yield this.refreshReveal(refreshSession);
        });
    }
    refreshMelt(refreshSession) {
        return __awaiter(this, void 0, void 0, function* () {
            if (refreshSession.norevealIndex !== undefined) {
                console.error("won't melt again");
                return;
            }
            const coin = yield this.q().get(dbTypes_1.Stores.coins, refreshSession.meltCoinPub);
            if (!coin) {
                console.error("can't melt coin, it does not exist");
                return;
            }
            const reqUrl = new URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl);
            const meltReq = {
                coin_pub: coin.coinPub,
                confirm_sig: refreshSession.confirmSig,
                denom_pub: coin.denomPub,
                denom_sig: coin.denomSig,
                rc: refreshSession.hash,
                value_with_fee: refreshSession.valueWithFee,
            };
            console.log("melt request:", meltReq);
            const resp = yield this.http.postJson(reqUrl.href(), meltReq);
            console.log("melt response:", resp.responseText);
            if (resp.status !== 200) {
                console.error(resp.responseText);
                throw Error("refresh failed");
            }
            const respJson = JSON.parse(resp.responseText);
            if (!respJson) {
                throw Error("exchange responded with garbage");
            }
            const norevealIndex = respJson.noreveal_index;
            if (typeof norevealIndex !== "number") {
                throw Error("invalid response");
            }
            refreshSession.norevealIndex = norevealIndex;
            yield this.q().put(dbTypes_1.Stores.refresh, refreshSession).finish();
            this.notifier.notify();
        });
    }
    refreshReveal(refreshSession) {
        return __awaiter(this, void 0, void 0, function* () {
            const norevealIndex = refreshSession.norevealIndex;
            if (norevealIndex === undefined) {
                throw Error("can't reveal without melting first");
            }
            const privs = Array.from(refreshSession.transferPrivs);
            privs.splice(norevealIndex, 1);
            const preCoins = refreshSession.preCoinsForGammas[norevealIndex];
            if (!preCoins) {
                throw Error("refresh index error");
            }
            const evs = preCoins.map((x) => x.coinEv);
            const req = {
                coin_evs: evs,
                new_denoms_h: refreshSession.newDenomHashes,
                rc: refreshSession.hash,
                transfer_privs: privs,
                transfer_pub: refreshSession.transferPubs[norevealIndex],
            };
            const reqUrl = new URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl);
            console.log("reveal request:", req);
            const resp = yield this.http.postJson(reqUrl.href(), req);
            console.log("session:", refreshSession);
            console.log("reveal response:", resp);
            if (resp.status !== 200) {
                console.log("error:  /refresh/reveal returned status " + resp.status);
                return;
            }
            const respJson = JSON.parse(resp.responseText);
            if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
                console.log("/refresh/reveal did not contain ev_sigs");
            }
            const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, refreshSession.exchangeBaseUrl);
            if (!exchange) {
                console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
                return;
            }
            const coins = [];
            for (let i = 0; i < respJson.ev_sigs.length; i++) {
                const denom = yield (this.q()
                    .get(dbTypes_1.Stores.denominations, [
                    refreshSession.exchangeBaseUrl,
                    refreshSession.newDenoms[i],
                ]));
                if (!denom) {
                    console.error("denom not found");
                    continue;
                }
                const pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex][i];
                const denomSig = yield this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig, pc.blindingKey, denom.denomPub);
                const coin = {
                    blindingKey: pc.blindingKey,
                    coinPriv: pc.privateKey,
                    coinPub: pc.publicKey,
                    currentAmount: denom.value,
                    denomPub: denom.denomPub,
                    denomSig,
                    exchangeBaseUrl: refreshSession.exchangeBaseUrl,
                    reservePub: undefined,
                    status: dbTypes_1.CoinStatus.Fresh,
                };
                coins.push(coin);
            }
            refreshSession.finished = true;
            yield this.q()
                .putAll(dbTypes_1.Stores.coins, coins)
                .put(dbTypes_1.Stores.refresh, refreshSession)
                .finish();
            this.notifier.notify();
        });
    }
    /**
     * Retrive the full event history for this wallet.
     */
    getHistory() {
        return __awaiter(this, void 0, void 0, function* () {
            const history = [];
            // FIXME: do pagination instead of generating the full history
            const proposals = yield this.q().iter(dbTypes_1.Stores.proposals).toArray();
            for (const p of proposals) {
                history.push({
                    detail: {
                        contractTermsHash: p.contractTermsHash,
                        merchantName: p.contractTerms.merchant.name,
                    },
                    timestamp: p.timestamp,
                    type: "offer-contract",
                });
            }
            const purchases = yield this.q().iter(dbTypes_1.Stores.purchases).toArray();
            for (const p of purchases) {
                history.push({
                    detail: {
                        amount: p.contractTerms.amount,
                        contractTermsHash: p.contractTermsHash,
                        fulfillmentUrl: p.contractTerms.fulfillment_url,
                        merchantName: p.contractTerms.merchant.name,
                    },
                    timestamp: p.timestamp,
                    type: "pay",
                });
                if (p.timestamp_refund) {
                    const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount);
                    const amountsPending = (Object.keys(p.refundsPending)
                        .map((x) => Amounts.parseOrThrow(p.refundsPending[x].refund_amount)));
                    const amountsDone = (Object.keys(p.refundsDone)
                        .map((x) => Amounts.parseOrThrow(p.refundsDone[x].refund_amount)));
                    const amounts = amountsPending.concat(amountsDone);
                    const amount = Amounts.add(Amounts.getZero(contractAmount.currency), ...amounts).amount;
                    history.push({
                        detail: {
                            contractTermsHash: p.contractTermsHash,
                            fulfillmentUrl: p.contractTerms.fulfillment_url,
                            merchantName: p.contractTerms.merchant.name,
                            refundAmount: amount,
                        },
                        timestamp: p.timestamp_refund,
                        type: "refund",
                    });
                }
            }
            const reserves = yield this.q().iter(dbTypes_1.Stores.reserves).toArray();
            for (const r of reserves) {
                history.push({
                    detail: {
                        exchangeBaseUrl: r.exchange_base_url,
                        requestedAmount: r.requested_amount,
                        reservePub: r.reserve_pub,
                    },
                    timestamp: r.created,
                    type: "create-reserve",
                });
                if (r.timestamp_depleted) {
                    history.push({
                        detail: {
                            exchangeBaseUrl: r.exchange_base_url,
                            requestedAmount: r.requested_amount,
                            reservePub: r.reserve_pub,
                        },
                        timestamp: r.timestamp_depleted,
                        type: "depleted-reserve",
                    });
                }
            }
            const tips = yield this.q().iter(dbTypes_1.Stores.tips).toArray();
            for (const tip of tips) {
                history.push({
                    detail: {
                        accepted: tip.accepted,
                        amount: tip.amount,
                        merchantDomain: tip.merchantDomain,
                        tipId: tip.tipId,
                    },
                    timestamp: tip.timestamp,
                    type: "tip",
                });
            }
            history.sort((h1, h2) => Math.sign(h1.timestamp - h2.timestamp));
            return { history };
        });
    }
    getDenoms(exchangeUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            const denoms = yield this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, exchangeUrl).toArray();
            return denoms;
        });
    }
    getProposal(proposalId) {
        return __awaiter(this, void 0, void 0, function* () {
            const proposal = yield this.q().get(dbTypes_1.Stores.proposals, proposalId);
            return proposal;
        });
    }
    getExchanges() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q()
                .iter(dbTypes_1.Stores.exchanges)
                .toArray();
        });
    }
    getCurrencies() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q()
                .iter(dbTypes_1.Stores.currencies)
                .toArray();
        });
    }
    updateCurrency(currencyRecord) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("updating currency to", currencyRecord);
            yield this.q()
                .put(dbTypes_1.Stores.currencies, currencyRecord)
                .finish();
            this.notifier.notify();
        });
    }
    getReserves(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q()
                .iter(dbTypes_1.Stores.reserves)
                .filter((r) => r.exchange_base_url === exchangeBaseUrl)
                .toArray();
        });
    }
    getCoins(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q()
                .iter(dbTypes_1.Stores.coins)
                .filter((c) => c.exchangeBaseUrl === exchangeBaseUrl)
                .toArray();
        });
    }
    getPreCoins(exchangeBaseUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q()
                .iter(dbTypes_1.Stores.precoins)
                .filter((c) => c.exchangeBaseUrl === exchangeBaseUrl)
                .toArray();
        });
    }
    hashContract(contract) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.cryptoApi.hashString(helpers_1.canonicalJson(contract));
        });
    }
    getCurrencyRecord(currency) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q().get(dbTypes_1.Stores.currencies, currency);
        });
    }
    payback(coinPub) {
        return __awaiter(this, void 0, void 0, function* () {
            let coin = yield this.q().get(dbTypes_1.Stores.coins, coinPub);
            if (!coin) {
                throw Error(`Coin ${coinPub} not found, can't request payback`);
            }
            const reservePub = coin.reservePub;
            if (!reservePub) {
                throw Error(`Can't request payback for a refreshed coin`);
            }
            const reserve = yield this.q().get(dbTypes_1.Stores.reserves, reservePub);
            if (!reserve) {
                throw Error(`Reserve of coin ${coinPub} not found`);
            }
            switch (coin.status) {
                case dbTypes_1.CoinStatus.Refreshed:
                    throw Error(`Can't do payback for coin ${coinPub} since it's refreshed`);
                case dbTypes_1.CoinStatus.PaybackDone:
                    console.log(`Coin ${coinPub} already payed back`);
                    return;
            }
            coin.status = dbTypes_1.CoinStatus.PaybackPending;
            // Even if we didn't get the payback yet, we suspend withdrawal, since
            // technically we might update reserve status before we get the response
            // from the reserve for the payback request.
            reserve.hasPayback = true;
            yield this.q().put(dbTypes_1.Stores.coins, coin).put(dbTypes_1.Stores.reserves, reserve);
            this.notifier.notify();
            const paybackRequest = yield this.cryptoApi.createPaybackRequest(coin);
            const reqUrl = new URI("payback").absoluteTo(coin.exchangeBaseUrl);
            const resp = yield this.http.postJson(reqUrl.href(), paybackRequest);
            if (resp.status !== 200) {
                throw Error();
            }
            const paybackConfirmation = talerTypes_1.PaybackConfirmation.checked(JSON.parse(resp.responseText));
            if (paybackConfirmation.reserve_pub !== coin.reservePub) {
                throw Error(`Coin's reserve doesn't match reserve on payback`);
            }
            coin = yield this.q().get(dbTypes_1.Stores.coins, coinPub);
            if (!coin) {
                throw Error(`Coin ${coinPub} not found, can't confirm payback`);
            }
            coin.status = dbTypes_1.CoinStatus.PaybackDone;
            yield this.q().put(dbTypes_1.Stores.coins, coin);
            this.notifier.notify();
            yield this.updateReserve(reservePub);
        });
    }
    denominationRecordFromKeys(exchangeBaseUrl, denomIn) {
        return __awaiter(this, void 0, void 0, function* () {
            const denomPubHash = yield this.cryptoApi.hashDenomPub(denomIn.denom_pub);
            const d = {
                denomPub: denomIn.denom_pub,
                denomPubHash,
                exchangeBaseUrl,
                feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit),
                feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh),
                feeRefund: Amounts.parseOrThrow(denomIn.fee_refund),
                feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
                isOffered: true,
                masterSig: denomIn.master_sig,
                stampExpireDeposit: denomIn.stamp_expire_deposit,
                stampExpireLegal: denomIn.stamp_expire_legal,
                stampExpireWithdraw: denomIn.stamp_expire_withdraw,
                stampStart: denomIn.stamp_start,
                status: dbTypes_1.DenominationStatus.Unverified,
                value: Amounts.parseOrThrow(denomIn.value),
            };
            return d;
        });
    }
    withdrawPaybackReserve(reservePub) {
        return __awaiter(this, void 0, void 0, function* () {
            const reserve = yield this.q().get(dbTypes_1.Stores.reserves, reservePub);
            if (!reserve) {
                throw Error(`Reserve ${reservePub} does not exist`);
            }
            reserve.hasPayback = false;
            yield this.q().put(dbTypes_1.Stores.reserves, reserve);
            this.depleteReserve(reserve);
        });
    }
    getPaybackReserves() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.q().iter(dbTypes_1.Stores.reserves).filter((r) => r.hasPayback).toArray();
        });
    }
    /**
     * Stop ongoing processing.
     */
    stop() {
        this.timerGroup.stopCurrentAndFutureTimers();
    }
    getSenderWireInfos() {
        return __awaiter(this, void 0, void 0, function* () {
            const m = {};
            yield this.q().iter(dbTypes_1.Stores.exchangeWireFees).map((x) => {
                const s = m[x.exchangeBaseUrl] = m[x.exchangeBaseUrl] || new Set();
                Object.keys(x.feesForType).map((k) => s.add(k));
            }).run();
            console.log(m);
            const exchangeWireTypes = {};
            Object.keys(m).map((e) => { exchangeWireTypes[e] = Array.from(m[e]); });
            const senderWiresSet = new Set();
            yield this.q().iter(dbTypes_1.Stores.senderWires).map((x) => {
                if (x.senderWire) {
                    senderWiresSet.add(helpers_1.canonicalJson(x.senderWire));
                }
            }).run();
            const senderWires = Array.from(senderWiresSet).map((x) => JSON.parse(x));
            return {
                exchangeWireTypes,
                senderWires,
            };
        });
    }
    /**
     * Trigger paying coins back into the user's account.
     */
    returnCoins(req) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("got returnCoins request", req);
            const wireType = req.senderWire.type;
            console.log("wireType", wireType);
            if (!wireType || typeof wireType !== "string") {
                console.error(`wire type must be a non-empty string, not ${wireType}`);
                return;
            }
            const stampSecNow = Math.floor((new Date()).getTime() / 1000);
            const exchange = yield this.q().get(dbTypes_1.Stores.exchanges, req.exchange);
            if (!exchange) {
                console.error(`Exchange ${req.exchange} not known to the wallet`);
                return;
            }
            console.log("selecting coins for return:", req);
            const cds = yield this.getCoinsForReturn(req.exchange, req.amount);
            console.log(cds);
            if (!cds) {
                throw Error("coin return impossible, can't select coins");
            }
            const { priv, pub } = yield this.cryptoApi.createEddsaKeypair();
            const wireHash = yield this.cryptoApi.hashString(helpers_1.canonicalJson(req.senderWire));
            const contractTerms = {
                H_wire: wireHash,
                amount: Amounts.toString(req.amount),
                auditors: [],
                exchanges: [{ master_pub: exchange.masterPublicKey, url: exchange.baseUrl }],
                extra: {},
                fulfillment_url: "",
                locations: [],
                max_fee: Amounts.toString(req.amount),
                merchant: {},
                merchant_pub: pub,
                order_id: "none",
                pay_deadline: `/Date(${stampSecNow + 60 * 5})/`,
                pay_url: "",
                products: [],
                refund_deadline: `/Date(${stampSecNow + 60 * 5})/`,
                timestamp: `/Date(${stampSecNow})/`,
                wire_method: wireType,
            };
            const contractTermsHash = yield this.cryptoApi.hashString(helpers_1.canonicalJson(contractTerms));
            const payCoinInfo = yield (this.cryptoApi.signDeposit(contractTerms, cds, Amounts.parseOrThrow(contractTerms.amount)));
            console.log("pci", payCoinInfo);
            const coins = payCoinInfo.sigs.map((s) => ({ coinPaySig: s }));
            const coinsReturnRecord = {
                coins,
                contractTerms,
                contractTermsHash,
                exchange: exchange.baseUrl,
                merchantPriv: priv,
                wire: req.senderWire,
            };
            yield this.q()
                .put(dbTypes_1.Stores.coinsReturns, coinsReturnRecord)
                .putAll(dbTypes_1.Stores.coins, payCoinInfo.updatedCoins)
                .finish();
            this.badge.showNotification();
            this.notifier.notify();
            this.depositReturnedCoins(coinsReturnRecord);
        });
    }
    depositReturnedCoins(coinsReturnRecord) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const c of coinsReturnRecord.coins) {
                if (c.depositedSig) {
                    continue;
                }
                const req = {
                    H_wire: coinsReturnRecord.contractTerms.H_wire,
                    coin_pub: c.coinPaySig.coin_pub,
                    coin_sig: c.coinPaySig.coin_sig,
                    contribution: c.coinPaySig.contribution,
                    denom_pub: c.coinPaySig.denom_pub,
                    h_contract_terms: coinsReturnRecord.contractTermsHash,
                    merchant_pub: coinsReturnRecord.contractTerms.merchant_pub,
                    pay_deadline: coinsReturnRecord.contractTerms.pay_deadline,
                    refund_deadline: coinsReturnRecord.contractTerms.refund_deadline,
                    timestamp: coinsReturnRecord.contractTerms.timestamp,
                    ub_sig: c.coinPaySig.ub_sig,
                    wire: coinsReturnRecord.wire,
                    wire_transfer_deadline: coinsReturnRecord.contractTerms.pay_deadline,
                };
                console.log("req", req);
                const reqUrl = (new URI("deposit")).absoluteTo(coinsReturnRecord.exchange);
                const resp = yield this.http.postJson(reqUrl.href(), req);
                if (resp.status !== 200) {
                    console.error("deposit failed due to status code", resp);
                    continue;
                }
                const respJson = JSON.parse(resp.responseText);
                if (respJson.status !== "DEPOSIT_OK") {
                    console.error("deposit failed", resp);
                    continue;
                }
                if (!respJson.sig) {
                    console.error("invalid 'sig' field", resp);
                    continue;
                }
                // FIXME: verify signature
                // For every successful deposit, we replace the old record with an updated one
                const currentCrr = yield this.q().get(dbTypes_1.Stores.coinsReturns, coinsReturnRecord.contractTermsHash);
                if (!currentCrr) {
                    console.error("database inconsistent");
                    continue;
                }
                for (const nc of currentCrr.coins) {
                    if (nc.coinPaySig.coin_pub === c.coinPaySig.coin_pub) {
                        nc.depositedSig = respJson.sig;
                    }
                }
                yield this.q().put(dbTypes_1.Stores.coinsReturns, currentCrr);
                this.notifier.notify();
            }
        });
    }
    acceptRefundResponse(refundResponse) {
        return __awaiter(this, void 0, void 0, function* () {
            const refundPermissions = refundResponse.refund_permissions;
            if (!refundPermissions.length) {
                console.warn("got empty refund list");
                throw Error("empty refund");
            }
            /**
             * Add refund to purchase if not already added.
             */
            function f(t) {
                if (!t) {
                    console.error("purchase not found, not adding refunds");
                    return;
                }
                t.timestamp_refund = (new Date()).getTime();
                for (const perm of refundPermissions) {
                    if (!t.refundsPending[perm.merchant_sig] && !t.refundsDone[perm.merchant_sig]) {
                        t.refundsPending[perm.merchant_sig] = perm;
                    }
                }
                return t;
            }
            const hc = refundResponse.h_contract_terms;
            // Add the refund permissions to the purchase within a DB transaction
            yield this.q().mutate(dbTypes_1.Stores.purchases, hc, f).finish();
            this.notifier.notify();
            // Start submitting it but don't wait for it here.
            this.submitRefunds(hc);
            return hc;
        });
    }
    /**
     * Accept a refund, return the contract hash for the contract
     * that was involved in the refund.
     */
    acceptRefund(refundUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("processing refund");
            let resp;
            try {
                const config = {
                    validateStatus: (s) => s === 200,
                };
                resp = yield axios_1.default.get(refundUrl, config);
            }
            catch (e) {
                console.log("error downloading refund permission", e);
                throw e;
            }
            const refundResponse = talerTypes_1.MerchantRefundResponse.checked(resp.data);
            return this.acceptRefundResponse(refundResponse);
        });
    }
    submitRefunds(contractTermsHash) {
        return __awaiter(this, void 0, void 0, function* () {
            const purchase = yield this.q().get(dbTypes_1.Stores.purchases, contractTermsHash);
            if (!purchase) {
                console.error("not submitting refunds, contract terms not found:", contractTermsHash);
                return;
            }
            const pendingKeys = Object.keys(purchase.refundsPending);
            if (pendingKeys.length === 0) {
                return;
            }
            for (const pk of pendingKeys) {
                const perm = purchase.refundsPending[pk];
                const req = {
                    coin_pub: perm.coin_pub,
                    h_contract_terms: purchase.contractTermsHash,
                    merchant_pub: purchase.contractTerms.merchant_pub,
                    merchant_sig: perm.merchant_sig,
                    refund_amount: perm.refund_amount,
                    refund_fee: perm.refund_fee,
                    rtransaction_id: perm.rtransaction_id,
                };
                console.log("sending refund permission", perm);
                // FIXME: not correct once we support multiple exchanges per payment
                const exchangeUrl = purchase.payReq.coins[0].exchange_url;
                const reqUrl = (new URI("refund")).absoluteTo(exchangeUrl);
                const resp = yield this.http.postJson(reqUrl.href(), req);
                if (resp.status !== 200) {
                    console.error("refund failed", resp);
                    continue;
                }
                // Transactionally mark successful refunds as done
                const transformPurchase = (t) => {
                    if (!t) {
                        console.warn("purchase not found, not updating refund");
                        return;
                    }
                    if (t.refundsPending[pk]) {
                        t.refundsDone[pk] = t.refundsPending[pk];
                        delete t.refundsPending[pk];
                    }
                    return t;
                };
                const transformCoin = (c) => {
                    if (!c) {
                        console.warn("coin not found, can't apply refund");
                        return;
                    }
                    const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
                    const refundFee = Amounts.parseOrThrow(perm.refund_fee);
                    c.status = dbTypes_1.CoinStatus.Dirty;
                    c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
                    c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
                    return c;
                };
                yield this.q()
                    .mutate(dbTypes_1.Stores.purchases, contractTermsHash, transformPurchase)
                    .mutate(dbTypes_1.Stores.coins, perm.coin_pub, transformCoin)
                    .finish();
                this.refresh(perm.coin_pub);
            }
            this.badge.showNotification();
            this.notifier.notify();
        });
    }
    getPurchase(contractTermsHash) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.q().get(dbTypes_1.Stores.purchases, contractTermsHash);
        });
    }
    getFullRefundFees(refundPermissions) {
        return __awaiter(this, void 0, void 0, function* () {
            if (refundPermissions.length === 0) {
                throw Error("no refunds given");
            }
            const coin0 = yield this.q().get(dbTypes_1.Stores.coins, refundPermissions[0].coin_pub);
            if (!coin0) {
                throw Error("coin not found");
            }
            let feeAcc = Amounts.getZero(Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency);
            const denoms = yield this.q().iterIndex(dbTypes_1.Stores.denominations.exchangeBaseUrlIndex, coin0.exchangeBaseUrl).toArray();
            for (const rp of refundPermissions) {
                const coin = yield this.q().get(dbTypes_1.Stores.coins, rp.coin_pub);
                if (!coin) {
                    throw Error("coin not found");
                }
                const denom = yield this.q().get(dbTypes_1.Stores.denominations, [coin0.exchangeBaseUrl, coin.denomPub]);
                if (!denom) {
                    throw Error(`denom not found (${coin.denomPub})`);
                }
                // FIXME:  this assumes that the refund already happened.
                // When it hasn't, the refresh cost is inaccurate.  To fix this,
                // we need introduce a flag to tell if a coin was refunded or
                // refreshed normally (and what about incremental refunds?)
                const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
                const refundFee = Amounts.parseOrThrow(rp.refund_fee);
                const refreshCost = getTotalRefreshCost(denoms, denom, Amounts.sub(refundAmount, refundFee).amount);
                feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
            }
            return feeAcc;
        });
    }
    processTip(tipToken) {
        return __awaiter(this, void 0, void 0, function* () {
            const merchantDomain = new URI(tipToken.pickup_url).origin();
            const key = tipToken.tip_id + merchantDomain;
            if (this.activeTipOperations[key]) {
                return this.activeTipOperations[key];
            }
            const p = this.processTipImpl(tipToken);
            this.activeTipOperations[key] = p;
            try {
                return yield p;
            }
            finally {
                delete this.activeTipOperations[key];
            }
        });
    }
    processTipImpl(tipToken) {
        return __awaiter(this, void 0, void 0, function* () {
            console.log("got tip token", tipToken);
            const merchantDomain = new URI(tipToken.pickup_url).origin();
            const deadlineSec = helpers_1.getTalerStampSec(tipToken.expiration);
            if (!deadlineSec) {
                throw Error("tipping failed (invalid expiration)");
            }
            let tipRecord = yield this.q().get(dbTypes_1.Stores.tips, [tipToken.tip_id, merchantDomain]);
            if (tipRecord && tipRecord.pickedUp) {
                return tipRecord;
            }
            const tipAmount = Amounts.parseOrThrow(tipToken.amount);
            yield this.updateExchangeFromUrl(tipToken.exchange_url);
            const denomsForWithdraw = yield this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipAmount);
            const planchets = yield Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)));
            const coinPubs = planchets.map(x => x.coinPub);
            const now = (new Date()).getTime();
            tipRecord = {
                accepted: false,
                amount: Amounts.parseOrThrow(tipToken.amount),
                coinPubs,
                deadline: deadlineSec,
                exchangeUrl: tipToken.exchange_url,
                merchantDomain,
                nextUrl: tipToken.next_url,
                pickedUp: false,
                planchets,
                timestamp: now,
                tipId: tipToken.tip_id,
            };
            let merchantResp;
            tipRecord = yield this.q().putOrGetExisting(dbTypes_1.Stores.tips, tipRecord, [tipRecord.tipId, merchantDomain]);
            this.notifier.notify();
            // Planchets in the form that the merchant expects
            const planchetsDetail = tipRecord.planchets.map((p) => ({
                coin_ev: p.coinEv,
                denom_pub_hash: p.denomPubHash,
            }));
            try {
                const config = {
                    validateStatus: (s) => s === 200,
                };
                const req = { planchets: planchetsDetail, tip_id: tipToken.tip_id };
                merchantResp = yield axios_1.default.post(tipToken.pickup_url, req, config);
            }
            catch (e) {
                console.log("tipping failed", e);
                throw e;
            }
            const response = talerTypes_1.TipResponse.checked(merchantResp.data);
            if (response.reserve_sigs.length !== tipRecord.planchets.length) {
                throw Error("number of tip responses does not match requested planchets");
            }
            for (let i = 0; i < tipRecord.planchets.length; i++) {
                const planchet = tipRecord.planchets[i];
                const preCoin = {
                    blindingKey: planchet.blindingKey,
                    coinEv: planchet.coinEv,
                    coinPriv: planchet.coinPriv,
                    coinPub: planchet.coinPub,
                    coinValue: planchet.coinValue,
                    denomPub: planchet.denomPub,
                    exchangeBaseUrl: tipRecord.exchangeUrl,
                    isFromTip: true,
                    reservePub: response.reserve_pub,
                    withdrawSig: response.reserve_sigs[i].reserve_sig,
                };
                yield this.q().put(dbTypes_1.Stores.precoins, preCoin);
                this.processPreCoin(preCoin);
            }
            tipRecord.pickedUp = true;
            yield this.q().put(dbTypes_1.Stores.tips, tipRecord).finish();
            this.notifier.notify();
            return tipRecord;
        });
    }
    /**
     * Start using the coins from a tip.
     */
    acceptTip(tipToken) {
        return __awaiter(this, void 0, void 0, function* () {
            const tipId = tipToken.tip_id;
            const merchantDomain = new URI(tipToken.pickup_url).origin();
            const tipRecord = yield this.q().get(dbTypes_1.Stores.tips, [tipId, merchantDomain]);
            if (!tipRecord) {
                throw Error("tip not found");
            }
            tipRecord.accepted = true;
            // Create one transactional query, within this transaction
            // both the tip will be marked as accepted and coins
            // already withdrawn will be untainted.
            const q = this.q();
            q.put(dbTypes_1.Stores.tips, tipRecord);
            const updateCoin = (c) => {
                if (c.status === dbTypes_1.CoinStatus.TainedByTip) {
                    c.status = dbTypes_1.CoinStatus.Fresh;
                }
                return c;
            };
            for (const coinPub of tipRecord.coinPubs) {
                q.mutate(dbTypes_1.Stores.coins, coinPub, updateCoin);
            }
            yield q.finish();
            this.badge.showNotification();
            this.notifier.notify();
        });
    }
    getTipStatus(tipToken) {
        return __awaiter(this, void 0, void 0, function* () {
            const tipId = tipToken.tip_id;
            const merchantDomain = new URI(tipToken.pickup_url).origin();
            let tipRecord = yield this.q().get(dbTypes_1.Stores.tips, [tipId, merchantDomain]);
            const amount = Amounts.parseOrThrow(tipToken.amount);
            const exchangeUrl = tipToken.exchange_url;
            this.processTip(tipToken);
            const nextUrl = tipToken.next_url;
            const tipStatus = {
                accepted: !!tipRecord && tipRecord.accepted,
                amount,
                exchangeUrl,
                merchantDomain,
                nextUrl,
                tipRecord,
            };
            return tipStatus;
        });
    }
    abortFailedPayment(contractTermsHash) {
        return __awaiter(this, void 0, void 0, function* () {
            const purchase = yield this.q().get(dbTypes_1.Stores.purchases, contractTermsHash);
            if (!purchase) {
                throw Error("Purchase not found, unable to abort with refund");
            }
            if (purchase.finished) {
                throw Error("Purchase already finished, not aborting");
            }
            if (purchase.abortDone) {
                console.warn("abort requested on already aborted purchase");
                return;
            }
            purchase.abortRequested = true;
            // From now on, we can't retry payment anymore,
            // so mark this in the DB in case the /pay abort
            // does not complete on the first try.
            yield this.q().put(dbTypes_1.Stores.purchases, purchase);
            let resp;
            const abortReq = Object.assign({}, purchase.payReq, { mode: "abort-refund" });
            try {
                const config = {
                    headers: { "Content-Type": "application/json;charset=UTF-8" },
                    timeout: 5000,
                    validateStatus: (s) => s === 200,
                };
                resp = yield axios_1.default.post(purchase.contractTerms.pay_url, abortReq, config);
            }
            catch (e) {
                // Gives the user the option to retry / abort and refresh
                console.log("aborting payment failed", e);
                throw e;
            }
            const refundResponse = talerTypes_1.MerchantRefundResponse.checked(resp.data);
            yield this.acceptRefundResponse(refundResponse);
            const markAbortDone = (p) => {
                p.abortDone = true;
                return p;
            };
            yield this.q().mutate(dbTypes_1.Stores.purchases, purchase.contractTermsHash, markAbortDone);
        });
    }
    /**
     * Synchronously get the paid URL for a resource from the plain fulfillment
     * URL.  Returns undefined if the fulfillment URL is not a resource that was
     * payed for, or if it is not cached anymore.  Use the asynchronous
     * queryPaymentByFulfillmentUrl to avoid false negatives.
     */
    getNextUrlFromResourceUrl(resourceUrl) {
        return this.cachedNextUrl[resourceUrl];
    }
    /**
     * Remove unreferenced / expired data from the wallet's database
     * based on the current system time.
     */
    collectGarbage() {
        return __awaiter(this, void 0, void 0, function* () {
            const nowMilli = (new Date()).getTime();
            const nowSec = Math.floor(nowMilli / 1000);
            const gcReserve = (r, n) => {
                // This rule to purge reserves is a bit over-eager, since we still might
                // receive an emergency payback from the exchange.  In this case we need
                // to wait for the exchange to wire the money back or change this rule to
                // wait until all coins from the reserve were spent.
                if (r.timestamp_depleted) {
                    return true;
                }
                return false;
            };
            yield this.q().deleteIf(dbTypes_1.Stores.reserves, gcReserve).finish();
            const gcProposal = (d, n) => {
                // Delete proposal after 60 minutes or 5 minutes before pay deadline,
                // whatever comes first.
                const deadlinePayMilli = helpers_1.getTalerStampSec(d.contractTerms.pay_deadline) * 1000;
                const deadlineExpireMilli = nowMilli + (1000 * 60 * 60);
                return d.timestamp < Math.min(deadlinePayMilli, deadlineExpireMilli);
            };
            yield this.q().deleteIf(dbTypes_1.Stores.proposals, gcProposal).finish();
            const activeExchanges = [];
            const gcExchange = (d, n) => {
                // Delete if if unused and last update more than 20 minutes ago
                if (!d.lastUsedTime && nowMilli > d.lastUpdateTime + (1000 * 60 * 20)) {
                    return true;
                }
                activeExchanges.push(d.baseUrl);
                return false;
            };
            yield this.q().deleteIf(dbTypes_1.Stores.exchanges, gcExchange).finish();
            const gcDenominations = (d, n) => {
                if (nowSec > helpers_1.getTalerStampSec(d.stampExpireDeposit)) {
                    return true;
                }
                if (activeExchanges.indexOf(d.exchangeBaseUrl) < 0) {
                    return true;
                }
                return false;
            };
            yield this.q().deleteIf(dbTypes_1.Stores.denominations, gcDenominations).finish();
            const gcWireFees = (r, n) => {
                if (activeExchanges.indexOf(r.exchangeBaseUrl) < 0) {
                    return true;
                }
                return false;
            };
            yield this.q().deleteIf(dbTypes_1.Stores.exchangeWireFees, gcWireFees).finish();
            // FIXME(#5210) also GC coins
        });
    }
    clearNotification() {
        this.badge.clearNotification();
    }
}
exports.Wallet = Wallet;


/***/ }),
/* 26 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
const timer = __webpack_require__(9);
const startWorker_1 = __webpack_require__(27);
/**
 * Number of different priorities. Each priority p
 * must be 0 <= p < NUM_PRIO.
 */
const NUM_PRIO = 5;
/**
 * Crypto API that interfaces manages a background crypto thread
 * for the execution of expensive operations.
 */
class CryptoApi {
    constructor() {
        this.nextRpcId = 1;
        /**
         * Number of busy workers.
         */
        this.numBusy = 0;
        let concurrency = 2;
        try {
            // only works in the browser
            // tslint:disable-next-line:no-string-literal
            concurrency = navigator["hardwareConcurrency"];
            concurrency = Math.max(1, Math.ceil(concurrency / 2));
        }
        catch (e) {
            // ignore
        }
        this.workers = new Array(concurrency);
        for (let i = 0; i < this.workers.length; i++) {
            this.workers[i] = {
                currentWorkItem: null,
                terminationTimerHandle: null,
                w: null,
            };
        }
        this.workQueues = [];
        for (let i = 0; i < NUM_PRIO; i++) {
            this.workQueues.push([]);
        }
    }
    /**
     * Start a worker (if not started) and set as busy.
     */
    wake(ws, work) {
        if (ws.currentWorkItem !== null) {
            throw Error("assertion failed");
        }
        ws.currentWorkItem = work;
        this.numBusy++;
        if (!ws.w) {
            const w = startWorker_1.startWorker();
            w.onmessage = (m) => this.handleWorkerMessage(ws, m);
            w.onerror = (e) => this.handleWorkerError(ws, e);
            ws.w = w;
        }
        const msg = {
            args: work.args,
            id: work.rpcId,
            operation: work.operation,
        };
        this.resetWorkerTimeout(ws);
        work.startTime = timer.performanceNow();
        ws.w.postMessage(msg);
    }
    resetWorkerTimeout(ws) {
        if (ws.terminationTimerHandle !== null) {
            ws.terminationTimerHandle.clear();
            ws.terminationTimerHandle = null;
        }
        const destroy = () => {
            // terminate worker if it's idle
            if (ws.w && ws.currentWorkItem === null) {
                ws.w.terminate();
                ws.w = null;
            }
        };
        ws.terminationTimerHandle = timer.after(20 * 1000, destroy);
    }
    handleWorkerError(ws, e) {
        if (ws.currentWorkItem) {
            console.error(`error in worker during ${ws.currentWorkItem.operation}`, e);
        }
        else {
            console.error("error in worker", e);
        }
        console.error(e.message);
        try {
            ws.w.terminate();
            ws.w = null;
        }
        catch (e) {
            console.error(e);
        }
        if (ws.currentWorkItem !== null) {
            ws.currentWorkItem.reject(e);
            ws.currentWorkItem = null;
            this.numBusy--;
        }
        this.findWork(ws);
    }
    findWork(ws) {
        // try to find more work for this worker
        for (let i = 0; i < NUM_PRIO; i++) {
            const q = this.workQueues[NUM_PRIO - i - 1];
            if (q.length !== 0) {
                const work = q.shift();
                this.wake(ws, work);
                return;
            }
        }
    }
    handleWorkerMessage(ws, msg) {
        const id = msg.data.id;
        if (typeof id !== "number") {
            console.error("rpc id must be number");
            return;
        }
        const currentWorkItem = ws.currentWorkItem;
        ws.currentWorkItem = null;
        this.numBusy--;
        this.findWork(ws);
        if (!currentWorkItem) {
            console.error("unsolicited response from worker");
            return;
        }
        if (id !== currentWorkItem.rpcId) {
            console.error(`RPC with id ${id} has no registry entry`);
            return;
        }
        console.log(`rpc ${currentWorkItem.operation} took ${timer.performanceNow() - currentWorkItem.startTime}ms`);
        currentWorkItem.resolve(msg.data.result);
    }
    doRpc(operation, priority, ...args) {
        const p = new Promise((resolve, reject) => {
            const rpcId = this.nextRpcId++;
            const workItem = { operation, args, resolve, reject, rpcId, startTime: 0 };
            if (this.numBusy === this.workers.length) {
                const q = this.workQueues[priority];
                if (!q) {
                    throw Error("assertion failed");
                }
                this.workQueues[priority].push(workItem);
                return;
            }
            for (const ws of this.workers) {
                if (ws.currentWorkItem !== null) {
                    continue;
                }
                this.wake(ws, workItem);
                return;
            }
            throw Error("assertion failed");
        });
        return p.then((r) => {
            return r;
        });
    }
    createPreCoin(denom, reserve) {
        return this.doRpc("createPreCoin", 1, denom, reserve);
    }
    createTipPlanchet(denom) {
        return this.doRpc("createTipPlanchet", 1, denom);
    }
    hashString(str) {
        return this.doRpc("hashString", 1, str);
    }
    hashDenomPub(denomPub) {
        return this.doRpc("hashDenomPub", 1, denomPub);
    }
    isValidDenom(denom, masterPub) {
        return this.doRpc("isValidDenom", 2, denom, masterPub);
    }
    isValidWireFee(type, wf, masterPub) {
        return this.doRpc("isValidWireFee", 2, type, wf, masterPub);
    }
    isValidPaymentSignature(sig, contractHash, merchantPub) {
        return this.doRpc("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
    }
    signDeposit(contractTerms, cds, totalAmount) {
        return this.doRpc("signDeposit", 3, contractTerms, cds, totalAmount);
    }
    createEddsaKeypair() {
        return this.doRpc("createEddsaKeypair", 1);
    }
    rsaUnblind(sig, bk, pk) {
        return this.doRpc("rsaUnblind", 4, sig, bk, pk);
    }
    createPaybackRequest(coin) {
        return this.doRpc("createPaybackRequest", 1, coin);
    }
    createRefreshSession(exchangeBaseUrl, kappa, meltCoin, newCoinDenoms, meltFee) {
        return this.doRpc("createRefreshSession", 4, exchangeBaseUrl, kappa, meltCoin, newCoinDenoms, meltFee);
    }
}
exports.CryptoApi = CryptoApi;


/***/ }),
/* 27 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* WEBPACK VAR INJECTION */(function(__dirname) {/* harmony export (immutable) */ __webpack_exports__["startWorker"] = startWorker;
/*
 This file is part of TALER
 (C) 2017 Inria and 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/>
 */


// @ts-nocheck


/**
 * Start a crypto worker, using different worker
 * mechanisms depending on the environment.
 *
 * @returns {Worker}
 */
function startWorker() {
  let workerCtor;
  let workerPath;
  if (typeof Worker !== "undefined") {
    // we're in the browser
    workerCtor = Worker;
    workerPath = "/dist/cryptoWorker-bundle.js";
  } else if (true) {
    workerCtor = __webpack_require__(28).Worker;
    workerPath = __dirname + "/cryptoWorker.js";
  } else {
    throw Error("Can't create worker, unknown environment");
  }
  return new workerCtor(workerPath);
}

/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, "/"))

/***/ }),
/* 28 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
/* WEBPACK VAR INJECTION */(function(__dirname, process) {
/*
 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, see <http://www.gnu.org/licenses/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
// tslint:disable:no-var-requires
const path = __webpack_require__(29);
const fork = __webpack_require__(30).fork;
const nodeWorkerEntry = path.join(__dirname, "nodeWorkerEntry.js");
/**
 * Worker implementation that uses node subprocesses.
 */
class Worker {
    constructor(scriptFilename) {
        this.child = fork(nodeWorkerEntry);
        this.onerror = undefined;
        this.onmessage = undefined;
        this.child.on("error", (e) => {
            if (this.onerror) {
                this.onerror(e);
            }
        });
        this.child.on("message", (msg) => {
            const message = JSON.parse(msg);
            if (!message.error && this.onmessage) {
                this.onmessage(message);
            }
            if (message.error && this.onerror) {
                const error = new Error(message.error);
                error.stack = message.stack;
                this.onerror(error);
            }
        });
        this.child.send({ scriptFilename, cwd: process.cwd() });
    }
    /**
     * Add an event listener for either an "error" or "message" event.
     */
    addEventListener(event, fn) {
        switch (event) {
            case "message":
                this.onmessage = fn;
                break;
            case "error":
                this.onerror = fn;
                break;
        }
    }
    /**
     * Send a message to the worker thread.
     */
    postMessage(msg) {
        this.child.send(JSON.stringify({ data: msg }));
    }
    /**
     * Forcibly terminate the worker thread.
     */
    terminate() {
        this.child.kill("SIGINT");
    }
}
exports.Worker = Worker;

/* WEBPACK VAR INJECTION */}.call(exports, "/", __webpack_require__(4)))

/***/ }),
/* 29 */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors.
//
// 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.

// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
  // if the path tries to go above the root, `up` ends up > 0
  var up = 0;
  for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last === '.') {
      parts.splice(i, 1);
    } else if (last === '..') {
      parts.splice(i, 1);
      up++;
    } else if (up) {
      parts.splice(i, 1);
      up--;
    }
  }

  // if the path is allowed to go above the root, restore leading ..s
  if (allowAboveRoot) {
    for (; up--; up) {
      parts.unshift('..');
    }
  }

  return parts;
}

// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
var splitPathRe =
    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
var splitPath = function(filename) {
  return splitPathRe.exec(filename).slice(1);
};

// path.resolve([from ...], to)
// posix version
exports.resolve = function() {
  var resolvedPath = '',
      resolvedAbsolute = false;

  for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    var path = (i >= 0) ? arguments[i] : process.cwd();

    // Skip empty and invalid entries
    if (typeof path !== 'string') {
      throw new TypeError('Arguments to path.resolve must be strings');
    } else if (!path) {
      continue;
    }

    resolvedPath = path + '/' + resolvedPath;
    resolvedAbsolute = path.charAt(0) === '/';
  }

  // At this point the path should be resolved to a full absolute path, but
  // handle relative paths to be safe (might happen when process.cwd() fails)

  // Normalize the path
  resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
    return !!p;
  }), !resolvedAbsolute).join('/');

  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};

// path.normalize(path)
// posix version
exports.normalize = function(path) {
  var isAbsolute = exports.isAbsolute(path),
      trailingSlash = substr(path, -1) === '/';

  // Normalize the path
  path = normalizeArray(filter(path.split('/'), function(p) {
    return !!p;
  }), !isAbsolute).join('/');

  if (!path && !isAbsolute) {
    path = '.';
  }
  if (path && trailingSlash) {
    path += '/';
  }

  return (isAbsolute ? '/' : '') + path;
};

// posix version
exports.isAbsolute = function(path) {
  return path.charAt(0) === '/';
};

// posix version
exports.join = function() {
  var paths = Array.prototype.slice.call(arguments, 0);
  return exports.normalize(filter(paths, function(p, index) {
    if (typeof p !== 'string') {
      throw new TypeError('Arguments to path.join must be strings');
    }
    return p;
  }).join('/'));
};


// path.relative(from, to)
// posix version
exports.relative = function(from, to) {
  from = exports.resolve(from).substr(1);
  to = exports.resolve(to).substr(1);

  function trim(arr) {
    var start = 0;
    for (; start < arr.length; start++) {
      if (arr[start] !== '') break;
    }

    var end = arr.length - 1;
    for (; end >= 0; end--) {
      if (arr[end] !== '') break;
    }

    if (start > end) return [];
    return arr.slice(start, end - start + 1);
  }

  var fromParts = trim(from.split('/'));
  var toParts = trim(to.split('/'));

  var length = Math.min(fromParts.length, toParts.length);
  var samePartsLength = length;
  for (var i = 0; i < length; i++) {
    if (fromParts[i] !== toParts[i]) {
      samePartsLength = i;
      break;
    }
  }

  var outputParts = [];
  for (var i = samePartsLength; i < fromParts.length; i++) {
    outputParts.push('..');
  }

  outputParts = outputParts.concat(toParts.slice(samePartsLength));

  return outputParts.join('/');
};

exports.sep = '/';
exports.delimiter = ':';

exports.dirname = function(path) {
  var result = splitPath(path),
      root = result[0],
      dir = result[1];

  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
  }

  return root + dir;
};


exports.basename = function(path, ext) {
  var f = splitPath(path)[2];
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};


exports.extname = function(path) {
  return splitPath(path)[3];
};

function filter (xs, f) {
    if (xs.filter) return xs.filter(f);
    var res = [];
    for (var i = 0; i < xs.length; i++) {
        if (f(xs[i], i, xs)) res.push(xs[i]);
    }
    return res;
}

// String.prototype.substr - negative index don't work in IE8
var substr = 'ab'.substr(-1) === 'b'
    ? function (str, start, len) { return str.substr(start, len) }
    : function (str, start, len) {
        if (start < 0) start = str.length + start;
        return str.substr(start, len);
    }
;

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))

/***/ }),
/* 30 */
/***/ (function(module, exports) {

module.exports = child_process;

/***/ }),
/* 31 */
/***/ (function(module, exports) {

module.exports = function(module) {
	if(!module.webpackPolyfill) {
		module.deprecate = function() {};
		module.paths = [];
		// module.parent = undefined by default
		if(!module.children) module.children = [];
		Object.defineProperty(module, "loaded", {
			enumerable: true,
			get: function() {
				return module.l;
			}
		});
		Object.defineProperty(module, "id", {
			enumerable: true,
			get: function() {
				return module.i;
			}
		});
		module.webpackPolyfill = 1;
	}
	return module;
};


/***/ }),
/* 32 */
/***/ (function(module, exports) {

var g;

// This works in non-strict mode
g = (function() {
	return this;
})();

try {
	// This works if eval is allowed (see CSP)
	g = g || Function("return this")() || (1,eval)("this");
} catch(e) {
	// This works if the window reference is available
	if(typeof window === "object")
		g = window;
}

// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}

module.exports = g;


/***/ }),
/* 33 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2017 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/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Compare two libtool-style version strings.
 */
function compare(me, other) {
    const meVer = parseVersion(me);
    const otherVer = parseVersion(other);
    if (!(meVer && otherVer)) {
        return undefined;
    }
    const compatible = (meVer.current - meVer.age <= otherVer.current &&
        meVer.current >= (otherVer.current - otherVer.age));
    const currentCmp = Math.sign(meVer.current - otherVer.current);
    return { compatible, currentCmp };
}
exports.compare = compare;
function parseVersion(v) {
    const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
    if (rest.length !== 0) {
        return undefined;
    }
    const current = Number.parseInt(currentStr);
    const revision = Number.parseInt(revisionStr);
    const age = Number.parseInt(ageStr);
    if (Number.isNaN(current)) {
        return undefined;
    }
    if (Number.isNaN(revision)) {
        return undefined;
    }
    if (Number.isNaN(age)) {
        return undefined;
    }
    return { current, revision, age };
}


/***/ }),
/* 34 */
/***/ (function(module, exports, __webpack_require__) {

module.exports = __webpack_require__(35);

/***/ }),
/* 35 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);
var bind = __webpack_require__(14);
var Axios = __webpack_require__(37);
var defaults = __webpack_require__(6);

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = __webpack_require__(18);
axios.CancelToken = __webpack_require__(51);
axios.isCancel = __webpack_require__(17);

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = __webpack_require__(52);

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;


/***/ }),
/* 36 */
/***/ (function(module, exports) {

/*!
 * Determine if an object is a Buffer
 *
 * @author   Feross Aboukhadijeh <https://feross.org>
 * @license  MIT
 */

// The _isBuffer check is for Safari 5-7 support, because it's missing
// Object.prototype.constructor. Remove this eventually
module.exports = function (obj) {
  return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer)
}

function isBuffer (obj) {
  return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)
}

// For Node v0.10 support. Remove this eventually.
function isSlowBuffer (obj) {
  return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0))
}


/***/ }),
/* 37 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var defaults = __webpack_require__(6);
var utils = __webpack_require__(0);
var InterceptorManager = __webpack_require__(46);
var dispatchRequest = __webpack_require__(47);
var isAbsoluteURL = __webpack_require__(49);
var combineURLs = __webpack_require__(50);

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = utils.merge({
      url: arguments[0]
    }, arguments[1]);
  }

  config = utils.merge(defaults, this.defaults, { method: 'get' }, config);
  config.method = config.method.toLowerCase();

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;


/***/ }),
/* 38 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

module.exports = function normalizeHeaderName(headers, normalizedName) {
  utils.forEach(headers, function processHeader(value, name) {
    if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
      headers[normalizedName] = value;
      delete headers[name];
    }
  });
};


/***/ }),
/* 39 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var createError = __webpack_require__(16);

/**
 * Resolve or reject a Promise based on response status.
 *
 * @param {Function} resolve A function that resolves the promise.
 * @param {Function} reject A function that rejects the promise.
 * @param {object} response The response.
 */
module.exports = function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  // Note: status is not exposed by XDomainRequest
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};


/***/ }),
/* 40 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


/**
 * Update an Error with the specified config, error code, and response.
 *
 * @param {Error} error The error to update.
 * @param {Object} config The config.
 * @param {string} [code] The error code (for example, 'ECONNABORTED').
 * @param {Object} [request] The request.
 * @param {Object} [response] The response.
 * @returns {Error} The error.
 */
module.exports = function enhanceError(error, config, code, request, response) {
  error.config = config;
  if (code) {
    error.code = code;
  }
  error.request = request;
  error.response = response;
  return error;
};


/***/ }),
/* 41 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

function encode(val) {
  return encodeURIComponent(val).
    replace(/%40/gi, '@').
    replace(/%3A/gi, ':').
    replace(/%24/g, '$').
    replace(/%2C/gi, ',').
    replace(/%20/g, '+').
    replace(/%5B/gi, '[').
    replace(/%5D/gi, ']');
}

/**
 * Build a URL by appending params to the end
 *
 * @param {string} url The base of the url (e.g., http://www.google.com)
 * @param {object} [params] The params to be appended
 * @returns {string} The formatted url
 */
module.exports = function buildURL(url, params, paramsSerializer) {
  /*eslint no-param-reassign:0*/
  if (!params) {
    return url;
  }

  var serializedParams;
  if (paramsSerializer) {
    serializedParams = paramsSerializer(params);
  } else if (utils.isURLSearchParams(params)) {
    serializedParams = params.toString();
  } else {
    var parts = [];

    utils.forEach(params, function serialize(val, key) {
      if (val === null || typeof val === 'undefined') {
        return;
      }

      if (utils.isArray(val)) {
        key = key + '[]';
      }

      if (!utils.isArray(val)) {
        val = [val];
      }

      utils.forEach(val, function parseValue(v) {
        if (utils.isDate(v)) {
          v = v.toISOString();
        } else if (utils.isObject(v)) {
          v = JSON.stringify(v);
        }
        parts.push(encode(key) + '=' + encode(v));
      });
    });

    serializedParams = parts.join('&');
  }

  if (serializedParams) {
    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
  }

  return url;
};


/***/ }),
/* 42 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

/**
 * Parse headers into an object
 *
 * ```
 * Date: Wed, 27 Aug 2014 08:58:49 GMT
 * Content-Type: application/json
 * Connection: keep-alive
 * Transfer-Encoding: chunked
 * ```
 *
 * @param {String} headers Headers needing to be parsed
 * @returns {Object} Headers parsed into an object
 */
module.exports = function parseHeaders(headers) {
  var parsed = {};
  var key;
  var val;
  var i;

  if (!headers) { return parsed; }

  utils.forEach(headers.split('\n'), function parser(line) {
    i = line.indexOf(':');
    key = utils.trim(line.substr(0, i)).toLowerCase();
    val = utils.trim(line.substr(i + 1));

    if (key) {
      parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
    }
  });

  return parsed;
};


/***/ }),
/* 43 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

module.exports = (
  utils.isStandardBrowserEnv() ?

  // Standard browser envs have full support of the APIs needed to test
  // whether the request URL is of the same origin as current location.
  (function standardBrowserEnv() {
    var msie = /(msie|trident)/i.test(navigator.userAgent);
    var urlParsingNode = document.createElement('a');
    var originURL;

    /**
    * Parse a URL to discover it's components
    *
    * @param {String} url The URL to be parsed
    * @returns {Object}
    */
    function resolveURL(url) {
      var href = url;

      if (msie) {
        // IE needs attribute set twice to normalize properties
        urlParsingNode.setAttribute('href', href);
        href = urlParsingNode.href;
      }

      urlParsingNode.setAttribute('href', href);

      // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
      return {
        href: urlParsingNode.href,
        protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
        host: urlParsingNode.host,
        search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
        hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
        hostname: urlParsingNode.hostname,
        port: urlParsingNode.port,
        pathname: (urlParsingNode.pathname.charAt(0) === '/') ?
                  urlParsingNode.pathname :
                  '/' + urlParsingNode.pathname
      };
    }

    originURL = resolveURL(window.location.href);

    /**
    * Determine if a URL shares the same origin as the current location
    *
    * @param {String} requestURL The URL to test
    * @returns {boolean} True if URL shares the same origin, otherwise false
    */
    return function isURLSameOrigin(requestURL) {
      var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL;
      return (parsed.protocol === originURL.protocol &&
            parsed.host === originURL.host);
    };
  })() :

  // Non standard browser envs (web workers, react-native) lack needed support.
  (function nonStandardBrowserEnv() {
    return function isURLSameOrigin() {
      return true;
    };
  })()
);


/***/ }),
/* 44 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


// btoa polyfill for IE<10 courtesy https://github.com/davidchambers/Base64.js

var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

function E() {
  this.message = 'String contains an invalid character';
}
E.prototype = new Error;
E.prototype.code = 5;
E.prototype.name = 'InvalidCharacterError';

function btoa(input) {
  var str = String(input);
  var output = '';
  for (
    // initialize result and counter
    var block, charCode, idx = 0, map = chars;
    // if the next str index does not exist:
    //   change the mapping table to "="
    //   check if d has no fractional digits
    str.charAt(idx | 0) || (map = '=', idx % 1);
    // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
    output += map.charAt(63 & block >> 8 - idx % 1 * 8)
  ) {
    charCode = str.charCodeAt(idx += 3 / 4);
    if (charCode > 0xFF) {
      throw new E();
    }
    block = block << 8 | charCode;
  }
  return output;
}

module.exports = btoa;


/***/ }),
/* 45 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

module.exports = (
  utils.isStandardBrowserEnv() ?

  // Standard browser envs support document.cookie
  (function standardBrowserEnv() {
    return {
      write: function write(name, value, expires, path, domain, secure) {
        var cookie = [];
        cookie.push(name + '=' + encodeURIComponent(value));

        if (utils.isNumber(expires)) {
          cookie.push('expires=' + new Date(expires).toGMTString());
        }

        if (utils.isString(path)) {
          cookie.push('path=' + path);
        }

        if (utils.isString(domain)) {
          cookie.push('domain=' + domain);
        }

        if (secure === true) {
          cookie.push('secure');
        }

        document.cookie = cookie.join('; ');
      },

      read: function read(name) {
        var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
        return (match ? decodeURIComponent(match[3]) : null);
      },

      remove: function remove(name) {
        this.write(name, '', Date.now() - 86400000);
      }
    };
  })() :

  // Non standard browser env (web workers, react-native) lack needed support.
  (function nonStandardBrowserEnv() {
    return {
      write: function write() {},
      read: function read() { return null; },
      remove: function remove() {}
    };
  })()
);


/***/ }),
/* 46 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

function InterceptorManager() {
  this.handlers = [];
}

/**
 * Add a new interceptor to the stack
 *
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;


/***/ }),
/* 47 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);
var transformData = __webpack_require__(48);
var isCancel = __webpack_require__(17);
var defaults = __webpack_require__(6);

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Ensure headers exist
  config.headers = config.headers || {};

  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};


/***/ }),
/* 48 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var utils = __webpack_require__(0);

/**
 * Transform the data for a request or a response
 *
 * @param {Object|String} data The data to be transformed
 * @param {Array} headers The headers for the request or response
 * @param {Array|Function} fns A single function or Array of functions
 * @returns {*} The resulting transformed data
 */
module.exports = function transformData(data, headers, fns) {
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};


/***/ }),
/* 49 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


/**
 * Determines whether the specified URL is absolute
 *
 * @param {string} url The URL to test
 * @returns {boolean} True if the specified URL is absolute, otherwise false
 */
module.exports = function isAbsoluteURL(url) {
  // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
  // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
  // by any combination of letters, digits, plus, period, or hyphen.
  return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
};


/***/ }),
/* 50 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


/**
 * Creates a new URL by combining the specified URLs
 *
 * @param {string} baseURL The base URL
 * @param {string} relativeURL The relative URL
 * @returns {string} The combined URL
 */
module.exports = function combineURLs(baseURL, relativeURL) {
  return relativeURL
    ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
    : baseURL;
};


/***/ }),
/* 51 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var Cancel = __webpack_require__(18);

/**
 * A `CancelToken` is an object that can be used to request cancellation of an operation.
 *
 * @class
 * @param {Function} executor The executor function.
 */
function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

/**
 * Returns an object that contains a new `CancelToken` and a function that, when called,
 * cancels the `CancelToken`.
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;


/***/ }),
/* 52 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


/**
 * Syntactic sugar for invoking a function and expanding an array for arguments.
 *
 * Common use case would be to use `Function.prototype.apply`.
 *
 *  ```js
 *  function f(x, y, z) {}
 *  var args = [1, 2, 3];
 *  f.apply(null, args);
 *  ```
 *
 * With `spread` this example can be re-written.
 *
 *  ```js
 *  spread(function(x, y, z) {})([1, 2, 3]);
 *  ```
 *
 * @param {Function} callback
 * @returns {Function}
 */
module.exports = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};


/***/ }),
/* 53 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

/*
 This file is part of TALER
 (C) 2016 INRIA

 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/>
 */
Object.defineProperty(exports, "__esModule", { value: true });
const compat_1 = __webpack_require__(20);
/**
 * Polyfill for requestAnimationFrame, which
 * doesn't work from a background page.
 */
function rAF(cb) {
    window.setTimeout(() => {
        cb(performance.now());
    }, 100 /* 100 ms delay between frames */);
}
/**
 * Badge for Chrome that renders a Taler logo with a rotating ring if some
 * background activity is happening.
 */
class ChromeBadge {
    constructor(window) {
        /**
         * True if animation running.  The animation
         * might still be running even if we're not busy anymore,
         * just to transition to the "normal" state in a animated way.
         */
        this.animationRunning = false;
        /**
         * Is the wallet still busy? Note that we do not stop the
         * animation immediately when the wallet goes idle, but
         * instead slowly close the gap.
         */
        this.isBusy = false;
        /**
         * Current rotation angle, ranges from 0 to rotationAngleMax.
         */
        this.rotationAngle = 0;
        /**
         * While animating, how wide is the current gap in the circle?
         * Ranges from 0 to openMax.
         */
        this.gapWidth = 0;
        /**
         * Should we show the notification dot?
         */
        this.hasNotification = false;
        // Allow injecting another window for testing
        const bg = window || chrome.extension.getBackgroundPage();
        if (!bg) {
            throw Error("no window available");
        }
        this.canvas = bg.document.createElement("canvas");
        // Note: changing the width here means changing the font
        // size in draw() as well!
        this.canvas.width = 32;
        this.canvas.height = 32;
        this.ctx = this.canvas.getContext("2d");
        this.draw();
    }
    /**
     * Draw the badge based on the current state.
     */
    draw() {
        this.ctx.setTransform(1, 0, 0, 1, 0, 0);
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);
        this.ctx.beginPath();
        this.ctx.arc(0, 0, this.canvas.width / 2 - 2, 0, 2 * Math.PI);
        this.ctx.fillStyle = "white";
        this.ctx.fill();
        // move into the center, off by 2 for aligning the "T" with the bottom
        // of the circle.
        this.ctx.translate(0, 2);
        // pick sans-serif font; note: 14px is based on the 32px width above!
        this.ctx.font = "bold 24px sans-serif";
        // draw the "T" perfectly centered (x and y) to the current position
        this.ctx.textAlign = "center";
        this.ctx.textBaseline = "middle";
        this.ctx.fillStyle = "black";
        this.ctx.fillText("T", 0, 0);
        // now move really into the center
        this.ctx.translate(0, -2);
        // start drawing the (possibly open) circle
        this.ctx.beginPath();
        this.ctx.lineWidth = 2.5;
        if (this.animationRunning) {
            /* Draw circle around the "T" with an opening of this.gapWidth */
            const aMax = ChromeBadge.rotationAngleMax;
            const startAngle = this.rotationAngle / aMax * Math.PI * 2;
            const stopAngle = ((this.rotationAngle + aMax - this.gapWidth) / aMax) * Math.PI * 2;
            this.ctx.arc(0, 0, this.canvas.width / 2 - 2, /* radius */ startAngle, stopAngle, false);
        }
        else {
            /* Draw full circle */
            this.ctx.arc(0, 0, this.canvas.width / 2 - 2, /* radius */ 0, Math.PI * 2, false);
        }
        this.ctx.stroke();
        // go back to the origin
        this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
        if (this.hasNotification) {
            // We draw a circle with a soft border in the
            // lower right corner.
            const r = 8;
            const cw = this.canvas.width;
            const ch = this.canvas.height;
            this.ctx.beginPath();
            this.ctx.arc(cw - r, ch - r, r, 0, 2 * Math.PI, false);
            const gradient = this.ctx.createRadialGradient(cw - r, ch - r, r, cw - r, ch - r, 5);
            gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
            gradient.addColorStop(1, "blue");
            this.ctx.fillStyle = gradient;
            this.ctx.fill();
        }
        // Allow running outside the extension for testing
        // tslint:disable-next-line:no-string-literal
        if (window["chrome"] && window.chrome["browserAction"]) {
            try {
                const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
                chrome.browserAction.setIcon({ imageData });
            }
            catch (e) {
                // Might fail if browser has over-eager canvas fingerprinting countermeasures.
                // There's nothing we can do then ...
            }
        }
    }
    animate() {
        if (this.animationRunning) {
            return;
        }
        if (compat_1.isFirefox()) {
            // Firefox does not support badge animations properly
            return;
        }
        this.animationRunning = true;
        let start;
        const step = (timestamp) => {
            if (!this.animationRunning) {
                return;
            }
            if (!start) {
                start = timestamp;
            }
            if (!this.isBusy && 0 === this.gapWidth) {
                // stop if we're close enough to origin
                this.rotationAngle = 0;
            }
            else {
                this.rotationAngle = (this.rotationAngle + (timestamp - start) *
                    ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax;
            }
            if (this.isBusy) {
                if (this.gapWidth < ChromeBadge.openMax) {
                    this.gapWidth += ChromeBadge.openSpeed * (timestamp - start);
                }
                if (this.gapWidth > ChromeBadge.openMax) {
                    this.gapWidth = ChromeBadge.openMax;
                }
            }
            else {
                if (this.gapWidth > 0) {
                    this.gapWidth--;
                    this.gapWidth *= ChromeBadge.closeSpeed;
                }
            }
            if (this.isBusy || this.gapWidth > 0) {
                start = timestamp;
                rAF(step);
            }
            else {
                this.animationRunning = false;
            }
            this.draw();
        };
        rAF(step);
    }
    /**
     * Draw the badge such that it shows the
     * user that something happened (balance changed).
     */
    showNotification() {
        this.hasNotification = true;
        this.draw();
    }
    /**
     * Draw the badge without the notification mark.
     */
    clearNotification() {
        this.hasNotification = false;
        this.draw();
    }
    startBusy() {
        if (this.isBusy) {
            return;
        }
        this.isBusy = true;
        this.animate();
    }
    stopBusy() {
        this.isBusy = false;
    }
}
/**
 * Maximum value for our rotationAngle, corresponds to 2 Pi.
 */
ChromeBadge.rotationAngleMax = 1000;
/**
 * How fast do we rotate?  Given in rotation angle (relative to rotationAngleMax) per millisecond.
 */
ChromeBadge.rotationSpeed = 0.5;
/**
 * How fast to we open?  Given in rotation angle (relative to rotationAngleMax) per millisecond.
 */
ChromeBadge.openSpeed = 0.15;
/**
 * How fast to we close?  Given as a multiplication factor per frame update.
 */
ChromeBadge.closeSpeed = 0.7;
/**
 * How far do we open? Given relative to rotationAngleMax.
 */
ChromeBadge.openMax = 100;
exports.ChromeBadge = ChromeBadge;


/***/ })
/******/ ]);
//# sourceMappingURL=background-bundle.js.map